@pnp/cli-microsoft365 10.0.0-beta.66db729 → 10.0.0-beta.7be7794

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,8 @@ const options = globalOptionsZod
21
21
  appId: z.string().optional(),
22
22
  tenant: z.string().optional(),
23
23
  secret: zod.alias('s', z.string().optional()),
24
- connectionName: z.string().optional()
24
+ connectionName: z.string().optional(),
25
+ ensure: z.boolean().optional()
25
26
  })
26
27
  .strict();
27
28
  class LoginCommand extends Command {
@@ -67,75 +68,16 @@ class LoginCommand extends Command {
67
68
  });
68
69
  }
69
70
  async commandAction(logger, args) {
70
- // disconnect before re-connecting
71
71
  if (this.debug) {
72
72
  await logger.logToStderr(`Logging out from Microsoft 365...`);
73
73
  }
74
- const deactivate = () => auth.connection.deactivate();
75
- const getCertificate = (options) => {
76
- // command args take precedence over settings
77
- if (options.certificateFile) {
78
- return fs.readFileSync(options.certificateFile).toString('base64');
79
- }
80
- if (options.certificateBase64Encoded) {
81
- return options.certificateBase64Encoded;
82
- }
83
- return cli.getConfig().get(settingsNames.clientCertificateFile) ||
84
- cli.getConfig().get(settingsNames.clientCertificateBase64Encoded);
85
- };
86
- const login = async () => {
87
- if (this.verbose) {
88
- await logger.logToStderr(`Signing in to Microsoft 365...`);
89
- }
90
- const authType = args.options.authType || cli.getSettingWithDefaultValue(settingsNames.authType, 'deviceCode');
91
- auth.connection.appId = args.options.appId || cli.getClientId();
92
- auth.connection.tenant = args.options.tenant || cli.getTenant();
93
- auth.connection.name = args.options.connectionName;
94
- switch (authType) {
95
- case 'password':
96
- auth.connection.authType = AuthType.Password;
97
- auth.connection.userName = args.options.userName;
98
- auth.connection.password = args.options.password;
99
- break;
100
- case 'certificate':
101
- auth.connection.authType = AuthType.Certificate;
102
- auth.connection.certificate = getCertificate(args.options);
103
- auth.connection.thumbprint = args.options.thumbprint;
104
- auth.connection.password = args.options.password ?? cli.getConfig().get(settingsNames.clientCertificatePassword);
105
- break;
106
- case 'identity':
107
- auth.connection.authType = AuthType.Identity;
108
- auth.connection.userName = args.options.userName;
109
- break;
110
- case 'browser':
111
- auth.connection.authType = AuthType.Browser;
112
- break;
113
- case 'secret':
114
- auth.connection.authType = AuthType.Secret;
115
- auth.connection.secret = args.options.secret || cli.getConfig().get(settingsNames.clientSecret);
116
- break;
117
- }
118
- auth.connection.cloudType = args.options.cloud;
119
- try {
120
- await auth.ensureAccessToken(auth.defaultResource, logger, this.debug);
121
- auth.connection.active = true;
122
- }
123
- catch (error) {
124
- if (this.debug) {
125
- await logger.logToStderr('Error:');
126
- await logger.logToStderr(error);
127
- await logger.logToStderr('');
128
- }
129
- throw new CommandError(error.message);
130
- }
131
- const details = auth.getConnectionDetails(auth.connection);
132
- if (this.debug) {
133
- details.accessToken = JSON.stringify(auth.connection.accessTokens, null, 2);
134
- }
135
- await logger.log(details);
136
- };
137
- deactivate();
138
- await login();
74
+ if (this.shouldLogin(args.options)) {
75
+ auth.connection.deactivate();
76
+ await this.login(logger, args);
77
+ }
78
+ else {
79
+ await this.ensureAccessToken(logger);
80
+ }
139
81
  }
