Skip to content

Commit

Permalink
Enhances 'entra m365group set' with displayName. Closes #6146
Browse files Browse the repository at this point in the history
  • Loading branch information
MathijsVerbeeck committed Sep 24, 2024
1 parent c512d43 commit 7404ed1
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 37 deletions.
15 changes: 9 additions & 6 deletions docs/docs/cmd/entra/m365group/m365group-set.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ m365 aad m365group set [options]
## Options

```md definition-list
`-i, --id <id>`
`-i, --id [id]`
: The ID of the Microsoft 365 Group to update

`-n, --displayName [displayName]`
: Display name for the Microsoft 365 Group
: Display name of the Microsoft 365 Group to update

`--newDisplayName [newDisplayName]`
: New display name for the Microsoft 365 Group

`-d, --description [description]`
: Description for the Microsoft 365 Group
Expand Down Expand Up @@ -72,7 +75,7 @@ Options `allowExternalSenders` and `autoSubscribeNewMembers` can only be set usi
Update Microsoft 365 Group display name.

```sh
m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --displayName Finance
m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --newDisplayName Finance
```

Change Microsoft 365 Group visibility to public.
Expand All @@ -81,16 +84,16 @@ Change Microsoft 365 Group visibility to public.
m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --isPrivate `false`
```

Add new Microsoft 365 Group owners.
Add new Microsoft 365 Group owners of group.

```sh
m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --owners "[email protected],[email protected]"
m365 entra m365group set --displayName 'Project Team' --owners "[email protected],[email protected]"
```

Add new Microsoft 365 Group members.

```sh
m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --members "[email protected],[email protected]"
m365 entra m365group set --displayName 'Project Team' --members "[email protected],[email protected]"
```

Update Microsoft 365 Group logo.
Expand Down
8 changes: 8 additions & 0 deletions docs/docs/v10-upgrade-guidance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ The deprecated option `userName` was removed from the command [entra m365group u

In your scripts, replace every occurrence of the deprecated option `userName` with `userNames`.

### Enhanced `entra m365group set` command

We've enhanced the [entra m365group set](./cmd/entra/m365group/m365group-set.mdx) command so that a group that we wish to update can also be retrieved by it's displayName. Before, this was only able by the group ID. We have replaced the original `displayName` option by `newDisplayName`.

#### What action do I need to take?

Make sure that if you are currently updating groups using the `displayName` option, you update your scripts to use the `newDisplayName` option instead.

## SharePoint

### Updated `spo site appcatalog remove` options
Expand Down
29 changes: 14 additions & 15 deletions src/m365/entra/commands/m365group/m365group-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ describe(commands.M365GROUP_SET, () => {
fs.readFileSync,
fs.existsSync,
fs.lstatSync,
accessToken.isAppOnlyAccessToken
accessToken.isAppOnlyAccessToken,
entraGroup.getGroupIdByDisplayName
]);
});

Expand All @@ -125,21 +126,19 @@ describe(commands.M365GROUP_SET, () => {
assert.deepStrictEqual(alias, [aadCommands.M365GROUP_SET]);
});

