@pnp/cli-microsoft365 7.5.0-beta.d4d820f → 7.6.0-beta.443bfd8

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.
Files changed (191) hide show
  1. package/.devcontainer/Dockerfile +2 -2
  2. package/.eslintrc.cjs +9 -2
  3. package/.mocharc.json +3 -5
  4. package/Dockerfile +2 -5
  5. package/README.md +2 -2
  6. package/allCommands.json +1 -1
  7. package/allCommandsFull.json +1 -1
  8. package/dist/Auth.js +181 -77
  9. package/dist/AuthServer.js +3 -3
  10. package/dist/Command.js +8 -5
  11. package/dist/auth/FileTokenStorage.js +4 -1
  12. package/dist/cli/cli.js +1 -1
  13. package/dist/m365/base/AppCommand.js +2 -2
  14. package/dist/m365/base/PowerAppsCommand.js +2 -2
  15. package/dist/m365/base/PowerAutomateCommand.js +2 -2
  16. package/dist/m365/base/PowerPlatformCommand.js +2 -2
  17. package/dist/m365/base/SpoCommand.js +3 -3
  18. package/dist/m365/base/{YammerCommand.js → VivaEngageCommand.js} +2 -2
  19. package/dist/m365/cli/commands/cli-consent.js +5 -3
  20. package/dist/m365/cli/commands/cli-doctor.js +5 -5
  21. package/dist/m365/commands/ConnectionDetails.js +2 -0
  22. package/dist/m365/commands/login.js +26 -48
  23. package/dist/m365/commands/logout.js +2 -2
  24. package/dist/m365/commands/request.js +2 -2
  25. package/dist/m365/commands/status.js +15 -23
  26. package/dist/m365/connection/commands/connection-list.js +47 -0
  27. package/dist/m365/connection/commands/connection-remove.js +67 -0
  28. package/dist/m365/connection/commands/connection-set.js +56 -0
  29. package/dist/m365/connection/commands/connection-use.js +51 -0
  30. package/dist/m365/connection/commands.js +8 -0
  31. package/dist/m365/entra/aadCommands.js +3 -0
  32. package/dist/m365/entra/commands/administrativeunit/administrativeunit-member-remove.js +153 -0
  33. package/dist/m365/entra/commands/app/app-add.js +2 -2
  34. package/dist/m365/entra/commands/app/app-get.js +1 -1
  35. package/dist/m365/entra/commands/app/app-list.js +1 -1
  36. package/dist/m365/entra/commands/app/app-permission-add.js +1 -1
  37. package/dist/m365/entra/commands/app/app-permission-list.js +182 -0
  38. package/dist/m365/entra/commands/app/app-remove.js +1 -1
  39. package/dist/m365/entra/commands/app/app-role-add.js +1 -1
  40. package/dist/m365/entra/commands/app/app-role-list.js +1 -1
  41. package/dist/m365/entra/commands/app/app-role-remove.js +1 -1
  42. package/dist/m365/entra/commands/app/app-set.js +1 -1
  43. package/dist/m365/entra/commands/{sp/sp-add.js → enterpriseapp/enterpriseapp-add.js} +18 -18
  44. package/dist/m365/entra/commands/{sp/sp-get.js → enterpriseapp/enterpriseapp-get.js} +19 -19
  45. package/dist/m365/entra/commands/{sp/sp-list.js → enterpriseapp/enterpriseapp-list.js} +13 -13
  46. package/dist/m365/entra/commands/group/group-add.js +224 -0
  47. package/dist/m365/entra/commands/group/group-user-add.js +145 -0
  48. package/dist/m365/entra/commands/m365group/m365group-report-activitygroupcounts.js +1 -1
  49. package/dist/m365/entra/commands/user/user-get.js +38 -38
  50. package/dist/m365/entra/commands/user/user-license-list.js +1 -1
  51. package/dist/m365/entra/commands/user/user-registrationdetails-list.js +224 -0
  52. package/dist/m365/entra/commands/user/user-set.js +2 -2
  53. package/dist/m365/entra/commands.js +17 -0
  54. package/dist/m365/external/commands/connection/connection-doctor.js +2 -1
  55. package/dist/m365/file/commands/convert/convert-pdf.js +1 -1
  56. package/dist/m365/file/commands/file-copy.js +151 -0
  57. package/dist/m365/file/commands.js +1 -0
  58. package/dist/m365/outlook/commands/mail/mail-send.js +1 -1
  59. package/dist/m365/outlook/commands/message/message-get.js +1 -1
  60. package/dist/m365/pa/commands/app/app-permission-ensure.js +1 -1
  61. package/dist/m365/pa/commands/app/app-permission-remove.js +1 -1
  62. package/dist/m365/planner/commands/roster/roster-plan-list.js +1 -1
  63. package/dist/m365/purview/commands/auditlog/auditlog-list.js +1 -1
  64. package/dist/m365/purview/commands/sensitivitylabel/sensitivitylabel-get.js +1 -1
  65. package/dist/m365/purview/commands/sensitivitylabel/sensitivitylabel-list.js +1 -1
  66. package/dist/m365/purview/commands/sensitivitylabel/sensitivitylabel-policysettings-list.js +1 -1
  67. package/dist/m365/purview/commands/threatassessment/threatassessment-add.js +123 -0
  68. package/dist/m365/purview/commands/threatassessment/threatassessment-list.js +104 -0
  69. package/dist/m365/purview/commands.js +3 -1
  70. package/dist/m365/spfx/commands/project/DeployWorkflow.js +115 -3
  71. package/dist/m365/spfx/commands/project/project-azuredevops-pipeline-add.js +183 -0
  72. package/dist/m365/spfx/commands/project/project-azuredevops-pipeline-model.js +2 -0
  73. package/dist/m365/spfx/commands/project/project-github-workflow-add.js +3 -4
  74. package/dist/m365/spfx/commands.js +1 -0
  75. package/dist/m365/spo/commands/spo-get.js +1 -1
  76. package/dist/m365/spo/commands/spo-search.js +52 -22
  77. package/dist/m365/spo/commands/spo-set.js +1 -1
  78. package/dist/m365/spo/commands/user/user-remove.js +93 -16
  79. package/dist/m365/teams/commands/chat/chat-get.js +1 -1
  80. package/dist/m365/teams/commands/chat/chat-list.js +1 -1
  81. package/dist/m365/teams/commands/chat/chat-message-send.js +1 -1
  82. package/dist/m365/teams/commands/meeting/meeting-add.js +1 -1
  83. package/dist/m365/teams/commands/meeting/meeting-attendancereport-list.js +1 -1
  84. package/dist/m365/teams/commands/meeting/meeting-get.js +1 -1
  85. package/dist/m365/teams/commands/meeting/meeting-list.js +2 -2
  86. package/dist/m365/teams/commands/meeting/meeting-transcript-list.js +1 -1
  87. package/dist/m365/tenant/commands/id/id-get.js +1 -1
  88. package/dist/m365/tenant/commands/info/info-get.js +1 -1
  89. package/dist/m365/util/commands/accesstoken/accesstoken-get.js +3 -3
  90. package/dist/m365/{yammer/commands/group/group-list.js → viva/commands/engage/engage-group-list.js} +19 -14
  91. package/dist/m365/{yammer/commands/group/group-user-add.js → viva/commands/engage/engage-group-user-add.js} +19 -14
  92. package/dist/m365/{yammer/commands/group/group-user-remove.js → viva/commands/engage/engage-group-user-remove.js} +19 -14
  93. package/dist/m365/{yammer/commands/message/message-add.js → viva/commands/engage/engage-message-add.js} +19 -14
  94. package/dist/m365/{yammer/commands/message/message-get.js → viva/commands/engage/engage-message-get.js} +17 -12
  95. package/dist/m365/{yammer/commands/message/message-like-set.js → viva/commands/engage/engage-message-like-set.js} +21 -16
  96. package/dist/m365/{yammer/commands/message/message-list.js → viva/commands/engage/engage-message-list.js} +21 -16
  97. package/dist/m365/{yammer/commands/message/message-remove.js → viva/commands/engage/engage-message-remove.js} +20 -15
  98. package/dist/m365/{yammer/commands/network/network-list.js → viva/commands/engage/engage-network-list.js} +16 -11
  99. package/dist/m365/viva/commands/engage/engage-report-activitycounts.js +23 -0
  100. package/dist/m365/viva/commands/engage/engage-report-activityusercounts.js +23 -0
  101. package/dist/m365/viva/commands/engage/engage-report-activityuserdetail.js +23 -0
  102. package/dist/m365/viva/commands/engage/engage-report-deviceusagedistributionusercounts.js +23 -0
  103. package/dist/m365/viva/commands/engage/engage-report-deviceusageusercounts.js +23 -0
  104. package/dist/m365/viva/commands/engage/engage-report-deviceusageuserdetail.js +23 -0
  105. package/dist/m365/viva/commands/engage/engage-report-groupsactivitycounts.js +23 -0
  106. package/dist/m365/viva/commands/engage/engage-report-groupsactivitydetail.js +23 -0
  107. package/dist/m365/viva/commands/engage/engage-report-groupsactivitygroupcounts.js +23 -0
  108. package/dist/m365/{yammer/commands/yammer-search.js → viva/commands/engage/engage-search.js} +23 -18
  109. package/dist/m365/{yammer/commands/user/user-get.js → viva/commands/engage/engage-user-get.js} +18 -13
  110. package/dist/m365/{yammer/commands/user/user-list.js → viva/commands/engage/engage-user-list.js} +18 -13
  111. package/dist/m365/{yammer/commands.js → viva/commands/engage/yammerCommands.js} +1 -1
  112. package/dist/m365/viva/commands.js +22 -1
  113. package/dist/request.js +1 -1
  114. package/dist/utils/entraUser.js +36 -0
  115. package/dist/utils/spo.js +36 -14
  116. package/dist/utils/urlUtil.js +31 -0
  117. package/dist/utils/validation.js +8 -0
  118. package/docs/docs/cmd/cli/cli-consent.mdx +9 -9
  119. package/docs/docs/cmd/connection/connection-list.mdx +100 -0
  120. package/docs/docs/cmd/connection/connection-remove.mdx +45 -0
  121. package/docs/docs/cmd/connection/connection-set.mdx +39 -0
  122. package/docs/docs/cmd/connection/connection-use.mdx +98 -0
  123. package/docs/docs/cmd/entra/administrativeunit/administrativeunit-member-add.mdx +5 -3
  124. package/docs/docs/cmd/entra/administrativeunit/administrativeunit-member-remove.mdx +107 -0
  125. package/docs/docs/cmd/entra/app/app-add.mdx +2 -1
  126. package/docs/docs/cmd/entra/app/app-get.mdx +1 -0
  127. package/docs/docs/cmd/entra/app/app-list.mdx +1 -0
  128. package/docs/docs/cmd/entra/app/app-permission-add.mdx +1 -0
  129. package/docs/docs/cmd/entra/app/app-permission-list.mdx +105 -0
  130. package/docs/docs/cmd/entra/app/app-remove.mdx +1 -0
  131. package/docs/docs/cmd/entra/app/app-role-add.mdx +1 -0
  132. package/docs/docs/cmd/entra/app/app-role-list.mdx +1 -0
  133. package/docs/docs/cmd/entra/app/app-role-remove.mdx +1 -0
  134. package/docs/docs/cmd/entra/app/app-set.mdx +1 -0
  135. package/docs/docs/cmd/entra/{sp/sp-add.mdx → enterpriseapp/enterpriseapp-add.mdx} +16 -15
  136. package/docs/docs/cmd/entra/{sp/sp-get.mdx → enterpriseapp/enterpriseapp-get.mdx} +15 -14
  137. package/docs/docs/cmd/entra/{sp/sp-list.mdx → enterpriseapp/enterpriseapp-list.mdx} +22 -21
  138. package/docs/docs/cmd/entra/group/group-add.mdx +231 -0
  139. package/docs/docs/cmd/entra/group/group-user-add.mdx +62 -0
  140. package/docs/docs/cmd/entra/m365group/m365group-report-activitygroupcounts.mdx +1 -1
  141. package/docs/docs/cmd/entra/oauth2grant/oauth2grant-add.mdx +3 -3
  142. package/docs/docs/cmd/entra/oauth2grant/oauth2grant-list.mdx +1 -1
  143. package/docs/docs/cmd/entra/user/user-registrationdetails-list.mdx +246 -0
  144. package/docs/docs/cmd/external/item/item-add.mdx +1 -1
  145. package/docs/docs/cmd/file/convert/convert-pdf.mdx +4 -0
  146. package/docs/docs/cmd/file/file-add.mdx +4 -0
  147. package/docs/docs/cmd/file/file-copy.mdx +68 -0
  148. package/docs/docs/cmd/file/file-list.mdx +100 -0
  149. package/docs/docs/cmd/login.mdx +15 -0
  150. package/docs/docs/cmd/purview/threatassessment/threatassessment-add.mdx +131 -0
  151. package/docs/docs/cmd/purview/threatassessment/threatassessment-list.mdx +110 -0
  152. package/docs/docs/cmd/setup.mdx +4 -0
  153. package/docs/docs/cmd/spfx/project/project-azuredevops-pipeline-add.mdx +87 -0
  154. package/docs/docs/cmd/spo/list/list-webhook-set.mdx +1 -1
  155. package/docs/docs/cmd/spo/spo-search.mdx +6 -2
  156. package/docs/docs/cmd/spo/theme/theme-set.mdx +14 -2
  157. package/docs/docs/cmd/spo/user/user-remove.mdx +40 -9
  158. package/docs/docs/cmd/{yammer/group/group-list.mdx → viva/engage/engage-group-list.mdx} +13 -13
  159. package/docs/docs/cmd/{yammer/group/group-user-add.mdx → viva/engage/engage-group-user-add.mdx} +7 -7
  160. package/docs/docs/cmd/{yammer/group/group-user-remove.mdx → viva/engage/engage-group-user-remove.mdx} +10 -10
  161. package/docs/docs/cmd/{yammer/message/message-add.mdx → viva/engage/engage-message-add.mdx} +12 -13
  162. package/docs/docs/cmd/{yammer/message/message-get.mdx → viva/engage/engage-message-get.mdx} +10 -10
  163. package/docs/docs/cmd/{yammer/message/message-like-set.mdx → viva/engage/engage-message-like-set.mdx} +10 -10
  164. package/docs/docs/cmd/{yammer/message/message-list.mdx → viva/engage/engage-message-list.mdx} +21 -21
  165. package/docs/docs/cmd/{yammer/message/message-remove.mdx → viva/engage/engage-message-remove.mdx} +10 -10
  166. package/docs/docs/cmd/{yammer/network/network-list.mdx → viva/engage/engage-network-list.mdx} +6 -6
  167. package/docs/docs/cmd/{yammer/report/report-activitycounts.mdx → viva/engage/engage-report-activitycounts.mdx} +9 -9
  168. package/docs/docs/cmd/{yammer/report/report-activityusercounts.mdx → viva/engage/engage-report-activityusercounts.mdx} +9 -9
  169. package/docs/docs/cmd/{yammer/report/report-activityuserdetail.mdx → viva/engage/engage-report-activityuserdetail.mdx} +11 -12
  170. package/docs/docs/cmd/{yammer/report/report-deviceusagedistributionusercounts.mdx → viva/engage/engage-report-deviceusagedistributionusercounts.mdx} +5 -5
  171. package/docs/docs/cmd/{yammer/report/report-deviceusageusercounts.mdx → viva/engage/engage-report-deviceusageusercounts.mdx} +5 -5
  172. package/docs/docs/cmd/{yammer/report/report-deviceusageuserdetail.mdx → viva/engage/engage-report-deviceusageuserdetail.mdx} +11 -11
  173. package/docs/docs/cmd/{yammer/report/report-groupsactivitycounts.mdx → viva/engage/engage-report-groupsactivitycounts.mdx} +9 -9
  174. package/docs/docs/cmd/{yammer/report/report-groupsactivitydetail.mdx → viva/engage/engage-report-groupsactivitydetail.mdx} +11 -11
  175. package/docs/docs/cmd/{yammer/report/report-groupsactivitygroupcounts.mdx → viva/engage/engage-report-groupsactivitygroupcounts.mdx} +5 -5
  176. package/docs/docs/cmd/{yammer/yammer-search.mdx → viva/engage/engage-search.mdx} +10 -10
  177. package/docs/docs/cmd/{yammer/user/user-get.mdx → viva/engage/engage-user-get.mdx} +8 -8
  178. package/docs/docs/cmd/{yammer/user/user-list.mdx → viva/engage/engage-user-list.mdx} +11 -11
  179. package/npm-shrinkwrap.json +230 -168
  180. package/package.json +20 -19
  181. package/dist/m365/base/AzmgmtCommand.js +0 -18
  182. package/dist/m365/base/AzmgmtItemsListCommand.js +0 -41
  183. package/dist/m365/yammer/commands/report/report-activitycounts.js +0 -15
  184. package/dist/m365/yammer/commands/report/report-activityusercounts.js +0 -15
  185. package/dist/m365/yammer/commands/report/report-activityuserdetail.js +0 -15
  186. package/dist/m365/yammer/commands/report/report-deviceusagedistributionusercounts.js +0 -15
  187. package/dist/m365/yammer/commands/report/report-deviceusageusercounts.js +0 -15
  188. package/dist/m365/yammer/commands/report/report-deviceusageuserdetail.js +0 -15
  189. package/dist/m365/yammer/commands/report/report-groupsactivitycounts.js +0 -15
  190. package/dist/m365/yammer/commands/report/report-groupsactivitydetail.js +0 -15
  191. package/dist/m365/yammer/commands/report/report-groupsactivitygroupcounts.js +0 -15