140
82
  async action(logger, args) {
141
83
  try {
@@ -147,6 +89,117 @@ class LoginCommand extends Command {
147
89
  await this.initAction(args, logger);
148
90
  await this.commandAction(logger, args);
149
91
  }
92
+ shouldLogin(options) {
93
+ if (!auth.connection.active) {
94
+ return true;
95
+ }
96
+ if (!options.ensure) {
97
+ return true;
98
+ }
99
+ const authType = options.authType || cli.getSettingWithDefaultValue(settingsNames.authType, 'deviceCode');
100
+ if (authType !== auth.connection.authType) {
101
+ return true;
102
+ }
103
+ if (options.cloud !== auth.connection.cloudType) {
104
+ return true;
105
+ }
106
+ if (options.appId && options.appId !== auth.connection.appId) {
107
+ return true;
108
+ }
109
+ if (options.tenant && options.tenant !== auth.connection.tenant) {
110
+ return true;
111
+ }
112
+ if (authType === AuthType.Password && (options.password && options.userName !== auth.connection.userName)) {
113
+ return true;
114
+ }
115
+ if (authType === AuthType.Certificate && (options.certificateFile && (auth.connection.certificate !== fs.readFileSync(options.certificateFile, 'base64')))) {
116
+ return true;
117
+ }
118
+ if (authType === AuthType.Identity && (options.userName && options.userName !== auth.connection.userName)) {
119
+ return true;
120
+ }
121
+ if (authType === AuthType.Secret && (options.secret && options.secret !== auth.connection.secret)) {
122
+ return true;
123
+ }
124
+ const now = new Date();
125
+ const accessToken = auth.connection.accessTokens[auth.defaultResource];
126
+ const expiresOn = accessToken && accessToken.expiresOn ?
127
+ // if expiresOn is serialized from the service file, it's set as a string
128
+ // if it's coming from MSAL, it's a Date
129
+ typeof accessToken.expiresOn === 'string' ? new Date(accessToken.expiresOn) : accessToken.expiresOn
130
+ : new Date(0);
131
+ if (expiresOn < now) {
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ async ensureAccessToken(logger) {
137
+ try {
138
+ await auth.ensureAccessToken(auth.defaultResource, logger, this.debug);
139
+ auth.connection.active = true;
140
+ }
141
+ catch (error) {
142
+ if (this.debug) {
143
+ await logger.logToStderr('Error:');
144
+ await logger.logToStderr(error);
145
+ await logger.logToStderr('');
146
+ }
147
+ throw new CommandError(error.message);
148
+ }
149
+ }
150
+ getCertificate(options) {
151
+ // command args take precedence over settings
152
+ if (options.certificateFile) {
153
+ return fs.readFileSync(options.certificateFile).toString('base64');
154
+ }
155
+ if (options.certificateBase64Encoded) {
156
+ return options.certificateBase64Encoded;
157
+ }
158
+ return cli.getConfig().get(settingsNames.clientCertificateFile) ||
159
+ cli.getConfig().get(settingsNames.clientCertificateBase64Encoded);
160
+ }
161
+ ;
162
+ async login(logger, args) {
163
+ if (this.verbose) {
164
+ await logger.logToStderr(`Signing in to Microsoft 365...`);
165
+ }
166
+ const authType = args.options.authType || cli.getSettingWithDefaultValue(settingsNames.authType, 'deviceCode');
167
+ auth.connection.appId = args.options.appId || cli.getClientId();
168
+ auth.connection.tenant = args.options.tenant || cli.getTenant();
169
+ auth.connection.name = args.options.connectionName;
170
+ switch (authType) {
171
+ case 'password':
172
+ auth.connection.authType = AuthType.Password;
173
+ auth.connection.userName = args.options.userName;
174
+ auth.connection.password = args.options.password;
175
+ break;
176
+ case 'certificate':
177
+ auth.connection.authType = AuthType.Certificate;
178
+ auth.connection.certificate = this.getCertificate(args.options);
179
+ auth.connection.thumbprint = args.options.thumbprint;
180
+ auth.connection.password = args.options.password ?? cli.getConfig().get(settingsNames.clientCertificatePassword);
181
+ break;
182
+ case 'identity':
183
+ auth.connection.authType = AuthType.Identity;
184
+ auth.connection.userName = args.options.userName;
185
+ break;
186
+ case 'browser':
187
+ auth.connection.authType = AuthType.Browser;
188
+ break;
189
+ case 'secret':
190
+ auth.connection.authType = AuthType.Secret;
191
+ auth.connection.secret = args.options.secret || cli.getConfig().get(settingsNames.clientSecret);
192
+ break;
193
+ }
194
+ auth.connection.cloudType = args.options.cloud;
195
+ await this.ensureAccessToken(logger);
196
+ const details = auth.getConnectionDetails(auth.connection);
197
+ if (this.debug) {
198
+ details.accessToken = JSON.stringify(auth.connection.accessTokens, null, 2);
199
+ }
200
+ await logger.log(details);
201
+ }
202
+ ;
150
203
  }
151
204
  export default new LoginCommand();
152
205
  //# sourceMappingURL=login.js.map
@@ -15,6 +15,9 @@ import { entraGroup } from '../../../../utils/entraGroup.js';
15
15
  import aadCommands from '../../aadCommands.js';
16
16
  import { accessToken } from '../../../../utils/accessToken.js';
17
17
  import auth from '../../../../Auth.js';
18
+ import { entraUser } from '../../../../utils/entraUser.js';
19
+ import { formatting } from '../../../../utils/formatting.js';
20
+ import { odata } from '../../../../utils/odata.js';
18
21
  class EntraM365GroupSetCommand extends GraphCommand {
19
22
  get name() {
20
23
  return commands.M365GROUP_SET;
@@ -102,59 +105,13 @@ class EntraM365GroupSetCommand extends GraphCommand {
102
105
  else if (this.debug) {
103
106
  await logger.logToStderr('logoPath not set. Skipping');
104
107
  }
105
- if (args.options.owners) {
106
- const owners = args.options.owners.split(',').map(o => o.trim());
107
- if (this.verbose) {
108
- await logger.logToStderr('Retrieving user information to set group owners...');
109
- }
110
- const requestOptions = {
111
- url: `${this.resource}/v1.0/users?$filter=${owners.map(o => `userPrincipalName eq '${o}'`).join(' or ')}&$select=id`,
112
- headers: {
113
- 'content-type': 'application/json'
114
- },
115
- responseType: 'json'
116
- };
117
- const res = await request.get(requestOptions);
118
- await Promise.all(res.value.map(u => request.post({
119
- url: `${this.resource}/v1.0/groups/${groupId}/owners/$ref`,
120
- headers: {
121
- 'content-type': 'application/json'
122
- },
123
- responseType: 'json',
124
- data: {
125
- "@odata.id": `https://graph.microsoft.com/v1.0/users/${u.id}`
126
- }
127
- })));
128
- }
129
- else if (this.debug) {
130
- await logger.logToStderr('Owners not set. Skipping');
131
- }
132
- if (args.options.members) {
133
- const members = args.options.members.split(',').map(o => o.trim());
134
- if (this.verbose) {
135
- await logger.logToStderr('Retrieving user information to set group members...');
136
- }
137
- const requestOptions = {
138
- url: `${this.resource}/v1.0/users?$filter=${members.map(o => `userPrincipalName eq '${o}'`).join(' or ')}&$select=id`,
139
- headers: {
140
- 'content-type': 'application/json'
141
- },
142
- responseType: 'json'
143
- };
144
- const res = await request.get(requestOptions);
145
- await Promise.all(res.value.map(u => request.post({
146
- url: `${this.resource}/v1.0/groups/${groupId}/members/$ref`,
147
- headers: {
148
- 'content-type': 'application/json'
149
- },
150
- responseType: 'json',
151
- data: {
152
- "@odata.id": `https://graph.microsoft.com/v1.0/users/${u.id}`
153
- }
154
- })));
108
+ const ownerIds = await this.getUserIds(logger, args.options.ownerIds, args.options.ownerUserNames);
109
+ const memberIds = await this.getUserIds(logger, args.options.memberIds, args.options.memberUserNames);
110
+ if (ownerIds.length > 0) {
111
+ await this.updateUsers(logger, groupId, 'owners', ownerIds);
155
112
  }
156
- else if (this.debug) {
157
- await logger.logToStderr('Members not set. Skipping');
113
+ if (memberIds.length > 0) {
114
+ await this.updateUsers(logger, groupId, 'members', memberIds);
158
115
  }
159
116
  }
160
117
  catch (err) {
@@ -186,6 +143,87 @@ class EntraM365GroupSetCommand extends GraphCommand {
186
143
  return 'image/jpeg';
187
144
  }
188
145
  }
146
+ async getUserIds(logger, userIds, userNames) {
147
+ if (userIds) {
148
+ return formatting.splitAndTrim(userIds);
149
+ }
150
+ if (userNames) {
151
+ if (this.verbose) {
152
+ await logger.logToStderr(`Retrieving user IDs...`);
153
+ }
154
+ return entraUser.getUserIdsByUpns(formatting.splitAndTrim(userNames));
155
+ }
156
+ return [];
157
+ }
158
+ async updateUsers(logger, groupId, role, userIds) {
159
+ const groupUsers = await odata.getAllItems(`${this.resource}/v1.0/groups/${groupId}/${role}/microsoft.graph.user?$select=id`);
160
+ const userIdsToAdd = userIds.filter(userId => !groupUsers.some(groupUser => groupUser.id === userId));
161
+ const userIdsToRemove = groupUsers.filter(groupUser => !userIds.some(userId => groupUser.id === userId)).map(user => user.id);
162
+ if (this.verbose) {
163
+ await logger.logToStderr(`Adding ${userIdsToAdd.length} ${role}...`);
164
+ }
165
+ for (let i = 0; i < userIdsToAdd.length; i += 400) {
166
+ const userIdsBatch = userIdsToAdd.slice(i, i + 400);
167
+ const batchRequestOptions = this.getBatchRequestOptions();
168
+ // only 20 requests per one batch are allowed
169
+ for (let j = 0; j < userIdsBatch.length; j += 20) {
170
+ // only 20 users can be added in one request
171
+ const userIdsChunk = userIdsBatch.slice(j, j + 20);
172
+ batchRequestOptions.data.requests.push({
173
+ id: j + 1,
174
+ method: 'PATCH',
175
+ url: `/groups/${groupId}`,
176
+ headers: {
177
+ 'content-type': 'application/json;odata.metadata=none',
178
+ accept: 'application/json;odata.metadata=none'
179
+ },
180
+ body: {
181
+ [`${role}@odata.bind`]: userIdsChunk.map(u => `${this.resource}/v1.0/directoryObjects/${u}`)
182
+ }
183
+ });
184
+ }
185
+ const res = await request.post(batchRequestOptions);
186
+ for (const response of res.responses) {
187
+ if (response.status !== 204) {
188
+ throw response.body;
189
+ }
190
+ }
191
+ }
192
+ if (this.verbose) {
193
+ await logger.logToStderr(`Removing ${userIdsToRemove.length} ${role}...`);
194
+ }
195
+ for (let i = 0; i < userIdsToRemove.length; i += 20) {
196
+ const userIdsBatch = userIdsToRemove.slice(i, i + 20);
197
+ const batchRequestOptions = this.getBatchRequestOptions();
198
+ userIdsBatch.map(userId => {
199
+ batchRequestOptions.data.requests.push({
200
+ id: userId,
201
+ method: 'DELETE',
202
+ url: `/groups/${groupId}/${role}/${userId}/$ref`
203
+ });
204
+ });
205
+ const res = await request.post(batchRequestOptions);
206
+ for (const response of res.responses) {
207
+ if (response.status !== 204) {
208
+ throw response.body;
209
+ }
210
+ }
211
+ }
212
+ }
213
+ getBatchRequestOptions() {
214
+ const requestOptions = {
215
+ url: `${this.resource}/v1.0/$batch`,
216
+ headers: {
217
+ 'content-type': 'application/json;odata.metadata=none',
218
+ accept: 'application/json;odata.metadata=none'
219
+ },
220
+ responseType: 'json',
221
+ data: {
222
+ requests: []
223
+ }
224
+ };
225
+ return requestOptions;
226
+ }
189
227
  }
190
228
  _EntraM365GroupSetCommand_instances = new WeakSet(), _EntraM365GroupSetCommand_initTelemetry = function _EntraM365GroupSetCommand_initTelemetry() {
191
229
  this.telemetry.push((args) => {
@@ -194,8 +232,10 @@ _EntraM365GroupSetCommand_instances = new WeakSet(), _EntraM365GroupSetCommand_i
194
232
  displayName: typeof args.options.displayName !== 'undefined',
195
233
  newDisplayName: typeof args.options.newDisplayName !== 'undefined',
196
234
  description: typeof args.options.description !== 'undefined',
197
- owners: typeof args.options.owners !== 'undefined',
198
- members: typeof args.options.members !== 'undefined',
235
+ ownerIds: typeof args.options.ownerIds !== 'undefined',
236
+ ownerUserNames: typeof args.options.ownerUserNames !== 'undefined',
237
+ memberIds: typeof args.options.memberIds !== 'undefined',
238
+ memberUserNames: typeof args.options.memberUserNames !== 'undefined',
199
239
  isPrivate: !!args.options.isPrivate,
200
240
  logoPath: typeof args.options.logoPath !== 'undefined',
201
241
  allowExternalSenders: !!args.options.allowExternalSenders,
@@ -214,9 +254,13 @@ _EntraM365GroupSetCommand_instances = new WeakSet(), _EntraM365GroupSetCommand_i
214
254
  }, {
215
255
  option: '-d, --description [description]'
216
256
  }, {
217
- option: '--owners [owners]'
257
+ option: '--ownerIds [ownerIds]'
258
+ }, {
259
+ option: '--ownerUserNames [ownerUserNames]'
218
260
  }, {
219
- option: '--members [members]'
261
+ option: '--memberIds [memberIds]'
262
+ }, {
263
+ option: '--memberUserNames [memberUserNames]'
220
264
  }, {
221
265
  option: '--isPrivate [isPrivate]',
222
266
  autocomplete: ['true', 'false']
@@ -237,17 +281,31 @@ _EntraM365GroupSetCommand_instances = new WeakSet(), _EntraM365GroupSetCommand_i
237
281
  });
238
282
  }, _EntraM365GroupSetCommand_initOptionSets = function _EntraM365GroupSetCommand_initOptionSets() {
239
283
  this.optionSets.push({ options: ['id', 'displayName'] });
284
+ this.optionSets.push({
285
+ options: ['ownerIds', 'ownerUserNames'],
286
+ runsWhen: (args) => {
287
+ return args.options.ownerIds !== undefined || args.options.ownerUserNames !== undefined;
288
+ }
289
+ });
290
+ this.optionSets.push({
291
+ options: ['memberIds', 'memberUserNames'],
292
+ runsWhen: (args) => {
293
+ return args.options.memberIds !== undefined || args.options.memberUserNames !== undefined;
294
+ }
295
+ });
240
296
  }, _EntraM365GroupSetCommand_initTypes = function _EntraM365GroupSetCommand_initTypes() {
241
297
  this.types.boolean.push('isPrivate', 'allowEternalSenders', 'autoSubscribeNewMembers', 'hideFromAddressLists', 'hideFromOutlookClients');
242
- this.types.string.push('id', 'displayName', 'newDisplayName', 'description', 'owners', 'members', 'logoPath');
298
+ this.types.string.push('id', 'displayName', 'newDisplayName', 'description', 'ownerIds', 'ownerUserNames', 'memberIds', 'memberUserNames', 'logoPath');
243
299
  }, _EntraM365GroupSetCommand_initValidators = function _EntraM365GroupSetCommand_initValidators() {
244
300
  this.validators.push(async (args) => {
245
301
  if (!args.options.newDisplayName &&
246
302
  args.options.description === undefined &&
247
- !args.options.members &&
248
- !args.options.owners &&
303
+ args.options.ownerIds === undefined &&
304
+ args.options.ownerUserNames === undefined &&
305
+ args.options.memberIds === undefined &&
306
+ args.options.memberUserNames === undefined &&
249
307
  args.options.isPrivate === undefined &&
250
- !args.options.logoPath &&
308
+ args.options.logoPath === undefined &&
251
309
  args.options.allowExternalSenders === undefined &&
252
310
  args.options.autoSubscribeNewMembers === undefined &&
253
311
  args.options.hideFromAddressLists === undefined &&
@@ -257,16 +315,28 @@ _EntraM365GroupSetCommand_instances = new WeakSet(), _EntraM365GroupSetCommand_i
257
315
  if (args.options.id && !validation.isValidGuid(args.options.id)) {
258
316
  return `${args.options.id} is not a valid GUID`;
259
317
  }
260
- if (args.options.owners) {
261
- const isValidArray = validation.isValidUserPrincipalNameArray(args.options.owners);
262
- if (isValidArray !== true) {
263
- return `Option 'owners' contains one or more invalid UPNs: ${isValidArray}.`;
318
+ if (args.options.ownerIds) {
319
+ const isValidGUIDArrayResult = validation.isValidGuidArray(args.options.ownerIds);
320
+ if (isValidGUIDArrayResult !== true) {
321
+ return `The following GUIDs are invalid for the option 'ownerIds': ${isValidGUIDArrayResult}.`;
322
+ }
323
+ }
324
+ if (args.options.ownerUserNames) {
325
+ const isValidUPNArrayResult = validation.isValidUserPrincipalNameArray(args.options.ownerUserNames);
326
+ if (isValidUPNArrayResult !== true) {
327
+ return `The following user principal names are invalid for the option 'ownerUserNames': ${isValidUPNArrayResult}.`;
328
+ }
329
+ }
330
+ if (args.options.memberIds) {
331
+ const isValidGUIDArrayResult = validation.isValidGuidArray(args.options.memberIds);
332
+ if (isValidGUIDArrayResult !== true) {
333
+ return `The following GUIDs are invalid for the option 'memberIds': ${isValidGUIDArrayResult}.`;
264
334
  }
265
335
  }
266
- if (args.options.members) {
267
- const isValidArray = validation.isValidUserPrincipalNameArray(args.options.members);
268
- if (isValidArray !== true) {
269
- return `Option 'members' contains one or more invalid UPNs: ${isValidArray}.`;
336
+ if (args.options.memberUserNames) {
337
+ const isValidUPNArrayResult = validation.isValidUserPrincipalNameArray(args.options.memberUserNames);
338
+ if (isValidUPNArrayResult !== true) {
339
+ return `The following user principal names are invalid for the option 'memberUserNames': ${isValidUPNArrayResult}.`;
270
340
  }
271
341
  }
272
342
  if (args.options.logoPath) {
@@ -20,28 +20,34 @@ m365 aad m365group set [options]
20
20
 
21
21
  ```md definition-list
22
22
  `-i, --id [id]`
23
- : The ID of the Microsoft 365 Group to update
23
+ : The ID of the Microsoft 365 Group to update.
24
24
 
25
25
  `-n, --displayName [displayName]`
26
- : Display name of the Microsoft 365 Group to update
26
+ : Display name of the Microsoft 365 Group to update.
27
27
 
28
28
  `--newDisplayName [newDisplayName]`
29
- : New display name for the Microsoft 365 Group
29
+ : New display name for the Microsoft 365 Group.
30
30
 
31
31
  `-d, --description [description]`
32
- : Description for the Microsoft 365 Group
32
+ : Description for the Microsoft 365 Group.
33
33
 
34
- `--owners [owners]`
35
- : Comma-separated list of Microsoft 365 Group owners to add
34
+ `--ownerIds [ownerIds]`
35
+ : Comma-separated list of IDs of Microsoft Entra ID users that will be group owners. Specify either `ownerIds` or `ownerUserNames`, but not both.
36
36
 
37
- `--members [members]`
38
- : Comma-separated list of Microsoft 365 Group members to add
37
+ `--ownerUserNames [ownerUserNames]`
38
+ : Comma-separated list of UPNs of Microsoft Entra ID users that will be group owners. Specify either `ownerIds` or `ownerUserNames`, but not both.
39
+
40
+ `--memberIds [memberIds]`
41
+ : Comma-separated list of IDs of Microsoft Entra ID users that will be group members. Specify either `memberIds` or `memberUserNames`, but not both.
42
+
43
+ `--memberUserNames [memberUserNames]`
44
+ : Comma-separated list of UPNs of Microsoft Entra ID users that will be group members. Specify either `memberIds` or `memberUserNames`, but not both.
39
45
 
40
46
  `--isPrivate [isPrivate]`
41
47
  : Set to `true` if the Microsoft 365 Group should be private and `false` if it should be public.
42
48
 
43
49
  `-l, --logoPath [logoPath]`
44
- : Local path to the image file to use as group logo
50
+ : Local path to the image file to use as group logo.
45
51
 
46
52
  `--allowExternalSenders [allowExternalSenders]`
47
53
  : Indicates if people external to the organization can send messages to the group. Valid values: `true`, `false`.
@@ -60,7 +66,7 @@ m365 aad m365group set [options]
60
66
 
61
67
  ## Remarks
62
68
 
63
- When updating group's owners and members, the command will add newly specified users to the previously set owners and members. The previously set users will not be replaced.
69
+ When updating group's owners and members, the command will remove existing owners/members from the group, and the specified users will be added.
64
70
 
65
71
  When specifying the path to the logo image you can use both relative and absolute paths. Note, that ~ in the path, will not be resolved and will most likely result in an error.
66
72
 
@@ -84,16 +90,28 @@ Change Microsoft 365 Group visibility to public.
84
90
  m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --isPrivate `false`
85
91
  ```
86
92
 
87
- Add new Microsoft 365 Group owners of group.
93
+ Updates the list of Microsoft 365 Group owners with a list of users by UPN.
94
+
95
+ ```sh
96
+ m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --ownerUserNames "DebraB@contoso.onmicrosoft.com,DiegoS@contoso.onmicrosoft.com"
97
+ ```
98
+
99
+ Updates the list of Microsoft 365 Group owners with a list of users by ID.
100
+
101
+ ```sh
102
+ m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --ownerIds "3527dada-9368-4cdd-a958-5460f5658e0e,e94b2cb8-7c9a-4651-b1af-207d81a010b6"
103
+ ```
104
+
105
+ Updates the list of Microsoft 365 Group members with a list of users by UPN.
88
106
 
89
107
  ```sh
90
- m365 entra m365group set --displayName 'Project Team' --owners "DebraB@contoso.onmicrosoft.com,DiegoS@contoso.onmicrosoft.com"
108
+ m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --memberUserNames "DebraB@contoso.onmicrosoft.com,DiegoS@contoso.onmicrosoft.com"
91
109
  ```
92
110
 
93
- Add new Microsoft 365 Group members.
111
+ Updates the list of Microsoft 365 Group members with a list of users by ID.
94
112
 
95
113
  ```sh
96
- m365 entra m365group set --displayName 'Project Team' --members "DebraB@contoso.onmicrosoft.com,DiegoS@contoso.onmicrosoft.com"
114
+ m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --memberIds "3527dada-9368-4cdd-a958-5460f5658e0e,e94b2cb8-7c9a-4651-b1af-207d81a010b6"
97
115
  ```
98
116
 
99
117
  Update Microsoft 365 Group logo.
@@ -116,4 +134,4 @@ m365 entra m365group set --id 28beab62-7540-4db1-a23f-29a6018a3848 --autoSubscri
116
134
 
117
135
  ## Response
118
136
 
119
- The command won't return a response on success.
137
+ The command won't return a response on success.
@@ -47,6 +47,9 @@ m365 login [options]
47
47
 
48
48
  `--connectionName [connectionName]`
49
49
  : Specify an optional name to make switching between connections easier.
50
+
51
+ `--ensure`
52
+ : Ensures that the user is signed in. if the user isn't signed in, it initiates the login flow
50
53
  ```
51
54
 
52
55
  <Global />
@@ -190,6 +193,12 @@ Log in to Microsoft 365 using a client secret.
190
193
  m365 login --authType secret --secret topSeCr3t@007
191
194
  ```
192
195
 
196
+ Ensures that the user is signed in, initiates the login flow if the user isn't signed in
197
+
198
+ ```sh
199
+ m365 login --ensure
200
+ ```
201
+
193
202
  ## Response
194
203
 
195
204
  <Tabs>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnp/cli-microsoft365",
3
- "version": "10.0.0-beta.66db729",
3
+ "version": "10.0.0-beta.7be7794",
4
4
  "description": "Manage Microsoft 365 and SharePoint Framework projects on any platform",
5
5
  "license": "MIT",
6
6
  "main": "./dist/api.js",