it('updates Microsoft 365 Group display name', async () => {
sinon.stub(request, 'patch').callsFake(async (opts) => {
if (opts.url === 'https://graph.microsoft.com/v1.0/groups/28beab62-7540-4db1-a23f-29a6018a3848') {
if (JSON.stringify(opts.data) === JSON.stringify(<Group>{
displayName: 'My group'
})) {
return;
}
it('updates Microsoft 365 Group display name while group is being retrieved by display name', async () => {
const groupName = 'Project A';
sinon.stub(entraGroup, 'getGroupIdByDisplayName').withArgs(groupName).resolves(groupId);
const patchStub = sinon.stub(request, 'patch').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/groups/${groupId}`) {
return;
}

throw 'Invalid request';
});

await command.action(logger, { options: { id: '28beab62-7540-4db1-a23f-29a6018a3848', displayName: 'My group' } });
assert(loggerLogSpy.notCalled);
await command.action(logger, { options: { displayName: groupName, newDisplayName: 'My group', verbose: true } });
assert(patchStub.calledOnce);
});

it('updates Microsoft 365 Group description (debug)', async () => {
Expand Down Expand Up @@ -497,15 +496,15 @@ describe(commands.M365GROUP_SET, () => {
}
});

await assert.rejects(command.action(logger, { options: { id: '28beab62-7540-4db1-a23f-29a6018a3848', displayName: 'My group' } } as any),
await assert.rejects(command.action(logger, { options: { id: '28beab62-7540-4db1-a23f-29a6018a3848', newDisplayName: 'My group' } } as any),
new CommandError('An error has occurred'));
});

it('throws error when the group is not a unified group', async () => {
sinonUtil.restore(entraGroup.isUnifiedGroup);
sinon.stub(entraGroup, 'isUnifiedGroup').resolves(false);

await assert.rejects(command.action(logger, { options: { id: groupId, displayName: 'Updated title' } }),
await assert.rejects(command.action(logger, { options: { id: groupId, newDisplayName: 'Updated title' } }),
new CommandError(`Specified group with id '${groupId}' is not a Microsoft 365 group.`));
});

Expand All @@ -532,7 +531,7 @@ describe(commands.M365GROUP_SET, () => {
});

it('passes validation when the id is a valid GUID and displayName specified', async () => {
const actual = await command.validate({ options: { id: '28beab62-7540-4db1-a23f-29a6018a3848', displayName: 'My group' } }, commandInfo);
const actual = await command.validate({ options: { id: '28beab62-7540-4db1-a23f-29a6018a3848', newDisplayName: 'My group' } }, commandInfo);
assert.strictEqual(actual, true);
});

Expand Down Expand Up @@ -622,7 +621,7 @@ describe(commands.M365GROUP_SET, () => {
sinon.stub(stats, 'isDirectory').returns(false);
sinon.stub(fs, 'existsSync').returns(true);
sinon.stub(fs, 'lstatSync').returns(stats);
const actual = await command.validate({ options: { id: '28beab62-7540-4db1-a23f-29a6018a3848', displayName: 'Title', description: 'Description', logoPath: 'logo.png', owners: '[email protected]', members: '[email protected]', isPrivate: false, allowExternalSenders: false, autoSubscribeNewMembers: false, hideFromAddressLists: false, hideFromOutlookClients: false } }, commandInfo);
const actual = await command.validate({ options: { id: '28beab62-7540-4db1-a23f-29a6018a3848', newDisplayName: 'Title', description: 'Description', logoPath: 'logo.png', owners: '[email protected]', members: '[email protected]', isPrivate: false, allowExternalSenders: false, autoSubscribeNewMembers: false, hideFromAddressLists: false, hideFromOutlookClients: false } }, commandInfo);
assert.strictEqual(actual, true);
});
});
44 changes: 28 additions & 16 deletions src/m365/entra/commands/m365group/m365group-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ interface CommandArgs {
}

export interface Options extends GlobalOptions {
id: string;
id?: string;
displayName?: string;
newDisplayName?: string;
description?: string;
owners?: string;
members?: string;
Expand Down Expand Up @@ -52,14 +53,17 @@ class EntraM365GroupSetCommand extends GraphCommand {

this.#initTelemetry();
this.#initOptions();
this.#initTypes();
this.#initValidators();
this.#initOptionSets();
this.#initTypes();
}

#initTelemetry(): void {
this.telemetry.push((args: CommandArgs) => {
Object.assign(this.telemetryProperties, {
id: typeof args.options.id !== 'undefined',
displayName: typeof args.options.displayName !== 'undefined',
newDisplayName: typeof args.options.newDisplayName !== 'undefined',
description: typeof args.options.description !== 'undefined',
owners: typeof args.options.owners !== 'undefined',
members: typeof args.options.members !== 'undefined',
Expand All @@ -76,11 +80,14 @@ class EntraM365GroupSetCommand extends GraphCommand {
#initOptions(): void {
this.options.unshift(
{
option: '-i, --id <id>'
option: '-i, --id [id]'
},
{
option: '-n, --displayName [displayName]'
},
{
option: '--newDisplayName [newDisplayName]'
},
{
option: '-d, --description [description]'
},
Expand Down Expand Up @@ -116,15 +123,19 @@ class EntraM365GroupSetCommand extends GraphCommand {
);
}

#initOptionSets(): void {
this.optionSets.push({ options: ['id', 'displayName'] });
}

#initTypes(): void {
this.types.boolean.push('isPrivate', 'allowEternalSenders', 'autoSubscribeNewMembers', 'hideFromAddressLists', 'hideFromOutlookClients');
this.types.string.push('id', 'displayName', 'description', 'owners', 'members', 'logoPath');
this.types.string.push('id', 'displayName', 'newDisplayName', 'description', 'owners', 'members', 'logoPath');
}

#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => {
if (!args.options.displayName &&
if (!args.options.newDisplayName &&
args.options.description === undefined &&
!args.options.members &&
!args.options.owners &&
Expand All @@ -137,7 +148,7 @@ class EntraM365GroupSetCommand extends GraphCommand {
return 'Specify at least one option to update.';
}

if (!validation.isValidGuid(args.options.id)) {
if (args.options.id && !validation.isValidGuid(args.options.id)) {
return `${args.options.id} is not a valid GUID`;
}

Expand Down Expand Up @@ -180,19 +191,20 @@ class EntraM365GroupSetCommand extends GraphCommand {
throw `Option 'allowExternalSenders' and 'autoSubscribeNewMembers' can only be used when using delegated permissions.`;
}

const isUnifiedGroup = await entraGroup.isUnifiedGroup(args.options.id);
const groupId = args.options.id || await entraGroup.getGroupIdByDisplayName(args.options.displayName!);
const isUnifiedGroup = await entraGroup.isUnifiedGroup(groupId);

if (!isUnifiedGroup) {
throw Error(`Specified group with id '${args.options.id}' is not a Microsoft 365 group.`);
throw Error(`Specified group with id '${groupId}' is not a Microsoft 365 group.`);
}

if (this.verbose) {
await logger.logToStderr(`Updating Microsoft 365 Group ${args.options.id}...`);
await logger.logToStderr(`Updating Microsoft 365 Group ${args.options.id || args.options.displayName}...`);
}

if (args.options.displayName || args.options.description !== undefined || args.options.isPrivate !== undefined) {
if (args.options.newDisplayName || args.options.description !== undefined || args.options.isPrivate !== undefined) {
const update: Group = {
displayName: args.options.displayName,
displayName: args.options.newDisplayName,
description: args.options.description !== '' ? args.options.description : null
};

Expand All @@ -201,7 +213,7 @@ class EntraM365GroupSetCommand extends GraphCommand {
}

const requestOptions: CliRequestOptions = {
url: `${this.resource}/v1.0/groups/${args.options.id}`,
url: `${this.resource}/v1.0/groups/${groupId}`,
headers: {
'accept': 'application/json;odata.metadata=none'
},
Expand All @@ -222,7 +234,7 @@ class EntraM365GroupSetCommand extends GraphCommand {
};

const requestOptions: CliRequestOptions = {
url: `${this.resource}/v1.0/groups/${args.options.id}`,
url: `${this.resource}/v1.0/groups/${groupId}`,
headers: {
accept: 'application/json;odata.metadata=none'
},
Expand All @@ -239,7 +251,7 @@ class EntraM365GroupSetCommand extends GraphCommand {
}

const requestOptions: CliRequestOptions = {
url: `${this.resource}/v1.0/groups/${args.options.id}/photo/$value`,
url: `${this.resource}/v1.0/groups/${groupId}/photo/$value`,
headers: {
'content-type': this.getImageContentType(fullPath)
},
Expand Down Expand Up @@ -270,7 +282,7 @@ class EntraM365GroupSetCommand extends GraphCommand {
const res = await request.get<{ value: { id: string; }[] }>(requestOptions);

await Promise.all(res.value.map(u => request.post({
url: `${this.resource}/v1.0/groups/${args.options.id}/owners/$ref`,
url: `${this.resource}/v1.0/groups/${groupId}/owners/$ref`,
headers: {
'content-type': 'application/json'
},
Expand Down Expand Up @@ -302,7 +314,7 @@ class EntraM365GroupSetCommand extends GraphCommand {
const res = await request.get<{ value: { id: string; }[] }>(requestOptions);

await Promise.all(res.value.map(u => request.post({
url: `${this.resource}/v1.0/groups/${args.options.id}/members/$ref`,
url: `${this.resource}/v1.0/groups/${groupId}/members/$ref`,
headers: {
'content-type': 'application/json'
},
Expand Down

0 comments on commit 7404ed1

Please sign in to comment.