Skip to content

Commit

Permalink
Adds 'entra group user remove' command. Closes #5472
Browse files Browse the repository at this point in the history
  • Loading branch information
milanholemans committed Apr 29, 2024
1 parent cec4ca2 commit 193de18
Show file tree
Hide file tree
Showing 8 changed files with 694 additions and 12 deletions.
2 changes: 1 addition & 1 deletion docs/docs/cmd/entra/group/group-user-add.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Global from '/docs/cmd/_global.mdx';

# entra group user add

Adds a user to a Microsoft Entra ID group
Adds users to a Microsoft Entra ID group

## Usage

Expand Down
84 changes: 84 additions & 0 deletions docs/docs/cmd/entra/group/group-user-remove.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Global from '/docs/cmd/_global.mdx';

# entra group user remove

Removes users from a Microsoft Entra ID group

## Usage

```sh
m365 entra group user remove [options]
```

## Options

```md definition-list
`-i, --groupId [groupId]`
: The ID of the Entra ID group. Specify `groupId` or `groupDisplayName` but not both.

`-n, --groupDisplayName [groupDisplayName]`
: The display name of the Entra ID group. Specify `groupId` or `groupDisplayName` but not both.

`--ids [ids]`
: Entra ID IDs of users. You can also pass a comma-separated list of IDs. Specify either `ids` or `userNames` but not both.

`--userNames [userNames]`
: The user principal names of users. You can also pass a comma-separated list of UPNs. Specify either `ids` or `userNames` but not both.

`-r, --role [role]`
: The role to be removed from the users. Valid values: `Owner`, `Member`. Defaults to both.

`--suppressNotFound`
: Suppress errors when a user was not found in a group.

`-f, --force`
: Don't prompt for confirmation.
```

<Global />

## Remarks

:::tip

When you use the `suppressNotFound` option, the command will not return an error if a user is not found as either an owner or a member of the group.
This feature proves useful when you need to remove a user from a group, but you are uncertain whether the user holds the role of a member or an owner within that group.
Without using this option, you would need to manually verify the user's role in the group before proceeding with removal.

:::

## Examples

Remove a single user specified by ID as member from a group specified by display name

```sh
m365 entra group user remove --groupDisplayName Developers --ids 098b9f52-f48c-4401-819f-29c33794c3f5 --role Member
```

Remove multiple users specified by ID from a group specified by ID

```sh
m365 entra group user remove --groupId a03c0c35-ef9a-419b-8cab-f89e0a8d2d2a --ids "098b9f52-f48c-4401-819f-29c33794c3f5,f1e06e31-3abf-4746-83c2-1513d71f38b8"
```

Remove a single user specified by UPN as an owner from a group specified by display name

```sh
m365 entra group user remove --groupDisplayName Developers --userNames [email protected] --role Owner
```

Remove multiple users specified by UPN from a group specified by ID

```sh
m365 entra group user remove --groupId a03c0c35-ef9a-419b-8cab-f89e0a8d2d2a --userNames "[email protected],[email protected]"
```

Remove a single user specified by ID as owner and member of the group and suppress errors when the user was not found as owner or member

```sh
m365 entra group user remove --groupDisplayName Developers --ids 098b9f52-f48c-4401-819f-29c33794c3f5 --suppressNotFound
```

## Response

The command won't return a response on success.
5 changes: 5 additions & 0 deletions docs/src/config/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,11 @@ const sidebars: SidebarsConfig = {
type: 'doc',
label: 'group user list',
id: 'cmd/entra/group/group-user-list'
},
{
type: 'doc',
label: 'group user remove',
id: 'cmd/entra/group/group-user-remove'
}
]
},
Expand Down
1 change: 1 addition & 0 deletions src/m365/entra/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default {
GROUP_REMOVE: `${prefix} group remove`,
GROUP_USER_ADD: `${prefix} group user add`,
GROUP_USER_LIST: `${prefix} group user list`,
GROUP_USER_REMOVE: `${prefix} group user remove`,
GROUPSETTING_ADD: `${prefix} groupsetting add`,
GROUPSETTING_GET: `${prefix} groupsetting get`,
GROUPSETTING_LIST: `${prefix} groupsetting list`,
Expand Down
17 changes: 9 additions & 8 deletions src/m365/entra/commands/group/group-user-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import GlobalOptions from '../../../../GlobalOptions.js';
import request, { CliRequestOptions } from '../../../../request.js';
import { entraGroup } from '../../../../utils/entraGroup.js';
import { entraUser } from '../../../../utils/entraUser.js';
import { GraphBatchRequest, GraphBatchRequestItem } from '../../../../utils/types.js';
import { validation } from '../../../../utils/validation.js';
import GraphCommand from '../../../base/GraphCommand.js';
import commands from '../../commands.js';
Expand All @@ -27,7 +28,7 @@ class EntraGroupUserAddCommand extends GraphCommand {
}

public get description(): string {
return 'Adds a user to a Microsoft Entra ID group';
return 'Adds users to a Microsoft Entra ID group';
}

constructor() {
Expand Down Expand Up @@ -75,19 +76,19 @@ class EntraGroupUserAddCommand extends GraphCommand {
#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => {
if (args.options.groupId && !validation.isValidGuid(args.options.groupId)) {
return `${args.options.groupId} is not a valid GUID for option groupId.`;
if (args.options.groupId !== undefined && !validation.isValidGuid(args.options.groupId)) {
return `'${args.options.groupId}' is not a valid GUID for option 'groupId'.`;
}

if (args.options.ids) {
if (args.options.ids !== undefined) {
const ids = args.options.ids.split(',').map(i => i.trim());
if (!validation.isValidGuidArray(ids)) {
const invalidGuid = ids.find(id => !validation.isValidGuid(id));
return `'${invalidGuid}' is not a valid GUID for option 'ids'.`;
}
}

if (args.options.userNames) {
if (args.options.userNames !== undefined) {
const isValidUserPrincipalNameArray = validation.isValidUserPrincipalNameArray(args.options.userNames.split(',').map(u => u.trim()));
if (isValidUserPrincipalNameArray !== true) {
return `User principal name '${isValidUserPrincipalNameArray}' is invalid for option 'userNames'.`;
Expand Down Expand Up @@ -133,7 +134,7 @@ class EntraGroupUserAddCommand extends GraphCommand {
responseType: 'json',
data: {
requests: []
}
} as GraphBatchRequest
};

for (let j = 0; j < userIdsBatch.length; j += 20) {
Expand All @@ -148,7 +149,7 @@ class EntraGroupUserAddCommand extends GraphCommand {
body: {
[`${args.options.role === 'Member' ? 'members' : 'owners'}@odata.bind`]: userIdsChunk.map(u => `${this.resource}/v1.0/directoryObjects/${u}`)
}
});
} as GraphBatchRequestItem);
}

const res = await request.post<{ responses: { status: number; body: any }[] }>(requestOptions);
Expand All @@ -170,7 +171,7 @@ class EntraGroupUserAddCommand extends GraphCommand {
}

if (this.verbose) {
await logger.logToStderr(`Retrieving ID of group ${options.groupDisplayName}...`);
await logger.logToStderr(`Retrieving ID of group '${options.groupDisplayName}'...`);
}

return entraGroup.getGroupIdByDisplayName(options.groupDisplayName!);
Expand Down
Loading

0 comments on commit 193de18

Please sign in to comment.