diff --git a/docs/docs/cmd/aad/group/group-user-list.mdx b/docs/docs/cmd/aad/group/group-user-list.mdx index e9e4409b667..0ec13e1ca0e 100644 --- a/docs/docs/cmd/aad/group/group-user-list.mdx +++ b/docs/docs/cmd/aad/group/group-user-list.mdx @@ -33,6 +33,10 @@ m365 aad group user list [options] +## Remarks + +When the `properties` option includes values with a `/`, for example: `memberof/id`, an additional `$expand` query parameter will be included on `memberof`. + ## Examples List all group users from a group specified by ID. @@ -47,10 +51,10 @@ List all owners from a group specified by display name. m365 aad group user list --groupDisplayName Developers --role Owner ``` -List all group users from a group specified by name. For each one return the display name and e-mail address +List all group users from a group specified by name. For each one return the display name, e-mail address, and memberof. ```sh -m365 aad group user list --groupDisplayName Developers --properties "displayName,mail" +m365 aad group user list --groupDisplayName Developers --properties "displayName,mail,memberof/id" ``` List all group members that are guest users. diff --git a/src/m365/aad/commands/group/group-user-list.spec.ts b/src/m365/aad/commands/group/group-user-list.spec.ts index 0f7df4f8335..c1f7776f14b 100644 --- a/src/m365/aad/commands/group/group-user-list.spec.ts +++ b/src/m365/aad/commands/group/group-user-list.spec.ts @@ -103,13 +103,13 @@ describe(commands.GROUP_USER_LIST, () => { it('correctly lists all users in a Azure AD group by id', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners?$select=id,displayName,userPrincipalName,givenName,surname`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname`) { return { "value": [{ "id": "00000000-0000-0000-0000-000000000000", "displayName": "Anne Matthews", "userPrincipalName": "anne.matthews@contoso.onmicrosoft.com", "givenName": "Anne", "surname": "Matthews" }] }; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members?$select=id,displayName,userPrincipalName,givenName,surname`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname`) { return { "value": [ { "id": "00000000-0000-0000-0000-000000000000", "displayName": "Anne Matthews", "userPrincipalName": "anne.matthews@contoso.onmicrosoft.com", "givenName": "Anne", "surname": "Matthews" }, @@ -147,13 +147,13 @@ describe(commands.GROUP_USER_LIST, () => { sinon.stub(aadGroup, 'getGroupIdByDisplayName').resolves(groupId); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners?$select=id,displayName,userPrincipalName,givenName,surname`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname`) { return { "value": [{ "id": "00000000-0000-0000-0000-000000000000", "displayName": "Anne Matthews", "userPrincipalName": "anne.matthews@contoso.onmicrosoft.com", "givenName": "Anne", "surname": "Matthews" }] }; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members?$select=id,displayName,userPrincipalName,givenName,surname`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname`) { return { "value": [ { "id": "00000000-0000-0000-0000-000000000000", "displayName": "Anne Matthews", "userPrincipalName": "anne.matthews@contoso.onmicrosoft.com", "givenName": "Anne", "surname": "Matthews" }, @@ -189,7 +189,7 @@ describe(commands.GROUP_USER_LIST, () => { it('correctly lists all owners in a Azure AD group', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners?$select=id,displayName,userPrincipalName,givenName,surname`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname`) { return { "value": [{ "id": "00000000-0000-0000-0000-000000000000", "displayName": "Anne Matthews", "userPrincipalName": "anne.matthews@contoso.onmicrosoft.com", "givenName": "Anne", "surname": "Matthews" }] }; @@ -212,7 +212,7 @@ describe(commands.GROUP_USER_LIST, () => { it('correctly lists all members in a Azure AD group', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members?$select=id,displayName,userPrincipalName,givenName,surname`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname`) { return { "value": [ { "id": "00000000-0000-0000-0000-000000000000", "displayName": "Anne Matthews", "userPrincipalName": "anne.matthews@contoso.onmicrosoft.com", "givenName": "Anne", "surname": "Matthews" }, @@ -248,18 +248,18 @@ describe(commands.GROUP_USER_LIST, () => { it('correctly lists properties for all users in a Azure AD group', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners?$select=displayName,mail,id`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners/microsoft.graph.user?$select=displayName,mail,id&$expand=memberof($select=id),memberof($select=displayName)`) { return { "value": [ - { "id": "00000000-0000-0000-0000-000000000000", "displayName": "Karl Matteson", "mail": "karl.matteson@contoso.onmicrosoft.com" } + { "id": "00000000-0000-0000-0000-000000000000", "displayName": "Karl Matteson", "mail": "karl.matteson@contoso.onmicrosoft.com", "memberOf": [{ "displayName": "Life and Music", "id": "d6c88284-c598-468d-8074-56acaf3c0453" }] } ] }; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members?$select=displayName,mail,id`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members/microsoft.graph.user?$select=displayName,mail,id&$expand=memberof($select=id),memberof($select=displayName)`) { return { "value": [ - { "id": "00000000-0000-0000-0000-000000000001", "displayName": "Anne Matthews", "mail": "anne.matthews@contoso.onmicrosoft.com" } + { "id": "00000000-0000-0000-0000-000000000001", "displayName": "Anne Matthews", "mail": "anne.matthews@contoso.onmicrosoft.com", "memberOf": [{ "displayName": "Life and Music", "id": "d6c88284-c598-468d-8074-56acaf3c0454" }] } ] }; } @@ -267,23 +267,23 @@ describe(commands.GROUP_USER_LIST, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { groupId: groupId, properties: "displayName,mail" } }); + await command.action(logger, { options: { groupId: groupId, properties: "displayName,mail,memberof/id,memberof/displayName" } }); assert(loggerLogSpy.calledOnceWithExactly([ - { "id": "00000000-0000-0000-0000-000000000000", "displayName": "Karl Matteson", "mail": "karl.matteson@contoso.onmicrosoft.com", "roles": ["Owner"] }, - { "id": "00000000-0000-0000-0000-000000000001", "displayName": "Anne Matthews", "mail": "anne.matthews@contoso.onmicrosoft.com", "roles": ["Member"] } + { "id": "00000000-0000-0000-0000-000000000000", "displayName": "Karl Matteson", "mail": "karl.matteson@contoso.onmicrosoft.com", "memberOf": [{ "displayName": "Life and Music", "id": "d6c88284-c598-468d-8074-56acaf3c0453" }], "roles": ["Owner"] }, + { "id": "00000000-0000-0000-0000-000000000001", "displayName": "Anne Matthews", "mail": "anne.matthews@contoso.onmicrosoft.com", "memberOf": [{ "displayName": "Life and Music", "id": "d6c88284-c598-468d-8074-56acaf3c0454" }], "roles": ["Member"] } ])); }); it('correctly lists all guest users in a Azure AD group', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners?$select=id,displayName,userPrincipalName,givenName,surname&$filter=userType%20eq%20'Guest'&$count=true`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname&$filter=userType%20eq%20'Guest'&$count=true`) { return { "value": [] }; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members?$select=id,displayName,userPrincipalName,givenName,surname&$filter=userType%20eq%20'Guest'&$count=true`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Members/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname&$filter=userType%20eq%20'Guest'&$count=true`) { return { "value": [ { "id": "00000000-0000-0000-0000-000000000000", "displayName": "Anne Matthews", "userPrincipalName": "annematthews_gmail.com#EXT#@contoso.onmicrosoft.com", "givenName": "Anne", "surname": "Matthews" } @@ -322,7 +322,7 @@ describe(commands.GROUP_USER_LIST, () => { }; sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners?$select=id,displayName,userPrincipalName,givenName,surname`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/2c1ba4c4-cd9b-4417-832f-92a34bc34b2a/Owners/microsoft.graph.user?$select=id,displayName,userPrincipalName,givenName,surname`) { throw error; } diff --git a/src/m365/aad/commands/group/group-user-list.ts b/src/m365/aad/commands/group/group-user-list.ts index ebe2297ba8e..161def7e7e2 100644 --- a/src/m365/aad/commands/group/group-user-list.ts +++ b/src/m365/aad/commands/group/group-user-list.ts @@ -3,7 +3,6 @@ import { Logger } from '../../../../cli/Logger.js'; import GlobalOptions from '../../../../GlobalOptions.js'; import { CliRequestOptions } from '../../../../request.js'; import { aadGroup } from '../../../../utils/aadGroup.js'; -import { formatting } from '../../../../utils/formatting.js'; import { odata } from '../../../../utils/odata.js'; import { validation } from '../../../../utils/validation.js'; import GraphCommand from '../../../base/GraphCommand.js'; @@ -110,33 +109,26 @@ class AadGroupUserListCommand extends GraphCommand { try { const groupId = await this.getGroupId(args.options); - let users: User[] = []; - - switch (args.options.role) { - case 'Owner': - users = await this.getUsers(args.options, 'Owner', groupId, logger); - break; - case 'Member': - users = await this.getUsers(args.options, 'Member', groupId, logger); - break; - default: - const owners = await this.getUsers(args.options, 'Owner', groupId, logger); - const members = await this.getUsers(args.options, 'Member', groupId, logger); - - if (!args.options.properties) { - owners.forEach((owner: ExtendedUser) => { - for (let i = 0; i < members.length; i++) { - if (members[i].id === owner.id) { - if (!owner.roles.includes('Member')) { - owner.roles.push('Member'); - } - } - } - }); - } + const users: ExtendedUser[] = []; + + if (!args.options.role || args.options.role === 'Owner') { + const owners = await this.getUsers(args.options, 'Owners', groupId, logger); + owners.forEach(owner => users.push({ ...owner, roles: ['Owner'] })); + } + + if (!args.options.role || args.options.role === 'Member') { + const members = await this.getUsers(args.options, 'Members', groupId, logger); - users = owners.concat(members); - users = users.filter((value, index, array) => index === array.findIndex(item => item.id === value.id)); + members.forEach((member: ExtendedUser) => { + const user = users.find((u: ExtendedUser) => u.id === member.id); + + if (user !== undefined) { + user.roles.push('Member'); + } + else { + users.push({ ...member, roles: ['Member'] }); + } + }); } await logger.log(users); @@ -160,13 +152,28 @@ class AadGroupUserListCommand extends GraphCommand { const { properties, filter } = options; if (this.verbose) { - await logger.logToStderr(`Retrieving ${role}s of the group with id ${groupId}`); + await logger.logToStderr(`Retrieving ${role} of the group with id ${groupId}`); } const selectProperties: string = properties ? - `?$select=${properties.split(',').filter(f => f.toLowerCase() !== 'id').concat('id').map(p => formatting.encodeQueryParameter(p.trim())).join(',')}` : - '?$select=id,displayName,userPrincipalName,givenName,surname'; - const endpoint: string = `${this.resource}/v1.0/groups/${groupId}/${role}s${selectProperties}`; + `${properties.split(',').filter(f => f.toLowerCase() !== 'id').concat('id').map(p => p.trim()).join(',')}` : + 'id,displayName,userPrincipalName,givenName,surname'; + const allSelectProperties: string[] = selectProperties.split(','); + const propertiesWithSlash: string[] = allSelectProperties.filter(item => item.includes('/')); + + let fieldExpand: string = ''; + propertiesWithSlash.forEach(p => { + if (fieldExpand.length > 0) { + fieldExpand += ','; + } + + fieldExpand += `${p.split('/')[0]}($select=${p.split('/')[1]})`; + }); + + const expandParam = fieldExpand.length > 0 ? `&$expand=${fieldExpand}` : ''; + + const selectParam = allSelectProperties.filter(item => !item.includes('/')); + const endpoint: string = `${this.resource}/v1.0/groups/${groupId}/${role}/microsoft.graph.user?$select=${selectParam}${expandParam}`; let users: ExtendedUser[] = []; @@ -187,10 +194,6 @@ class AadGroupUserListCommand extends GraphCommand { users = await odata.getAllItems(endpoint); } - users.forEach((user: ExtendedUser) => { - user.roles = [role]; - }); - return users; } }