package/dist/Auth.js CHANGED
@@ -7,6 +7,8 @@ import config from './config.js';
7
7
  import request from './request.js';
8
8
  import { settingsNames } from './settingsNames.js';
9
9
  import { browserUtil } from './utils/browserUtil.js';
10
+ import * as accessTokenUtil from './utils/accessToken.js';
11
+ import assert from 'assert';
10
12
  export var CloudType;
11
13
  (function (CloudType) {
12
14
  CloudType[CloudType["Public"] = 0] = "Public";
@@ -15,9 +17,9 @@ export var CloudType;
15
17
  CloudType[CloudType["USGovDoD"] = 3] = "USGovDoD";
16
18
  CloudType[CloudType["China"] = 4] = "China";
17
19
  })(CloudType || (CloudType = {}));
18
- export class Service {
20
+ export class Connection {
19
21
  constructor() {
20
- this.connected = false;
22
+ this.active = false;
21
23
  this.authType = AuthType.DeviceCode;
22
24
  this.certificateType = CertificateType.Unknown;
23
25
  this.cloudType = CloudType.Public;
@@ -26,17 +28,22 @@ export class Service {
26
28
  this.tenant = config.tenant;
27
29
  this.cloudType = CloudType.Public;
28
30
  }
29
- logout() {
30
- this.connected = false;
31
+ deactivate() {
32
+ this.active = false;
33
+ this.name = undefined;
34
+ this.identityName = undefined;
35
+ this.identityId = undefined;
36
+ this.identityTenantId = undefined;
31
37
  this.accessTokens = {};
32
38
  this.authType = AuthType.DeviceCode;
33
39
  this.userName = undefined;
34
40
  this.password = undefined;
35
41
  this.certificateType = CertificateType.Unknown;
42
+ this.cloudType = CloudType.Public;
36
43
  this.certificate = undefined;
37
44
  this.thumbprint = undefined;
38
45
  this.spoUrl = undefined;
39
- this.tenantId = undefined;
46
+ this.spoTenantId = undefined;
40
47
  this.appId = config.cliAadAppId;
41
48
  this.tenant = config.tenant;
42
49
  }
@@ -57,14 +64,26 @@ export var CertificateType;
57
64
  CertificateType[CertificateType["Binary"] = 2] = "Binary";
58
65
  })(CertificateType || (CertificateType = {}));
59
66
  export class Auth {
60
- get service() {
61
- return this._service;
67
+ // Retrieves the connections from the file store if it's not already loaded
68
+ async getAllConnections() {
69
+ if (this._allConnections === undefined) {
70
+ try {
71
+ this._allConnections = await this.getAllConnectionsFromStorage();
72
+ }
73
+ catch {
74
+ this._allConnections = [];
75
+ }
76
+ }
77
+ return this._allConnections;
78
+ }
79
+ get connection() {
80
+ return this._connection;
62
81
  }
63
82
  get defaultResource() {
64
- return Auth.getEndpointForResource('https://graph.microsoft.com', this._service.cloudType);
83
+ return Auth.getEndpointForResource('https://graph.microsoft.com', this._connection.cloudType);
65
84
  }
66
85
  constructor() {
67
- this._service = new Service();
86
+ this._connection = new Connection();
68
87
  }
69
88
  // we need to init cloud endpoints here, because we're using CloudType enum
70
89
  // as indexers, which we can't do in the static initializer
@@ -98,19 +117,19 @@ export class Auth {
98
117
  }
99
118
  async restoreAuth() {
100
119
  // check if auth has been restored previously
101
- if (this._service.connected) {
120
+ if (this._connection.active) {
102
121
  return Promise.resolve();
103
122
  }
104
123
  try {
105
- const service = await this.getServiceConnectionInfo();
106
- this._service = Object.assign(this._service, service);
124
+ const connection = await this.getConnectionInfoFromStorage();
125
+ this._connection = Object.assign(this._connection, connection);
107
126
  }
108
127
  catch {
109
128
  }
110
129
  }
111
130
  async ensureAccessToken(resource, logger, debug = false, fetchNew = false) {
112
131
  const now = new Date();
113
- const accessToken = this.service.accessTokens[resource];
132
+ const accessToken = this.connection.accessTokens[resource];
114
133
  const expiresOn = accessToken && accessToken.expiresOn ?
115
134
  // if expiresOn is serialized from the service file, it's set as a string
116
135
  // if it's coming from MSAL, it's a Date
@@ -125,7 +144,7 @@ export class Auth {
125
144
  else {
126
145
  if (debug) {
127
146
  if (!accessToken) {
128
- await logger.logToStderr(`No token found for resource ${resource}`);
147
+ await logger.logToStderr(`No token found for resource ${resource}.`);
129
148
  }
130
149
  else {
131
150
  await logger.logToStderr(`Access token expired. Token: ${accessToken.accessToken}, ExpiresAt: ${accessToken.expiresOn}`);
@@ -133,21 +152,24 @@ export class Auth {
133
152
  }
134
153
  }
135
154
  let getTokenPromise;
136
- // when using cert, you can't retrieve token silently, because there is
137
- // no account. Also cert auth instantiates clientApplication itself
155
+ // When using an application identity, you can't retrieve the access token silently, because there is
156
+ // no account. Also (for cert auth) clientApplication is instantiated later
138
157
  // after inspecting the specified cert and calculating thumbprint if one
139
158
  // wasn't specified
140
- if (this.service.authType !== AuthType.Certificate) {
141
- this.clientApplication = await this.getClientApplication(logger, debug);
159
+ if (this.connection.authType !== AuthType.Certificate &&
160
+ this.connection.authType !== AuthType.Secret &&
161
+ this.connection.authType !== AuthType.Identity) {
162
+ this.clientApplication = await this.getPublicClient(logger, debug);
142
163
  if (this.clientApplication) {
143
164
  const accounts = await this.clientApplication.getTokenCache().getAllAccounts();
144
- if (accounts.length > 0) {
165
+ // if there is an account in the cache and it's active, we can try to get the token silently
166
+ if (accounts.filter(a => a.localAccountId === this.connection.identityId).length > 0 && this.connection.active === true) {
145
167
  getTokenPromise = this.ensureAccessTokenSilent.bind(this);
146
168
  }
147
169
  }
148
170
  }
149
171
  if (!getTokenPromise) {
150
- switch (this.service.authType) {
172
+ switch (this.connection.authType) {
151
173
  case AuthType.DeviceCode:
152
174
  getTokenPromise = this.ensureAccessTokenWithDeviceCode.bind(this);
153
175
  break;
@@ -171,9 +193,9 @@ export class Auth {
171
193
  const response = await getTokenPromise(resource, logger, debug, fetchNew);
172
194
  if (!response) {
173
195
  if (debug) {
174
- await logger.logToStderr(`getTokenPromise authentication result is null`);
196
+ await logger.logToStderr('getTokenPromise authentication result is null.');
175
197
  }
176
- throw `Failed to retrieve an access token. Please try again`;
198
+ throw 'Failed to retrieve an access token. Please try again.';
177
199
  }
178
200
  else {
179
201
  if (debug) {
@@ -182,11 +204,15 @@ export class Auth {
182
204
  await logger.logToStderr('');
183
205
  }
184
206
  }
185
- this.service.accessTokens[resource] = {
207
+ this.connection.accessTokens[resource] = {
186
208
  expiresOn: response.expiresOn,
187
209
  accessToken: response.accessToken
188
210
  };
189
- this.service.connected = true;
211
+ this.connection.active = true;
212
+ this.connection.identityName = accessTokenUtil.accessToken.getUserNameFromAccessToken(response.accessToken);
213
+ this.connection.identityId = accessTokenUtil.accessToken.getUserIdFromAccessToken(response.accessToken);
214
+ this.connection.identityTenantId = accessTokenUtil.accessToken.getTenantIdFromAccessToken(response.accessToken);
215
+ this.connection.name = this.connection.name || this.connection.identityId;
190
216
  try {
191
217
  await this.storeConnectionInfo();
192
218
  }
@@ -199,21 +225,6 @@ export class Auth {
199
225
  }
200
226
  return response.accessToken;
201
227
  }
202
- async getClientApplication(logger, debug) {
203
- switch (this.service.authType) {
204
- case AuthType.DeviceCode:
205
- case AuthType.Password:
206
- case AuthType.Browser:
207
- return await this.getPublicClient(logger, debug);
208
- case AuthType.Certificate:
209
- return await this.getConfidentialClient(logger, debug, this.service.thumbprint, this.service.password, undefined);
210
- case AuthType.Identity:
211
- // msal-node doesn't support managed identity so we need to do it manually
212
- return undefined;
213
- case AuthType.Secret:
214
- return await this.getConfidentialClient(logger, debug, undefined, undefined, this.service.secret);
215
- }
216
- }
217
228
  async getAuthClientConfiguration(logger, debug, certificateThumbprint, certificatePrivateKey, clientSecret) {
218
229
  const msal = await import('@azure/msal-node');
219
230
  const { LogLevel } = msal;
@@ -222,7 +233,7 @@ export class Auth {
222
233
  privateKey: certificatePrivateKey
223
234
  };
224
235
  let azureCloudInstance = AzureCloudInstance.None;
225
- switch (this.service.cloudType) {
236
+ switch (this.connection.cloudType) {
226
237
  case CloudType.Public:
227
238
  azureCloudInstance = AzureCloudInstance.AzurePublic;
228
239
  break;
@@ -236,11 +247,11 @@ export class Auth {
236
247
  break;
237
248
  }
238
249
  const config = {
239
- clientId: this.service.appId,
240
- authority: `${Auth.getEndpointForResource('https://login.microsoftonline.com', this.service.cloudType)}/${this.service.tenant}`,
250
+ clientId: this.connection.appId,
251
+ authority: `${Auth.getEndpointForResource('https://login.microsoftonline.com', this.connection.cloudType)}/${this.connection.tenant}`,
241
252
  azureCloudOptions: {
242
253
  azureCloudInstance,
243
- tenant: this.service.tenant
254
+ tenant: this.connection.tenant
244
255
  }
245
256
  };
246
257
  const authConfig = cert
@@ -270,11 +281,11 @@ export class Auth {
270
281
  async getPublicClient(logger, debug) {
271
282
  const msal = await import('@azure/msal-node');
272
283
  const { PublicClientApplication } = msal;
273
- if (this.service.authType === AuthType.Password &&
274
- this.service.tenant === 'common') {
284
+ if (this.connection.authType === AuthType.Password &&
285
+ this.connection.tenant === 'common') {
275
286
  // common is not supported for the password flow and must be changed to
276
287
  // organizations
277
- this.service.tenant = 'organizations';
288
+ this.connection.tenant = 'organizations';
278
289
  }
279
290
  return new PublicClientApplication(await this.getAuthClientConfiguration(logger, debug));
280
291
  }
@@ -292,7 +303,7 @@ export class Auth {
292
303
  if (!this._authServer) {
293
304
  this._authServer = (await import('./AuthServer.js')).default;
294
305
  }
295
- this._authServer.initializeServer(this.service, resource, resolve, reject, logger, debug);
306
+ this._authServer.initializeServer(this.connection, resource, resolve, reject, logger, debug);
296
307
  });
297
308
  }
298
309
  async ensureAccessTokenWithBrowser(resource, logger, debug) {
@@ -313,10 +324,14 @@ export class Auth {
313
324
  if (debug) {
314
325
  await logger.logToStderr(`Retrieving new access token silently`);
315
326
  }
316
- const accounts = await this.clientApplication
317
- .getTokenCache().getAllAccounts();
327
+ // Asserting identityId because it is expected to be available at this point.
328
+ assert(this.connection.identityId !== undefined);
329
+ const account = await this.clientApplication
330
+ .getTokenCache().getAccountByLocalId(this.connection.identityId);
331
+ // Asserting account because it is expected to be available at this point.
332
+ assert(account !== null);
318
333
  return this.clientApplication.acquireTokenSilent({
319
- account: accounts[0],
334
+ account: account,
320
335
  scopes: [`${resource}/.default`],
321
336
  forceRefresh: fetchNew
322
337
  });
@@ -367,40 +382,40 @@ export class Auth {
367
382
  await logger.logToStderr(`Retrieving new access token using credentials...`);
368
383
  }
369
384
  return this.clientApplication.acquireTokenByUsernamePassword({
370
- username: this.service.userName,
371
- password: this.service.password,
385
+ username: this.connection.userName,
386
+ password: this.connection.password,
372
387
  scopes: [`${resource}/.default`]
373
388
  });
374
389
  }
375
- async ensureAccessTokenWithCertificate(resource, logger, debug) {
390
+ async ensureAccessTokenWithCertificate(resource, logger, debug, fetchNew) {
376
391
  const nodeForge = (await import('node-forge')).default;
377
392
  const { pem, pki, asn1, pkcs12 } = nodeForge;
378
393
  if (debug) {
379
394
  await logger.logToStderr(`Retrieving new access token using certificate...`);
380
395
  }
381
396
  let cert = '';
382
- const buf = Buffer.from(this.service.certificate, 'base64');
383
- if (this.service.certificateType === CertificateType.Unknown || this.service.certificateType === CertificateType.Base64) {
397
+ const buf = Buffer.from(this.connection.certificate, 'base64');
398
+ if (this.connection.certificateType === CertificateType.Unknown || this.connection.certificateType === CertificateType.Base64) {
384
399
  // First time this method is called, we don't know if certificate is PEM or PFX (type is Unknown)
385
400
  // We assume it is PEM but when parsing of PEM fails, we assume it could be PFX
386
401
  // Type is persisted on service so subsequent calls only run through the correct parsing flow
387
402
  try {
388
403
  cert = buf.toString('utf8');
389
404
  const pemObjs = pem.decode(cert);
390
- if (this.service.thumbprint === undefined) {
405
+ if (this.connection.thumbprint === undefined) {
391
406
  const pemCertObj = pemObjs.find(pem => pem.type === "CERTIFICATE");
392
407
  const pemCertStr = pem.encode(pemCertObj);
393
408
  const pemCert = pki.certificateFromPem(pemCertStr);
394
- this.service.thumbprint = await this.calculateThumbprint(pemCert);
409
+ this.connection.thumbprint = await this.calculateThumbprint(pemCert);
395
410
  }
396
411
  }
397
412
  catch (e) {
398
- this.service.certificateType = CertificateType.Binary;
413
+ this.connection.certificateType = CertificateType.Binary;
399
414
  }
400
415
  }
401
- if (this.service.certificateType === CertificateType.Binary) {
416
+ if (this.connection.certificateType === CertificateType.Binary) {
402
417
  const p12Asn1 = asn1.fromDer(buf.toString('binary'), false);
403
- const p12Parsed = pkcs12.pkcs12FromAsn1(p12Asn1, false, this.service.password);
418
+ const p12Parsed = pkcs12.pkcs12FromAsn1(p12Asn1, false, this.connection.password);
404
419
  let keyBags = p12Parsed.getBags({ bagType: pki.oids.pkcs8ShroudedKeyBag });
405
420
  const pkcs8ShroudedKeyBag = keyBags[pki.oids.pkcs8ShroudedKeyBag][0];
406
421
  if (debug) {
@@ -419,19 +434,20 @@ export class Auth {
419
434
  const privateKeyInfo = pki.wrapRsaPrivateKey(rsaPrivateKey);
420
435
  // convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
421
436
  cert = pki.privateKeyInfoToPem(privateKeyInfo);
422
- if (this.service.thumbprint === undefined) {
437
+ if (this.connection.thumbprint === undefined) {
423
438
  const certBags = p12Parsed.getBags({ bagType: pki.oids.certBag });
424
439
  const certBag = (certBags[pki.oids.certBag])[0];
425
- this.service.thumbprint = await this.calculateThumbprint(certBag.cert);
440
+ this.connection.thumbprint = await this.calculateThumbprint(certBag.cert);
426
441
  }
427
442
  }
428
- this.clientApplication = await this.getConfidentialClient(logger, debug, this.service.thumbprint, cert);
443
+ this.clientApplication = await this.getConfidentialClient(logger, debug, this.connection.thumbprint, cert);
429
444
  return this.clientApplication.acquireTokenByClientCredential({
430
- scopes: [`${resource}/.default`]
445
+ scopes: [`${resource}/.default`],
446
+ skipCache: fetchNew
431
447
  });
432
448
  }
433
449
  async ensureAccessTokenWithIdentity(resource, logger, debug) {
434
- const userName = this.service.userName;
450
+ const userName = this.connection.userName;
435
451
  if (debug) {
436
452
  await logger.logToStderr('Will try to retrieve access token using identity...');
437
453
  }
@@ -547,10 +563,11 @@ export class Auth {
547
563
  }
548
564
  }
549
565
  }
550
- async ensureAccessTokenWithSecret(resource, logger, debug) {
551
- this.clientApplication = await this.getConfidentialClient(logger, debug, undefined, undefined, this.service.secret);
566
+ async ensureAccessTokenWithSecret(resource, logger, debug, fetchNew) {
567
+ this.clientApplication = await this.getConfidentialClient(logger, debug, undefined, undefined, this.connection.secret);
552
568
  return this.clientApplication.acquireTokenByClientCredential({
553
- scopes: [`${resource}/.default`]
569
+ scopes: [`${resource}/.default`],
570
+ skipCache: fetchNew
554
571
  });
555
572
  }
556
573
  async calculateThumbprint(certificate) {
@@ -564,7 +581,7 @@ export class Auth {
564
581
  let resource = url;
565
582
  const pos = resource.indexOf('/', 8);
566
583
  if (pos > -1) {
567
- resource = resource.substr(0, pos);
584
+ resource = resource.substring(0, pos);
568
585
  }
569
586
  if (resource === 'https://api.bap.microsoft.com' ||
570
587
  resource === 'https://api.powerapps.com' ||
@@ -581,29 +598,70 @@ export class Auth {
581
598
  }
582
599
  return resource;
583
600
  }
584
- async getServiceConnectionInfo() {
585
- const tokenStorage = this.getTokenStorage();
601
+ async getConnectionInfoFromStorage() {
602
+ const tokenStorage = this.getConnectionStorage();
586
603
  const json = await tokenStorage.get();
587
604
  return JSON.parse(json);
588
605
  }
589
- storeConnectionInfo() {
590
- const tokenStorage = this.getTokenStorage();
591
- return tokenStorage.set(JSON.stringify(this.service));
606
+ async storeConnectionInfo() {
607
+ const connectionStorage = this.getConnectionStorage();
608
+ await connectionStorage.set(JSON.stringify(this.connection));
609
+ let allConnections = await this.getAllConnections();
610
+ if (this.connection.active) {
611
+ allConnections = allConnections.filter(c => c.identityId !== this.connection.identityId);
612
+ allConnections.forEach(c => c.active = false);
613
+ allConnections = [{ ...this.connection }, ...allConnections];
614
+ }
615
+ this._allConnections = allConnections;
616
+ const allConnectionsStorage = this.getAllConnectionsStorage();
617
+ await allConnectionsStorage.set(JSON.stringify(allConnections));
592
618
  }
593
619
  async clearConnectionInfo() {
594
- const tokenStorage = this.getTokenStorage();
595
- await tokenStorage.remove();
620
+ const connectionStorage = this.getConnectionStorage();
621
+ const allConnectionsStorage = this.getAllConnectionsStorage();
622
+ await connectionStorage.remove();
623
+ await allConnectionsStorage.remove();
596
624
  // we need to manually clear MSAL cache, because MSAL doesn't have support
597
625
  // for logging out when using cert-based auth
598
626
  const msalCache = this.getMsalCacheStorage();
599
627
  await msalCache.remove();
600
628
  }
601
- getTokenStorage() {
629
+ async removeConnectionInfo(connection, logger, debug) {
630
+ const allConnections = await this.getAllConnections();
631
+ const isCurrentConnection = this.connection.name === connection.name;
632
+ this._allConnections = allConnections.filter(c => c.name !== connection.name);
633
+ // Asserting identityId because it is optional, but required at this point.
634
+ assert(connection.identityId !== undefined);
635
+ // When using an application identity, there is no account in the MSAL TokenCache
636
+ if (this.connection.authType !== AuthType.Certificate &&
637
+ this.connection.authType !== AuthType.Secret &&
638
+ this.connection.authType !== AuthType.Identity) {
639
+ this.clientApplication = await this.getPublicClient(logger, debug);
640
+ if (this.clientApplication) {
641
+ const tokenCache = this.clientApplication.getTokenCache();
642
+ const account = await tokenCache.getAccountByLocalId(connection.identityId);
643
+ if (account !== null) {
644
+ await tokenCache.removeAccount(account);
645
+ }
646
+ }
647
+ }
648
+ const connectionStorage = this.getConnectionStorage();
649
+ const allConnectionsStorage = this.getAllConnectionsStorage();
650
+ await allConnectionsStorage.set(JSON.stringify(this._allConnections));
651
+ if (isCurrentConnection) {
652
+ await connectionStorage.remove();
653
+ this.connection.deactivate();
654
+ }
655
+ }
656
+ getConnectionStorage() {
602
657
  return new FileTokenStorage(FileTokenStorage.connectionInfoFilePath());
603
658
  }
604
659
  getMsalCacheStorage() {
605
660
  return new FileTokenStorage(FileTokenStorage.msalCacheFilePath());
606
661
  }
662
+ getAllConnectionsStorage() {
663
+ return new FileTokenStorage(FileTokenStorage.allConnectionsFilePath());
664
+ }
607
665
  static getEndpointForResource(resource, cloudType) {
608
666
  if (Auth.cloudEndpoints[cloudType] &&
609
667
  Auth.cloudEndpoints[cloudType][resource]) {
@@ -613,6 +671,52 @@ export class Auth {
613
671
  return resource;
614
672
  }
615
673
  }
674
+ async getAllConnectionsFromStorage() {
675
+ const connectionStorage = this.getAllConnectionsStorage();
676
+ const json = await connectionStorage.get();
677
+ return JSON.parse(json);
678
+ }
679
+ async switchToConnection(connection) {
680
+ this.connection.deactivate();
681
+ this._connection = Object.assign(this._connection, connection);
682
+ this._connection.active = true;
683
+ await this.storeConnectionInfo();
684
+ }
685
+ async updateConnection(connection, newName) {
686
+ const allConnections = await this.getAllConnections();
687
+ const existingConnection = allConnections.find(c => c.name === newName);
688
+ const oldName = connection.name;
689
+ if (existingConnection) {
690
+ throw new CommandError(`The connection name '${newName}' is already in use`);
691
+ }
692
+ connection.name = newName;
693
+ if (this.connection.name === oldName) {
694
+ this._connection.name = newName;
695
+ }
696
+ await this.storeConnectionInfo();
697
+ }
698
+ async getConnection(name) {
699
+ const allConnections = await this.getAllConnections();
700
+ const connection = allConnections.find(i => i.name === name);
701
+ if (!connection) {
702
+ throw new CommandError(`The connection '${name}' cannot be found`);
703
+ }
704
+ return connection;
705
+ }
706
+ getConnectionDetails(connection) {
707
+ // Asserting name and identityId because they are optional, but required at this point.
708
+ assert(connection.identityName !== undefined);
709
+ assert(connection.name !== undefined);
710
+ const details = {
711
+ connectionName: connection.name,
712
+ connectedAs: connection.identityName,
713
+ authType: AuthType[connection.authType],
714
+ appId: connection.appId,
715
+ appTenant: connection.tenant,
716
+ cloudType: CloudType[connection.cloudType]
717
+ };
718
+ return details;
719
+ }
616
720
  }
617
721
  Auth.cloudEndpoints = [];
618
722
  Auth.initialize();
@@ -7,8 +7,8 @@ export class AuthServer {
7
7
  this.debug = false;
8
8
  this.resource = "";
9
9
  this.generatedServerUrl = "";
10
- this.initializeServer = (service, resource, resolve, reject, logger, debug = false) => {
11
- this.service = service;
10
+ this.initializeServer = (connection, resource, resolve, reject, logger, debug = false) => {
11
+ this.connection = connection;
12
12
  this.resolve = resolve;
13
13
  this.reject = reject;
14
14
  this.logger = logger;
@@ -20,7 +20,7 @@ export class AuthServer {
20
20
  const requestState = Math.random().toString(16).substr(2, 20);
21
21
  const address = this.httpServer.address();
22
22
  this.generatedServerUrl = `http://localhost:${address.port}`;
23
- const url = `${Auth.getEndpointForResource('https://login.microsoftonline.com', this.service.cloudType)}/${this.service.tenant}/oauth2/authorize?response_type=code&client_id=${this.service.appId}&redirect_uri=${this.generatedServerUrl}&state=${requestState}&resource=${this.resource}&prompt=select_account`;
23
+ const url = `${Auth.getEndpointForResource('https://login.microsoftonline.com', this.connection.cloudType)}/${this.connection.tenant}/oauth2/authorize?response_type=code&client_id=${this.connection.appId}&redirect_uri=${this.generatedServerUrl}&state=${requestState}&resource=${this.resource}&prompt=select_account`;
24
24
  if (this.debug) {
25
25
  this.logger.logToStderr('Redirect URL:');
26
26
  this.logger.logToStderr(url);
package/dist/Command.js CHANGED
@@ -180,7 +180,7 @@ class Command {
180
180
  throw new CommandError(error);
181
181
  }
182
182
  this.initAction(args, logger);
183
- if (!auth.service.connected) {
183
+ if (!auth.connection.active) {
184
184
  throw new CommandError('Log in to Microsoft 365 first');
185
185
  }
186
186
  try {
@@ -313,6 +313,9 @@ class Command {
313
313
  this.verbose = this.debug || args.options.verbose || process.env.CLIMICROSOFT365_VERBOSE === '1';
314
314
  request.debug = this.debug;
315
315
  request.logger = logger;
316
+ if (this.debug && auth.connection.identityName !== undefined) {
317
+ logger.logToStderr(`Executing command as '${auth.connection.identityName}', appId: ${auth.connection.appId}, tenantId: ${auth.connection.identityTenantId}`);
318
+ }
316
319
  telemetry.trackEvent(this.getUsedCommandName(), this.getTelemetryProperties(args));
317
320
  }
318
321
  getUnknownOptions(options) {
@@ -350,10 +353,10 @@ class Command {
350
353
  });
351
354
  }
352
355
  loadValuesFromAccessToken(args) {
353
- if (!auth.service.accessTokens[auth.defaultResource]) {
356
+ if (!auth.connection.accessTokens[auth.defaultResource]) {
354
357
  return;
355
358
  }
356
- const token = auth.service.accessTokens[auth.defaultResource].accessToken;
359
+ const token = auth.connection.accessTokens[auth.defaultResource].accessToken;
357
360
  const optionNames = Object.getOwnPropertyNames(args.options);
358
361
  optionNames.forEach(option => {
359
362
  const value = args.options[option];
@@ -362,7 +365,7 @@ class Command {
362
365
  }
363
366
  const lowerCaseValue = value.toLowerCase().trim();
364
367
  if (lowerCaseValue === '@meid' || lowerCaseValue === '@meusername') {
365
- const isAppOnlyAccessToken = accessToken.isAppOnlyAccessToken(auth.service.accessTokens[auth.defaultResource].accessToken);
368
+ const isAppOnlyAccessToken = accessToken.isAppOnlyAccessToken(auth.connection.accessTokens[auth.defaultResource].accessToken);
366
369
  if (isAppOnlyAccessToken) {
367
370
  throw `It's not possible to use ${value} with application permissions`;
368
371
  }
@@ -379,7 +382,7 @@ class Command {
379
382
  if (cli.currentCommandName &&
380
383
  cli.currentCommandName.indexOf(deprecated) === 0) {
381
384
  const chalk = (await import('chalk')).default;
382
- await logger.logToStderr(chalk.yellow(`Command '${deprecated}' is deprecated. Please use '${recommended}' instead`));
385
+ await logger.logToStderr(chalk.yellow(`Command '${deprecated}' is deprecated. Please use '${recommended}' instead.`));
383
386
  }
384
387
  }
385
388
  async warn(logger, warning) {
@@ -6,7 +6,10 @@ export class FileTokenStorage {
6
6
  return path.join(os.homedir(), '.cli-m365-msal.json');
7
7
  }
8
8
  static connectionInfoFilePath() {
9
- return path.join(os.homedir(), '.cli-m365-tokens.json');
9
+ return path.join(os.homedir(), '.cli-m365-connection.json');
10
+ }
11
+ static allConnectionsFilePath() {
12
+ return path.join(os.homedir(), '.cli-m365-all-connections.json');
10
13
  }
11
14
  constructor(filePath) {
12
15
  this.filePath = filePath;
package/dist/cli/cli.js CHANGED
@@ -98,7 +98,7 @@ async function execute(rawArgs) {
98
98
  if (parsedArgs.output !== 'none') {
99
99
  printHelp(await getHelpMode(parsedArgs));
100
100
  }
101
- return Promise.resolve();
101
+ return;
102
102
  }
103
103
  delete cli.optionsFromArgs.options._;
104
104
  delete cli.optionsFromArgs.options['--'];
@@ -39,7 +39,7 @@ class AppCommand extends Command {
39
39
  }
40
40
  if (!this.m365rcJson.apps ||
41
41
  this.m365rcJson.apps.length === 0) {
42
- throw new CommandError(`No Azure AD apps found in ${m365rcJsonPath}`);
42
+ throw new CommandError(`No Entra apps found in ${m365rcJsonPath}`);
43
43
  }
44
44
  if (args.options.appId) {
45
45
  if (!this.m365rcJson.apps.some(app => app.appId === args.options.appId)) {
@@ -54,7 +54,7 @@ class AppCommand extends Command {
54
54
  }
55
55
  if (this.m365rcJson.apps.length > 1) {
56
56
  const resultAsKeyValuePair = formatting.convertArrayToHashTable('appIdIndex', this.m365rcJson.apps);
57
- const result = await cli.handleMultipleResultsFound(`Multiple Azure AD apps found in ${m365rcJsonPath}.`, resultAsKeyValuePair);
57
+ const result = await cli.handleMultipleResultsFound(`Multiple Entra apps found in ${m365rcJsonPath}.`, resultAsKeyValuePair);
58
58
  this.appId = this.m365rcJson.apps[result.appIdIndex].appId;
59
59
  await super.action(logger, args);
60
60
  }
@@ -6,11 +6,11 @@ export default class PowerAppsCommand extends Command {
6
6
  }
7
7
  initAction(args, logger) {
8
8
  super.initAction(args, logger);
9
- if (!auth.service.connected) {
9
+ if (!auth.connection.active) {
10
10
  // we fail no login in the base command command class
11
11
  return;
12
12
  }
13
- if (auth.service.cloudType !== CloudType.Public) {
13
+ if (auth.connection.cloudType !== CloudType.Public) {
14
14
  throw new CommandError(`Power Apps commands only support the public cloud at the moment. We'll add support for other clouds in the future. Sorry for the inconvenience.`);
15
15
  }
16
16
  }
@@ -6,11 +6,11 @@ export default class PowerAutomateCommand extends Command {
6
6
  }
7
7
  initAction(args, logger) {
8
8
  super.initAction(args, logger);
9
- if (!auth.service.connected) {
9
+ if (!auth.connection.active) {
10
10
  // we fail no login in the base command command class
11
11
  return;
12
12
  }
13
- if (auth.service.cloudType !== CloudType.Public) {
13
+ if (auth.connection.cloudType !== CloudType.Public) {
14
14
  throw new CommandError(`Power Automate commands only support the public cloud at the moment. We'll add support for other clouds in the future. Sorry for the inconvenience.`);
15
15
  }
16
16
  }
@@ -6,11 +6,11 @@ export default class PowerPlatformCommand extends Command {
6
6
  }
7
7
  initAction(args, logger) {
8
8
  super.initAction(args, logger);
9
- if (!auth.service.connected) {
9
+ if (!auth.connection.active) {
10
10
  // we fail no login in the base command command class
11
11
  return;
12
12
  }
13
- if (auth.service.cloudType !== CloudType.Public) {
13
+ if (auth.connection.cloudType !== CloudType.Public) {
14
14
  throw new CommandError(`Power Platform commands only support the public cloud at the moment. We'll add support for other clouds in the future. Sorry for the inconvenience.`);
15
15
  }
16
16
  }