diff --git a/docs/docs/cmd/tenant/info/info-get.mdx b/docs/docs/cmd/tenant/info/info-get.mdx
new file mode 100644
index 00000000000..ca0b62965ee
--- /dev/null
+++ b/docs/docs/cmd/tenant/info/info-get.mdx
@@ -0,0 +1,101 @@
+import Global from '/docs/cmd/_global.mdx';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# tenant info get
+
+Gets information about any tenant
+
+## Usage
+
+```sh
+m365 tenant info get [options]
+```
+
+## Options
+
+```md definition-list
+`-d, --domainName [domainName]`
+: The primary domain name of a tenant. Optionally specify either `domainName` or `tenantId` but not both. If none are specified, the tenantId of the currently signed-in user is used.
+
+`-i, --tenantId [tenantId]`
+: The unique tenant identifier of a tenant. Optionally specify either `domainName` or `tenantId` but not both. If none are specified, the tenantId of the currently signed-in user is used.
+```
+
+
+
+## Remarks
+
+If no domain name or tenantId is specified, the command will return the tenant information of the currently signed in user.
+
+## Examples
+
+Get tenant information for the currently signed in user.
+
+```sh
+m365 tenant info get
+```
+
+Get tenant information for the Contoso tenant.
+
+```sh
+m365 tenant info get --domainName contoso.com
+```
+
+Get tenant information by id.
+
+```sh
+m365 tenant info get --tenantId e65b162c-6f87-4eb1-a24e-1b37d3504663
+```
+
+## Response
+
+
+
+
+ ```json
+ {
+ "tenantId": "e65b162c-6f87-4eb1-a24e-1b37d3504663",
+ "federationBrandName": null,
+ "displayName": "Contoso",
+ "defaultDomainName": "contoso.com"
+ }
+ ```
+
+
+
+
+ ```text
+ defaultDomainName : contoso.com
+ displayName : Contoso
+ federationBrandName : null
+ tenantId : e65b162c-6f87-4eb1-a24e-1b37d3504663
+ ```
+
+
+
+
+ ```csv
+ tenantId,displayName,defaultDomainName
+ e65b162c-6f87-4eb1-a24e-1b37d3504663,Contoso,contoso.com
+ ```
+
+
+
+
+ ```md
+ # tenant info get
+
+ Date: 9/14/2023
+
+ ## Contoso
+
+ Property | Value
+ ---------|-------
+ tenantId | e65b162c-6f87-4eb1-a24e-1b37d3504663
+ displayName | Contoso
+ defaultDomainName | contoso.com
+ ```
+
+
+
diff --git a/docs/src/config/sidebars.js b/docs/src/config/sidebars.js
index 5183d89f4a5..af62ef54fbc 100644
--- a/docs/src/config/sidebars.js
+++ b/docs/src/config/sidebars.js
@@ -630,6 +630,15 @@ const sidebars = {
}
]
},
+ {
+ info: [
+ {
+ type: 'doc',
+ label: 'info get',
+ id: 'cmd/tenant/info/info-get'
+ }
+ ]
+ },
{
report: [
{
diff --git a/src/m365/tenant/commands.ts b/src/m365/tenant/commands.ts
index 28f5dad2faa..85a06726e6c 100644
--- a/src/m365/tenant/commands.ts
+++ b/src/m365/tenant/commands.ts
@@ -2,13 +2,14 @@ const prefix: string = 'tenant';
export default {
ID_GET: `${prefix} id get`,
+ INFO_GET: `${prefix} info get`,
REPORT_ACTIVEUSERCOUNTS: `${prefix} report activeusercounts`,
REPORT_ACTIVEUSERDETAIL: `${prefix} report activeuserdetail`,
REPORT_OFFICE365ACTIVATIONCOUNTS: `${prefix} report office365activationcounts`,
REPORT_OFFICE365ACTIVATIONSUSERDETAIL: `${prefix} report office365activationsuserdetail`,
REPORT_OFFICE365ACTIVATIONSUSERCOUNTS: `${prefix} report office365activationsusercounts`,
REPORT_SERVICESUSERCOUNTS: `${prefix} report servicesusercounts`,
- SECURITY_ALERTS_LIST:`${prefix} security alerts list`,
+ SECURITY_ALERTS_LIST: `${prefix} security alerts list`,
SERVICEANNOUNCEMENT_HEALTHISSUE_GET: `${prefix} serviceannouncement healthissue get`,
SERVICEANNOUNCEMENT_HEALTH_GET: `${prefix} serviceannouncement health get`,
SERVICEANNOUNCEMENT_HEALTH_LIST: `${prefix} serviceannouncement health list`,
diff --git a/src/m365/tenant/commands/info/info-get.spec.ts b/src/m365/tenant/commands/info/info-get.spec.ts
new file mode 100644
index 00000000000..cf70d172d0c
--- /dev/null
+++ b/src/m365/tenant/commands/info/info-get.spec.ts
@@ -0,0 +1,189 @@
+import assert from 'assert';
+import sinon from 'sinon';
+import auth from '../../../../Auth.js';
+import { Cli } from '../../../../cli/Cli.js';
+import { CommandInfo } from '../../../../cli/CommandInfo.js';
+import { Logger } from '../../../../cli/Logger.js';
+import { CommandError } from '../../../../Command.js';
+import request from '../../../../request.js';
+import { telemetry } from '../../../../telemetry.js';
+import { accessToken } from '../../../../utils/accessToken.js';
+import { pid } from '../../../../utils/pid.js';
+import { session } from '../../../../utils/session.js';
+import { sinonUtil } from '../../../../utils/sinonUtil.js';
+import commands from '../../commands.js';
+import command from './info-get.js';
+
+describe(commands.INFO_GET, () => {
+ const domainName = 'contoso.com';
+ const tenantId = 'e65b162c-6f87-4eb1-a24e-1b37d3504663';
+ const tenantInfoResponse = {
+ tenantId: tenantId,
+ federationBrandName: null,
+ displayName: "Contoso",
+ defaultDomainName: domainName
+ };
+
+ let log: any[];
+ let loggerLogSpy: sinon.SinonSpy;
+ let logger: Logger;
+ let commandInfo: CommandInfo;
+
+ before(() => {
+ sinon.stub(auth, 'restoreAuth').resolves();
+ sinon.stub(telemetry, 'trackEvent').returns();
+ sinon.stub(pid, 'getProcessName').returns('');
+ sinon.stub(session, 'getId').returns('');
+ auth.service.connected = true;
+ commandInfo = Cli.getCommandInfo(command);
+ if (!auth.service.accessTokens[auth.defaultResource]) {
+ auth.service.accessTokens[auth.defaultResource] = {
+ expiresOn: '123',
+ accessToken: 'abc'
+ };
+ }
+ });
+
+ beforeEach(() => {
+ log = [];
+ logger = {
+ log: async (msg: string) => {
+ log.push(msg);
+ },
+ logRaw: async (msg: string) => {
+ log.push(msg);
+ },
+ logToStderr: async (msg: string) => {
+ log.push(msg);
+ }
+ };
+ loggerLogSpy = sinon.spy(logger, 'log');
+ });
+
+ afterEach(() => {
+ sinonUtil.restore([
+ request.get
+ ]);
+ });
+
+ after(() => {
+ sinon.restore();
+ auth.service.connected = false;
+ });
+
+ it('has correct name', () => {
+ assert.strictEqual(command.name, commands.INFO_GET);
+ });
+
+ it('has a description', () => {
+ assert.notStrictEqual(command.description, null);
+ });
+
+ it('fails validation if the tenantId is not a valid guid', async () => {
+ const actual = await command.validate({ options: { tenantId: 'abc' } }, commandInfo);
+ assert.notStrictEqual(actual, true);
+ });
+
+ it('passes validation when the tenantId is a valid GUID', async () => {
+ const actual = await command.validate({ options: { tenantId: tenantId } }, commandInfo);
+ assert.strictEqual(actual, true);
+ });
+
+ it('fails validation if both domainName and tenantId are specified', async () => {
+ const actual = await command.validate({ options: { domainName: domainName, tenantId: tenantId } }, commandInfo);
+ assert.notStrictEqual(actual, true);
+ });
+
+ it('gets tenant information for the currently signed in user if no domain name or tenantId is passed', async () => {
+ sinon.stub(accessToken, 'getUserNameFromAccessToken').callsFake(() => {
+ return 'admin@contoso.com';
+ });
+
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/findTenantInformationByDomainName(domainName='contoso.com')`) {
+ return tenantInfoResponse;
+ }
+
+ throw 'Invalid Request';
+ });
+
+ await command.action(logger, { options: { verbose: true } });
+ assert(loggerLogSpy.calledOnceWithExactly(tenantInfoResponse));
+ sinonUtil.restore(accessToken.getUserNameFromAccessToken);
+ });
+
+ it('gets tenant information with correct domain name', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/findTenantInformationByDomainName(domainName='contoso.com')`) {
+ return tenantInfoResponse;
+ }
+
+ throw 'Invalid Request';
+ });
+
+ await command.action(logger, { options: { verbose: true, domainName: domainName } });
+ assert(loggerLogSpy.calledOnceWithExactly(tenantInfoResponse));
+ });
+
+ it('gets tenant information with correct tenant id', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/findTenantInformationByTenantId(tenantId='e65b162c-6f87-4eb1-a24e-1b37d3504663')`) {
+ return tenantInfoResponse;
+ }
+
+ throw 'Invalid Request';
+ });
+
+ await command.action(logger, { options: { verbose: true, tenantId: tenantId } });
+ assert(loggerLogSpy.calledOnceWithExactly(tenantInfoResponse));
+ });
+
+ it('handles error when trying to retrieve information for a non-existant tenant by id', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/findTenantInformationByTenantId(tenantId='e65b162c-6f87-4eb1-a24e-1b37d3504663')`) {
+ throw {
+ "error": {
+ "code": "Directory_ObjectNotFound",
+ "message": "Unable to read the company information from the directory.",
+ "innerError": {
+ "date": "2023-09-14T14:07:47",
+ "request-id": "3b91132c-5c79-454b-8dd4-06964e788a24",
+ "client-request-id": "2147e6c6-8036-cc2f-f4d0-eec89dbc48d7"
+ }
+ }
+ };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ await assert.rejects(command.action(logger, { options: { tenantId: tenantId } } as any), new CommandError("Unable to read the company information from the directory."));
+ });
+
+ it('handles error when trying to retrieve information for a non-existant tenant by domain name', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/findTenantInformationByDomainName(domainName='xyz.com')`) {
+ throw {
+ "error": {
+ "code": "Directory_ObjectNotFound",
+ "message": "Unable to read the company information from the directory.",
+ "innerError": {
+ "date": "2023-09-14T14:07:47",
+ "request-id": "3b91132c-5c79-454b-8dd4-06964e788a24",
+ "client-request-id": "2147e6c6-8036-cc2f-f4d0-eec89dbc48d7"
+ }
+ }
+ };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ await assert.rejects(command.action(logger, { options: { domainName: 'xyz.com' } } as any), new CommandError("Unable to read the company information from the directory."));
+ });
+
+ it('correctly handles random API error', async () => {
+ sinon.stub(request, 'get').rejects(new Error('An error has occurred'));
+ await assert.rejects(command.action(logger, { options: { domainName: 'xyz.com' } } as any), new CommandError('An error has occurred'));
+ });
+});
\ No newline at end of file
diff --git a/src/m365/tenant/commands/info/info-get.ts b/src/m365/tenant/commands/info/info-get.ts
new file mode 100644
index 00000000000..3a6173f3a1b
--- /dev/null
+++ b/src/m365/tenant/commands/info/info-get.ts
@@ -0,0 +1,109 @@
+import auth from '../../../../Auth.js';
+import { Logger } from '../../../../cli/Logger.js';
+import GlobalOptions from '../../../../GlobalOptions.js';
+import GraphCommand from '../../../base/GraphCommand.js';
+import request from '../../../../request.js';
+import { accessToken } from '../../../../utils/accessToken.js';
+import { formatting } from '../../../../utils/formatting.js';
+import { validation } from '../../../../utils/validation.js';
+import commands from '../../commands.js';
+
+interface CommandArgs {
+ options: Options;
+}
+
+interface Options extends GlobalOptions {
+ domainName?: string;
+ tenantId?: string;
+}
+
+class TenantInfoGetCommand extends GraphCommand {
+ public get name(): string {
+ return commands.INFO_GET;
+ }
+
+ public get description(): string {
+ return 'Gets information about any tenant';
+ }
+
+ constructor() {
+ super();
+
+ this.#initTelemetry();
+ this.#initOptions();
+ this.#initValidators();
+ }
+
+ #initTelemetry(): void {
+ this.telemetry.push((args: CommandArgs) => {
+ Object.assign(this.telemetryProperties, {
+ domainName: typeof args.options.domainName !== 'undefined',
+ tenantId: typeof args.options.tenantId !== 'undefined'
+ });
+ });
+ }
+
+ #initOptions(): void {
+ this.options.unshift(
+ {
+ option: '-d, --domainName [domainName]'
+ },
+ {
+ option: '-i, --tenantId [tenantId]'
+ }
+ );
+ }
+
+ #initValidators(): void {
+ this.validators.push(
+ async (args: CommandArgs) => {
+ if (args.options.tenantId && !validation.isValidGuid(args.options.tenantId)) {
+ return `${args.options.tenantId} is not a valid GUID`;
+ }
+
+ if (args.options.tenantId && args.options.domainName) {
+ return `Specify either domainName or tenantId but not both`;
+ }
+
+ return true;
+ }
+ );
+ }
+
+ public async commandAction(logger: Logger, args: CommandArgs): Promise {
+ let domainName: string | undefined = args.options.domainName;
+ const tenantId: string | undefined = args.options.tenantId;
+
+ if (!domainName && !tenantId) {
+ const userName: string = accessToken.getUserNameFromAccessToken(auth.service.accessTokens[auth.defaultResource].accessToken);
+ domainName = userName.split('@')[1];
+ }
+
+ let requestUrl = `${this.resource}/v1.0/tenantRelationships/`;
+
+ if (tenantId) {
+ requestUrl += `findTenantInformationByTenantId(tenantId='${formatting.encodeQueryParameter(tenantId)}')`;
+ }
+ else {
+ requestUrl += `findTenantInformationByDomainName(domainName='${formatting.encodeQueryParameter(domainName!)}')`;
+ }
+
+ const requestOptions: any = {
+ url: requestUrl,
+ headers: {
+ accept: 'application/json;odata.metadata=none'
+ },
+ responseType: 'json'
+ };
+
+ try {
+ const res: any = await request.get(requestOptions);
+ await logger.log(res);
+ }
+ catch (err: any) {
+ this.handleRejectedODataJsonPromise(err);
+ }
+ }
+}
+
+export default new TenantInfoGetCommand();
\ No newline at end of file