diff --git a/docs/docs/cmd/entra/agent/agent-list.mdx b/docs/docs/cmd/entra/agent/agent-list.mdx new file mode 100644 index 00000000000..24e0ead28b6 --- /dev/null +++ b/docs/docs/cmd/entra/agent/agent-list.mdx @@ -0,0 +1,209 @@ +import Global from '../../_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# entra agent list + +Retrieves a list of agents + +## Usage + +```sh +m365 entra agent list [options] +``` + +## Options + +```md definition-list +`-p, --properties [properties]` +: Comma-separated list of properties to be returned. +``` + + + +## Remarks + +Using the `--properties` option, you can specify a comma-separated list of agent properties to retrieve from the Microsoft Graph. If you don't specify any properties, the command will output the default properties returned by Graph. + +## Permissions + + + + + | Resource | Permissions | + |-----------------|--------------------| + | Microsoft Graph | User.ReadBasic.All | + + + + + | Resource | Permissions | + |-----------------|--------------------| + | Microsoft Graph | User.ReadBasic.All | + + + + +## Examples + +Retrieve all agents. + +```sh +m365 entra agent list +``` + +Retrieve id and name of all agents + +```sh +m365 entra agent list --properties 'id,displayName' +``` + +## Response + + + + + ```json + [ + { + "id": "819f4e7f-a913-448a-9b5c-2cae064cabe7", + "deletedDateTime": null, + "accountEnabled": true, + "ageGroup": null, + "businessPhones": [], + "city": null, + "companyName": null, + "consentProvidedForMinor": null, + "country": null, + "createdDateTime": "2026-07-14T04:19:25Z", + "creationType": null, + "department": null, + "displayName": "Sales Agent", + "employeeId": null, + "employeeHireDate": null, + "employeeLeaveDateTime": null, + "employeeType": null, + "externalUserState": null, + "externalUserStateChangeDateTime": null, + "faxNumber": null, + "givenName": null, + "isLicenseReconciliationNeeded": false, + "jobTitle": null, + "legalAgeGroupClassification": null, + "mail": null, + "mailNickname": "SalesAgent", + "mobilePhone": null, + "onPremisesDistinguishedName": null, + "onPremisesDomainName": null, + "onPremisesImmutableId": null, + "onPremisesLastSyncDateTime": null, + "onPremisesSecurityIdentifier": null, + "onPremisesSamAccountName": null, + "onPremisesSyncEnabled": null, + "onPremisesUserPrincipalName": null, + "otherMails": [], + "passwordPolicies": null, + "officeLocation": null, + "postalCode": null, + "preferredDataLocation": null, + "preferredLanguage": null, + "proxyAddresses": [], + "refreshTokensValidFromDateTime": null, + "imAddresses": [], + "isResourceAccount": null, + "showInAddressList": null, + "securityIdentifier": "S-1-12-1-2174701183-1149937939-2922142875-3869985798", + "signInSessionsValidFromDateTime": null, + "state": null, + "streetAddress": null, + "surname": null, + "usageLocation": null, + "userPrincipalName": "SalesAgent@contoso.com", + "externalUserConvertedOn": null, + "userType": "Member", + "identityParentId": "54d8c728-decb-477b-beb6-19570d8a51ab", + "agentIdentityBlueprintId": "822c92e8-29d7-4f83-903a-744d00628003", + "employeeOrgData": null, + "passwordProfile": null, + "identityParent": { + "id": "54d8c728-decb-477b-beb6-19570d8a51ab" + }, + "assignedLicenses": [], + "assignedPlans": [], + "authorizationInfo": { + "certificateUserIds": [] + }, + "identities": [ + { + "signInType": "userPrincipalName", + "issuer": "contoso.com", + "issuerAssignedId": "SalesAgent@contoso.com" + } + ], + "onPremisesProvisioningErrors": [], + "onPremisesExtensionAttributes": { + "extensionAttribute1": null, + "extensionAttribute2": null, + "extensionAttribute3": null, + "extensionAttribute4": null, + "extensionAttribute5": null, + "extensionAttribute6": null, + "extensionAttribute7": null, + "extensionAttribute8": null, + "extensionAttribute9": null, + "extensionAttribute10": null, + "extensionAttribute11": null, + "extensionAttribute12": null, + "extensionAttribute13": null, + "extensionAttribute14": null, + "extensionAttribute15": null + }, + "provisionedPlans": [], + "serviceProvisioningErrors": [] + } + ] + ``` + + + + + ```text + displayName: Sales Agent + id : 819f4e7f-a913-448a-9b5c-2cae064cabe7 + ``` + + + + + ```csv + id,deletedDateTime,accountEnabled,ageGroup,city,companyName,consentProvidedForMinor,country,createdDateTime,creationType,department,displayName,employeeId,employeeHireDate,employeeLeaveDateTime,employeeType,externalUserState,externalUserStateChangeDateTime,faxNumber,givenName,isLicenseReconciliationNeeded,jobTitle,legalAgeGroupClassification,mail,mailNickname,mobilePhone,onPremisesDistinguishedName,onPremisesDomainName,onPremisesImmutableId,onPremisesLastSyncDateTime,onPremisesSecurityIdentifier,onPremisesSamAccountName,onPremisesSyncEnabled,onPremisesUserPrincipalName,passwordPolicies,officeLocation,postalCode,preferredDataLocation,preferredLanguage,refreshTokensValidFromDateTime,isResourceAccount,showInAddressList,securityIdentifier,signInSessionsValidFromDateTime,state,streetAddress,surname,usageLocation,userPrincipalName,externalUserConvertedOn,userType,identityParentId,agentIdentityBlueprintId,employeeOrgData,passwordProfile + 819f4e7f-a913-448a-9b5c-2cae064cabe7,,1,,,,,,2026-07-14T04:19:25Z,,,Sales Agent,,,,,,,,,0,,,,SalesAgent,,,,,,,,,,,,,,,,,,S-1-12-1-2174701183-1149937939-2922142875-3869985798,,,,,,SalesAgent@contoso.com,,Member,54d8c728-decb-477b-beb6-19570d8a51ab,822c92e8-29d7-4f83-903a-744d00628003,, + ``` + + + + + ```md + # entra agent list --debug "false" --verbose "false" + + Date: 7/14/2026 + + ## Sales Agent (819f4e7f-a913-448a-9b5c-2cae064cabe7) + + Property | Value + ---------|------- + id | 819f4e7f-a913-448a-9b5c-2cae064cabe7 + accountEnabled | true + createdDateTime | 2026-07-14T04:19:25Z + displayName | Sales Agent + isLicenseReconciliationNeeded | false + mailNickname | SalesAgent + securityIdentifier | S-1-12-1-2174701183-1149937939-2922142875-3869985798 + userPrincipalName | SalesAgent@contoso.com + userType | Member + identityParentId | 54d8c728-decb-477b-beb6-19570d8a51ab + agentIdentityBlueprintId | 822c92e8-29d7-4f83-903a-744d00628003 + ``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index d62a65500a7..6fac3d0bde5 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -249,6 +249,15 @@ const sidebars: SidebarsConfig = { } ] }, + { + agent: [ + { + type: 'doc', + label: 'agent list', + id: 'cmd/entra/agent/agent-list' + } + ] + }, { app: [ { diff --git a/eslint.config.mjs b/eslint.config.mjs index 925703e197b..8b6515e1671 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -14,6 +14,7 @@ const dictionary = [ 'activations', 'adaptive', 'administrative', + 'agent', 'ai', 'app', 'application', diff --git a/src/config.ts b/src/config.ts index 8218037c14f..0097e3ab5b4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -57,6 +57,7 @@ export default { 'https://graph.microsoft.com/TeamSettings.ReadWrite.All', 'https://graph.microsoft.com/TeamsTab.ReadWrite.All', 'https://graph.microsoft.com/User.Invite.All', + 'https://graph.microsoft.com/User.ReadBasic.All', 'https://manage.office.com/ActivityFeed.Read', 'https://manage.office.com/ServiceHealth.Read', 'https://analysis.windows.net/powerbi/api/Dataset.Read.All', diff --git a/src/m365/entra/commands.ts b/src/m365/entra/commands.ts index 1b9476ec605..c660eb6b6e3 100644 --- a/src/m365/entra/commands.ts +++ b/src/m365/entra/commands.ts @@ -10,6 +10,7 @@ export default { ADMINISTRATIVEUNIT_MEMBER_LIST: `${prefix} administrativeunit member list`, ADMINISTRATIVEUNIT_MEMBER_REMOVE: `${prefix} administrativeunit member remove`, ADMINISTRATIVEUNIT_ROLEASSIGNMENT_ADD: `${prefix} administrativeunit roleassignment add`, + AGENT_LIST: `${prefix} agent list`, APP_ADD: `${prefix} app add`, APP_GET: `${prefix} app get`, APP_LIST: `${prefix} app list`, diff --git a/src/m365/entra/commands/agent/agent-list.spec.ts b/src/m365/entra/commands/agent/agent-list.spec.ts new file mode 100644 index 00000000000..025379565c7 --- /dev/null +++ b/src/m365/entra/commands/agent/agent-list.spec.ts @@ -0,0 +1,221 @@ +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 { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import commands from '../../commands.js'; +import command, { options } from './agent-list.js'; + +describe(commands.AGENT_LIST, () => { + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; + + const response = [ + { + "id": "819f4e7f-a913-448a-9b5c-2cae064cabe7", + "deletedDateTime": null, + "accountEnabled": true, + "ageGroup": null, + "businessPhones": [], + "city": null, + "companyName": null, + "consentProvidedForMinor": null, + "country": null, + "createdDateTime": "2026-07-14T04:19:25Z", + "creationType": null, + "department": null, + "displayName": "Sales Agent", + "employeeId": null, + "employeeHireDate": null, + "employeeLeaveDateTime": null, + "employeeType": null, + "externalUserState": null, + "externalUserStateChangeDateTime": null, + "faxNumber": null, + "givenName": null, + "isLicenseReconciliationNeeded": false, + "jobTitle": null, + "legalAgeGroupClassification": null, + "mail": null, + "mailNickname": "SalesAgent", + "mobilePhone": null, + "onPremisesDistinguishedName": null, + "onPremisesDomainName": null, + "onPremisesImmutableId": null, + "onPremisesLastSyncDateTime": null, + "onPremisesSecurityIdentifier": null, + "onPremisesSamAccountName": null, + "onPremisesSyncEnabled": null, + "onPremisesUserPrincipalName": null, + "otherMails": [], + "passwordPolicies": null, + "officeLocation": null, + "postalCode": null, + "preferredDataLocation": null, + "preferredLanguage": null, + "proxyAddresses": [], + "refreshTokensValidFromDateTime": null, + "imAddresses": [], + "isResourceAccount": null, + "showInAddressList": null, + "securityIdentifier": "S-1-12-1-2174701183-1149937939-2922142875-3869985798", + "signInSessionsValidFromDateTime": null, + "state": null, + "streetAddress": null, + "surname": null, + "usageLocation": null, + "userPrincipalName": "SalesAgent@contoso.com", + "externalUserConvertedOn": null, + "userType": "Member", + "identityParentId": "54d8c728-decb-477b-beb6-19570d8a51ab", + "agentIdentityBlueprintId": "822c92e8-29d7-4f83-903a-744d00628003", + "employeeOrgData": null, + "passwordProfile": null, + "identityParent": { + "id": "54d8c728-decb-477b-beb6-19570d8a51ab" + }, + "assignedLicenses": [], + "assignedPlans": [], + "authorizationInfo": { + "certificateUserIds": [] + }, + "identities": [ + { + "signInType": "userPrincipalName", + "issuer": "contoso.com", + "issuerAssignedId": "SalesAgent@contoso.com" + } + ], + "onPremisesProvisioningErrors": [], + "onPremisesExtensionAttributes": { + "extensionAttribute1": null, + "extensionAttribute2": null, + "extensionAttribute3": null, + "extensionAttribute4": null, + "extensionAttribute5": null, + "extensionAttribute6": null, + "extensionAttribute7": null, + "extensionAttribute8": null, + "extensionAttribute9": null, + "extensionAttribute10": null, + "extensionAttribute11": null, + "extensionAttribute12": null, + "extensionAttribute13": null, + "extensionAttribute14": null, + "extensionAttribute15": null + }, + "provisionedPlans": [], + "serviceProvisioningErrors": [] + } + ]; + + const propertiesResponse = [ + { + "displayName": "Sales Agent", + "id": "819f4e7f-a913-448a-9b5c-2cae064cabe7" + } + ]; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').resolves(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; + }); + + 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.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.AGENT_LIST); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('defines correct properties for the default output', () => { + assert.deepStrictEqual(command.defaultProperties(), ['id', 'displayName']); + }); + + it(`should get a list of agents`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/users/microsoft.graph.agentUser`) { + return { + value: response + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({}) }); + assert(loggerLogSpy.calledWith(response)); + }); + + it(`should get a list of agents with specified properties`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/users/microsoft.graph.agentUser?$select=id,displayName`) { + return { + value: propertiesResponse + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ properties: 'id,displayName' }) }); + assert(loggerLogSpy.calledWith(propertiesResponse)); + }); + + it('handles error when retrieving agents list failed', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/users/microsoft.graph.agentUser`) { + throw { error: { message: 'An error has occurred' } }; + } + throw `Invalid request`; + }); + + await assert.rejects( + command.action(logger, { options: commandOptionsSchema.parse({}) }), + new CommandError('An error has occurred') + ); + }); +}); \ No newline at end of file diff --git a/src/m365/entra/commands/agent/agent-list.ts b/src/m365/entra/commands/agent/agent-list.ts new file mode 100644 index 00000000000..7aa3fb74c4d --- /dev/null +++ b/src/m365/entra/commands/agent/agent-list.ts @@ -0,0 +1,64 @@ +import { z } from 'zod'; +import { Logger } from '../../../../cli/Logger.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import { odata } from '../../../../utils/odata.js'; +import GraphCommand from '../../../base/GraphCommand.js'; +import commands from '../../commands.js'; + +export const options = z.strictObject({ + ...globalOptionsZod.shape, + properties: z.string().optional().alias('p') +}); +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +class EntraAgentListCommand extends GraphCommand { + public get name(): string { + return commands.AGENT_LIST; + } + + public get description(): string { + return 'Retrieves a list of agents'; + } + + public get schema(): z.ZodType | undefined { + return options; + } + + public defaultProperties(): string[] | undefined { + return ['id', 'displayName']; + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + const queryParameters: string[] = []; + + if (args.options.properties) { + const allProperties = args.options.properties + .split(',') + .map(prop => prop.replace(/['"]/g, '').trim()) + .filter(prop => prop.length > 0); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + + try { + const results = await odata.getAllItems(`${this.resource}/v1.0/users/microsoft.graph.agentUser${queryString}`); + await logger.log(results); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new EntraAgentListCommand(); \ No newline at end of file