├── AccessPackages ├── LA-001-Create-TemporaryAccessPass.json └── Set-ManagedIdentityPermission.ps1 ├── AccessReviews └── Enable_Access_Reviews_On_Scale_M365_Groups.ps1 ├── AdminLifecycleManagement ├── Configure-MsGraphPermissions.ps1 ├── Configure-MsGraphPermissions_AdminAccount_Offboarding.ps1 ├── Configure-MsGraphPermissions_AdminAccount_PostOffboarding.ps1 ├── LA-099-Request-Admin-Account.json ├── LA-AdminAccount-Offboarding.json └── LA-AdminAccount-PostOffboarding.json ├── AzureAD-GroupWriteBackV2.ps1 ├── Configure-AuthenticationMethods.ps1 ├── Configure-AuthenticationMethods_including_token_request.ps1 ├── EntraPrivateAccess └── AppDiscoveryWorkbook.json ├── Guest-IdentityLifecycleManagement.ps1 ├── IdentityLifecycleManagement ├── RB-001-TransformHRUserInputforAPI.ps1 └── la-001-identity-provisioning-api.json ├── LifecycleWorkflows ├── RB-100-EnableAccountAndMbxInAD.ps1 ├── RB-400-ConvertMailboxEXO.ps1 ├── RB-400-DisableAccountAndConvertMbxInAD.ps1 └── RB-500-DeleteAccountInAD.ps1 ├── NordicVirtualSummit ├── LA-99-NordicsVirtualSummit ├── RB-99-NordicsVirtualSummit-Saml ├── RB-99-NordicsVirtualSummit-Saml.ps1 └── RB-99-NordicsVirtualSummit.ps1 ├── Pre-ConfigureAuthMethods V2 ├── AuthMethodsImport.csv └── Pre-ConfigureAuthMethods.ps1 └── WPNinjas-Demo-GuestLifeCycle /AccessPackages/LA-001-Create-TemporaryAccessPass.json: -------------------------------------------------------------------------------- 1 | { 2 | "definition": { 3 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 4 | "actions": { 5 | "Condition": { 6 | "actions": { 7 | "Condition_2": { 8 | "actions": {}, 9 | "expression": { 10 | "and": [ 11 | { 12 | "equals": [ 13 | "@triggerBody()?['Stage']", 14 | "CustomExtensionConnectionTest" 15 | ] 16 | } 17 | ] 18 | }, 19 | "runAfter": {}, 20 | "type": "If" 21 | } 22 | }, 23 | "expression": { 24 | "and": [ 25 | { 26 | "equals": [ 27 | "@{triggerBody()?['AccessPackageCatalog']?['Id']}", 28 | "3287d6ec-504d-4237-a0b5-20ff79d1767d" 29 | ] 30 | } 31 | ] 32 | }, 33 | "runAfter": { 34 | "Condition_-_One_Answer_need_to_be_'Yes'": [ 35 | "Succeeded" 36 | ] 37 | }, 38 | "type": "If" 39 | }, 40 | "Condition_-_One_Answer_need_to_be_'Yes'": { 41 | "actions": { 42 | "HTTP_-_Request_TAP_for_user": { 43 | "inputs": { 44 | "authentication": { 45 | "audience": "https://graph.microsoft.com", 46 | "type": "ManagedServiceIdentity" 47 | }, 48 | "body": { 49 | "isUsableOnce": false, 50 | "lifetimeInMinutes": 2870 51 | }, 52 | "headers": { 53 | "Content-Type": "application/json" 54 | }, 55 | "method": "POST", 56 | "uri": "https://graph.microsoft.com/beta/users/@{triggerBody()?['Assignment']?['Target']?['ObjectId']}/authentication/temporaryAccessPassMethods" 57 | }, 58 | "runAfter": { 59 | "Parse_JSON_-_Retrieve_user_details": [ 60 | "Succeeded" 61 | ] 62 | }, 63 | "type": "Http" 64 | }, 65 | "HTTP_-_Retrieve_User_Details": { 66 | "inputs": { 67 | "authentication": { 68 | "audience": "https://graph.microsoft.com", 69 | "type": "ManagedServiceIdentity" 70 | }, 71 | "method": "GET", 72 | "uri": "https://graph.microsoft.com/beta/users/@{triggerBody()?['Assignment']?['Target']?['ObjectId']}" 73 | }, 74 | "runAfter": {}, 75 | "type": "Http" 76 | }, 77 | "Parse_JSON_-_Request_TAP_for_user": { 78 | "inputs": { 79 | "content": "@body('HTTP_-_Request_TAP_for_user')", 80 | "schema": { 81 | "properties": { 82 | "@@odata.context": { 83 | "type": "string" 84 | }, 85 | "createdDateTime": { 86 | "type": "string" 87 | }, 88 | "id": { 89 | "type": "string" 90 | }, 91 | "isUsable": { 92 | "type": "boolean" 93 | }, 94 | "isUsableOnce": { 95 | "type": "boolean" 96 | }, 97 | "lifetimeInMinutes": { 98 | "type": "integer" 99 | }, 100 | "methodUsabilityReason": { 101 | "type": "string" 102 | }, 103 | "startDateTime": { 104 | "type": "string" 105 | }, 106 | "temporaryAccessPass": { 107 | "type": "string" 108 | } 109 | }, 110 | "type": "object" 111 | } 112 | }, 113 | "runAfter": { 114 | "HTTP_-_Request_TAP_for_user": [ 115 | "Succeeded" 116 | ] 117 | }, 118 | "type": "ParseJson" 119 | }, 120 | "Parse_JSON_-_Retrieve_user_details": { 121 | "inputs": { 122 | "content": "@body('HTTP_-_Retrieve_User_Details')", 123 | "schema": { 124 | "properties": { 125 | "@@odata.context": {}, 126 | "accountEnabled": {}, 127 | "ageGroup": {}, 128 | "assignedLicenses": {}, 129 | "assignedPlans": {}, 130 | "businessPhones": {}, 131 | "city": {}, 132 | "companyName": {}, 133 | "consentProvidedForMinor": {}, 134 | "country": {}, 135 | "createdDateTime": {}, 136 | "creationType": {}, 137 | "deletedDateTime": {}, 138 | "department": {}, 139 | "deviceKeys": {}, 140 | "displayName": {}, 141 | "employeeHireDate": {}, 142 | "employeeId": {}, 143 | "employeeLeaveDateTime": {}, 144 | "employeeOrgData": {}, 145 | "employeeType": {}, 146 | "externalUserConvertedOn": {}, 147 | "externalUserState": {}, 148 | "externalUserStateChangeDateTime": {}, 149 | "faxNumber": {}, 150 | "givenName": {}, 151 | "id": {}, 152 | "imAddresses": {}, 153 | "infoCatalogs": {}, 154 | "isLicenseReconciliationNeeded": {}, 155 | "isManagementRestricted": {}, 156 | "isResourceAccount": {}, 157 | "jobTitle": {}, 158 | "legalAgeGroupClassification": {}, 159 | "mail": {}, 160 | "mailNickname": {}, 161 | "mobilePhone": {}, 162 | "officeLocation": {}, 163 | "onPremisesDistinguishedName": {}, 164 | "onPremisesDomainName": {}, 165 | "onPremisesExtensionAttributes": { 166 | "properties": { 167 | "extensionAttribute1": {}, 168 | "extensionAttribute10": {}, 169 | "extensionAttribute11": {}, 170 | "extensionAttribute12": {}, 171 | "extensionAttribute13": {}, 172 | "extensionAttribute14": {}, 173 | "extensionAttribute15": {}, 174 | "extensionAttribute2": {}, 175 | "extensionAttribute3": {}, 176 | "extensionAttribute4": {}, 177 | "extensionAttribute5": {}, 178 | "extensionAttribute6": {}, 179 | "extensionAttribute7": {}, 180 | "extensionAttribute8": {}, 181 | "extensionAttribute9": {} 182 | }, 183 | "type": "object" 184 | }, 185 | "onPremisesImmutableId": {}, 186 | "onPremisesLastSyncDateTime": {}, 187 | "onPremisesObjectIdentifier": {}, 188 | "onPremisesProvisioningErrors": {}, 189 | "onPremisesSamAccountName": {}, 190 | "onPremisesSecurityIdentifier": {}, 191 | "onPremisesSyncEnabled": {}, 192 | "onPremisesUserPrincipalName": {}, 193 | "otherMails": {}, 194 | "passwordPolicies": {}, 195 | "postalCode": {}, 196 | "preferredDataLocation": {}, 197 | "preferredLanguage": {}, 198 | "provisionedPlans": {}, 199 | "proxyAddresses": {}, 200 | "refreshTokensValidFromDateTime": {}, 201 | "securityIdentifier": {}, 202 | "serviceProvisioningErrors": {}, 203 | "showInAddressList": {}, 204 | "signInSessionsValidFromDateTime": {}, 205 | "state": {}, 206 | "streetAddress": {}, 207 | "surname": {}, 208 | "usageLocation": {}, 209 | "userPrincipalName": {}, 210 | "userType": {} 211 | }, 212 | "type": "object" 213 | } 214 | }, 215 | "runAfter": { 216 | "HTTP_-_Retrieve_User_Details": [ 217 | "Succeeded" 218 | ] 219 | }, 220 | "type": "ParseJson" 221 | }, 222 | "Send_TAP_to_end_user": { 223 | "inputs": { 224 | "body": { 225 | "Body": "

Hi @{body('Parse_JSON_-_Retrieve_user_details')?['givenName']},
\n
\nYou recently requested a Temporary Access Pass via My Access, this request has now been approved!
\n
\nYour Temporary Access Pass is: @{body('Parse_JSON_-_Request_TAP_for_user')?['temporaryAccessPass']}
\n
\nYou can use the Temporary Access Pass to enroll for MFA or Passwordless Methods via My Security Info .
\n
\nPlease be aware that from the moment you received this email the Temporary Access Pass is valid for 48 hours.
\n
\nKind Regards,
\n
\nIT Team Identity Man

", 226 | "Importance": "Normal", 227 | "Subject": "Your Temporary Access Pass request is ready!", 228 | "To": "@{body('Parse_JSON_-_Retrieve_user_details')?['mail']}" 229 | }, 230 | "host": { 231 | "connection": { 232 | "name": "@parameters('$connections')['office365']['connectionId']" 233 | } 234 | }, 235 | "method": "post", 236 | "path": "/v2/Mail" 237 | }, 238 | "runAfter": { 239 | "Parse_JSON_-_Request_TAP_for_user": [ 240 | "Succeeded" 241 | ] 242 | }, 243 | "type": "ApiConnection" 244 | } 245 | }, 246 | "expression": { 247 | "or": [ 248 | { 249 | "equals": [ 250 | "@triggerBody()?['Answers'][1].value", 251 | "Yes" 252 | ] 253 | }, 254 | { 255 | "equals": [ 256 | "@triggerBody()?['Answers'][0].value", 257 | "Yes" 258 | ] 259 | } 260 | ] 261 | }, 262 | "runAfter": {}, 263 | "type": "If" 264 | } 265 | }, 266 | "contentVersion": "1.0.0.0", 267 | "outputs": {}, 268 | "parameters": { 269 | "$connections": { 270 | "defaultValue": {}, 271 | "type": "Object" 272 | } 273 | }, 274 | "triggers": { 275 | "manual": { 276 | "inputs": { 277 | "schema": { 278 | "properties": { 279 | "AccessPackage": { 280 | "properties": { 281 | "Description": { 282 | "description": "AccessPackage-Description", 283 | "type": "string" 284 | }, 285 | "DisplayName": { 286 | "description": "AccessPackage-DisplayName", 287 | "type": "string" 288 | }, 289 | "Id": { 290 | "description": "AccessPackage-Id", 291 | "type": "string" 292 | } 293 | }, 294 | "type": "object" 295 | }, 296 | "AccessPackageAssignmentRequestId": { 297 | "type": "string" 298 | }, 299 | "AccessPackageCatalog": { 300 | "properties": { 301 | "Description": { 302 | "description": "AccessPackageCatalog-Description", 303 | "type": "string" 304 | }, 305 | "DisplayName": { 306 | "description": "AccessPackageCatalog-DisplayName", 307 | "type": "string" 308 | }, 309 | "Id": { 310 | "description": "AccessPackageCatalog-Id", 311 | "type": "string" 312 | } 313 | }, 314 | "type": "object" 315 | }, 316 | "Answers": { 317 | "type": "array" 318 | }, 319 | "Assignment": { 320 | "properties": { 321 | "AssignmentPolicy": { 322 | "properties": { 323 | "DisplayName": { 324 | "description": "AssignmentPolicy-DisplayName", 325 | "type": "string" 326 | }, 327 | "Id": { 328 | "description": "AssignmentPolicy-Id", 329 | "type": "string" 330 | } 331 | }, 332 | "type": "object" 333 | }, 334 | "Id": { 335 | "description": "Assignment-Id", 336 | "type": "string" 337 | }, 338 | "State": { 339 | "description": "Assignment-State", 340 | "type": "string" 341 | }, 342 | "Status": { 343 | "description": "Assignment-Status", 344 | "type": "string" 345 | }, 346 | "Target": { 347 | "properties": { 348 | "ConnectedOrganization": { 349 | "properties": { 350 | "Description": { 351 | "description": "Assignment-Target-ConnectedOrganization-Description", 352 | "type": "string" 353 | }, 354 | "DisplayName": { 355 | "description": "Assignment-Target-ConnectedOrganization-DisplayName", 356 | "type": "string" 357 | }, 358 | "Id": { 359 | "description": "Assignment-Target-ConnectedOrganization-Id", 360 | "type": "string" 361 | } 362 | }, 363 | "type": "object" 364 | }, 365 | "DisplayName": { 366 | "description": "Assignment-Target-DisplayName", 367 | "type": "string" 368 | }, 369 | "Id": { 370 | "description": "Assignment-Target-Id", 371 | "type": "string" 372 | }, 373 | "ObjectId": { 374 | "description": "Assignment-Target-ObjectId", 375 | "type": "string" 376 | } 377 | }, 378 | "type": "object" 379 | } 380 | }, 381 | "type": "object" 382 | }, 383 | "CallbackConfiguration": { 384 | "properties": { 385 | "DurationBeforeTimeout": { 386 | "type": "string" 387 | } 388 | }, 389 | "type": "object" 390 | }, 391 | "CallbackUriPath": { 392 | "type": "string" 393 | }, 394 | "CustomExtensionStageInstanceId": { 395 | "type": "string" 396 | }, 397 | "RequestType": { 398 | "type": "string" 399 | }, 400 | "Requestor": { 401 | "properties": { 402 | "DisplayName": { 403 | "description": "Requestor-DisplayName", 404 | "type": "string" 405 | }, 406 | "Id": { 407 | "description": "Requestor-Id", 408 | "type": "string" 409 | }, 410 | "ObjectId": { 411 | "description": "Requestor-ObjectId", 412 | "type": "string" 413 | } 414 | }, 415 | "type": "object" 416 | }, 417 | "Stage": { 418 | "type": "string" 419 | }, 420 | "State": { 421 | "type": "string" 422 | }, 423 | "Status": { 424 | "type": "string" 425 | } 426 | }, 427 | "type": "object" 428 | } 429 | }, 430 | "kind": "Http", 431 | "operationOptions": "IncludeAuthorizationHeadersInOutputs", 432 | "type": "Request" 433 | } 434 | } 435 | }, 436 | "parameters": { 437 | "$connections": { 438 | "value": { 439 | "office365": { 440 | "connectionId": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/resourceGroups/RG-Generic/providers/Microsoft.Web/connections/office365", 441 | "connectionName": "office365", 442 | "id": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/providers/Microsoft.Web/locations/westeurope/managedApis/office365" 443 | } 444 | } 445 | } 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /AccessPackages/Set-ManagedIdentityPermission.ps1: -------------------------------------------------------------------------------- 1 | #Import Modules 2 | Import-Module Microsoft.Graph.Applications 3 | 4 | #Connect with the right permission scopes 5 | Connect-MgGraph -Scopes Application.Read.All, AppRoleAssignment.ReadWrite.All, RoleManagement.ReadWrite.Directory 6 | 7 | #Configure Variables 8 | $managedIdentityId = "ObjectID of ServicePrincipal" 9 | $roleName = "User.Read.All" 10 | 11 | #Retrieve additional Details 12 | $msgraph = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'" 13 | $role = $Msgraph.AppRoles| Where-Object {$_.Value -eq $roleName} 14 | 15 | #Assign application permissions to Managed Identity 16 | New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $managedIdentityId -PrincipalId $managedIdentityId -ResourceId $msgraph.Id -AppRoleId $role.Id 17 | -------------------------------------------------------------------------------- /AccessReviews/Enable_Access_Reviews_On_Scale_M365_Groups.ps1: -------------------------------------------------------------------------------- 1 | #Configure Managed Identity Variable for the use a managed identity (permissions required are AccessReview.ReadWrite.All & Group.Read.All 2 | $ManagedIdentity = $True 3 | If ($ManagedIdentity -ne $True) { 4 | #Configure tenant variables based on your own service pricipal (permissions required are AccessReview.ReadWrite.All & Group.Read.All 5 | $AppClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 6 | $TenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 7 | $ClientSecret = "xxxxxxxxxxxx" 8 | 9 | #Configure connection to Graph API and make sure to retrieve access token 10 | $RequestBody = @{client_id=$AppClientId;client_secret=$ClientSecret;grant_type="client_credentials";scope="https://graph.microsoft.com/.default";} 11 | $OAuthResponse = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token -Body $RequestBody 12 | $AccessToken = $OAuthResponse.access_token 13 | 14 | #Form request headers with the acquired $AccessToken 15 | $headers = @{'Content-Type'="application\json";'Authorization'="Bearer $AccessToken"} 16 | } 17 | else { 18 | $resourceURI = "https://graph.microsoft.com" 19 | $tokenAuthURI = $env:IDENTITY_ENDPOINT + "?resource=$resourceURI&api-version=2019-08-01" 20 | $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"X-IDENTITY-HEADER"="$env:IDENTITY_HEADER"} -Uri $tokenAuthURI 21 | $AccessToken = $tokenResponse.access_token 22 | 23 | #Form request headers with the acquired $AccessToken 24 | $headers = @{'Content-Type'="application\json";'Authorization'="Bearer $AccessToken"} 25 | } 26 | 27 | ################################################### 28 | 29 | Function Build-AccessReviewRequestBody($id,$displayname,$LabelResponse) { 30 | ## For Confidential only review guest users each quarter and give the review (owner) 14 days time to respond. 31 | ## If owner doesn't respond don't take action. 32 | if ($LabelResponse -eq "Confidential") { 33 | $accessReviewBody = @" 34 | { 35 | "displayName": "Confidential Guest Access to the group $displayname ", 36 | "descriptionForAdmins": "Access Review for guest access to the group $displayname ", 37 | "descriptionForReviewers": "Please review guest membership and access to the group $displayname as it's labeled as confidential.", 38 | "scope": { 39 | "query": "/v1.0/groups/$id/transitiveMembers/microsoft.graph.user/?`$count=true&`$filter=(userType eq 'Guest')", 40 | "queryType": "MicrosoftGraph" 41 | }, 42 | "instanceEnumerationScope": { 43 | "query": "/groups/$id", 44 | "queryType": "MicrosoftGraph" 45 | }, 46 | "reviewers": [ 47 | { 48 | "query": "/v1.0/groups/$id/owners", 49 | "queryType": "MicrosoftGraph" 50 | } 51 | ], 52 | "settings": { 53 | "mailNotificationsEnabled": true, 54 | "reminderNotificationsEnabled": true, 55 | "justificationRequiredOnApproval": true, 56 | "defaultDecisionEnabled": false, 57 | "defaultDecision": "None", 58 | "instanceDurationInDays": 21, 59 | "autoApplyDecisionsEnabled": true, 60 | "recommendationsEnabled": true, 61 | "recommendationLookBackDuration": "P30D", 62 | "decisionHistoriesForReviewersEnabled": false, 63 | "recurrence": { 64 | "pattern": { 65 | "type": "absoluteMonthly", 66 | "interval": 6, 67 | }, 68 | "range": { 69 | "type": "noEnd", 70 | "numberOfOccurrences": 0, 71 | "recurrenceTimeZone": null, 72 | "startDate": "2022-01-02", 73 | "endDate": "9999-12-31" 74 | } 75 | }, 76 | "applyActions": [ 77 | { 78 | "@odata.type": "#microsoft.graph.removeAccessApplyAction" 79 | } 80 | ], 81 | "recommendationInsightSettings": [ 82 | { 83 | "@odata.type": "#microsoft.graph.userLastSignInRecommendationInsightSetting", 84 | "recommendationLookBackDuration": "P30D", 85 | "signInScope": "tenant" 86 | } 87 | ] 88 | } 89 | } 90 | "@ 91 | } 92 | 93 | ## For Highly Confidential review all users in the M365 Group each quarter and give the review (owner) 14 days time to respond. 94 | ## If owner doesn't respond take recommended actions. 95 | if ($LabelResponse -eq "Highly Confidential") { 96 | $accessReviewBody = @" 97 | { 98 | "displayName": "Highly Confidential user Access to the group $displayname ", 99 | "descriptionForAdmins": "Access Review for user access to the group $displayname ", 100 | "descriptionForReviewers": "Please review user membership and access to the group $displayname as it's labeled as Highly Confidential.", 101 | "scope": { 102 | "query": "/groups/$id/transitiveMembers", 103 | "queryType": "MicrosoftGraph" 104 | }, 105 | "instanceEnumerationScope": { 106 | "query": "/groups/$id", 107 | "queryType": "MicrosoftGraph" 108 | }, 109 | "reviewers": [ 110 | { 111 | "query": "/v1.0/groups/$id/owners", 112 | "queryType": "MicrosoftGraph" 113 | } 114 | ], 115 | "settings": { 116 | "mailNotificationsEnabled": true, 117 | "reminderNotificationsEnabled": true, 118 | "justificationRequiredOnApproval": true, 119 | "defaultDecisionEnabled": true, 120 | "defaultDecision": "Recommendation", 121 | "instanceDurationInDays": 21, 122 | "autoApplyDecisionsEnabled": true, 123 | "recommendationsEnabled": true, 124 | "recommendationLookBackDuration": "P30D", 125 | "decisionHistoriesForReviewersEnabled": false, 126 | "recurrence": { 127 | "pattern": { 128 | "type": "absoluteMonthly", 129 | "interval": 3, 130 | }, 131 | "range": { 132 | "type": "noEnd", 133 | "numberOfOccurrences": 0, 134 | "recurrenceTimeZone": null, 135 | "startDate": "2022-01-02", 136 | "endDate": "9999-12-31" 137 | } 138 | }, 139 | "applyActions": [ 140 | { 141 | "@odata.type": "#microsoft.graph.removeAccessApplyAction" 142 | } 143 | ], 144 | "recommendationInsightSettings": [ 145 | { 146 | "@odata.type": "#microsoft.graph.userLastSignInRecommendationInsightSetting", 147 | "recommendationLookBackDuration": "P30D", 148 | "signInScope": "tenant" 149 | } 150 | ] 151 | } 152 | } 153 | "@ 154 | } 155 | 156 | return $accessReviewBody 157 | } 158 | 159 | ################################################### 160 | 161 | #Define Graph API Call for all Microsoft 365 Groups / Microsoft Teams Groups. 162 | $ApiGroupUrl = "https://graph.microsoft.com/v1.0/groups?`$filter=groupTypes/any(c:c+eq+'Unified')" 163 | 164 | #Perform pagination if next page link (odata.nextlink) returned. 165 | While ($ApiGroupUrl -ne $Null) { 166 | #Retrieve all groups. 167 | $GroupResponse = Invoke-WebRequest -Method GET -Uri $ApiGroupUrl -ContentType "application\json" -Headers $headers -UseBasicParsing | ConvertFrom-Json 168 | 169 | #If the variable $groupresponse contains a value continue. 170 | if($GroupResponse.value) { 171 | #Retrieve the value details 172 | $Groups = $GroupResponse.value 173 | 174 | #Define Graph API Call to retrieve all current access reviews. 175 | $ApiReviewsUrl = "https://graph.microsoft.com/beta/identityGovernance/accessReviews/definitions/" 176 | 177 | #Perform pagination if next page link (odata.nextlink) returned. 178 | While ($ApiReviewsUrl -ne $Null){ 179 | 180 | #retrieve all Access Reviews 181 | $AccessReviewsResponse = Invoke-WebRequest -Method GET -Uri $ApiReviewsUrl -ContentType "application\json" -Headers $headers -UseBasicParsing | ConvertFrom-Json 182 | 183 | #List all groups in Azure AD which do contain an Access Review and grab their ID. 184 | $GroupsWithAccessReviews = $AccessReviewsResponse.value.instanceEnumerationScope.query | where {$_ -like "*/groups/*"} | ForEach-Object { ($_ -split "/groups/")[1] } 185 | 186 | #for each group in $groups do the following 187 | ForEach($Group in $Groups) { 188 | 189 | #Grab ID and DisplayName and check if group already has an Access Review Applied. 190 | $id = $group.id 191 | $displayname = $group.displayName 192 | $AccessReviewApplied = $GroupsWithAccessReviews.Contains($id) 193 | 194 | #If the group doesn't have an access review yet the next section will be ran. 195 | if ($AccessReviewApplied -eq $false) { 196 | 197 | #Now let's retreive the label from the group (if any). 198 | $ApiLabelUrl = "https://graph.microsoft.com/beta/groups/{$id}?`$select=assignedLabels" 199 | $LabelResponse = Invoke-WebRequest -Method GET -Uri $ApiLabelUrl -ContentType "application\json" -Headers $headers -UseBasicParsing | ConvertFrom-Json 200 | $LabelResponse = $LabelResponse.assignedLabels.displayname 201 | 202 | #If the group does have a specific label applied the following section will be ran. 203 | If ($LabelResponse) { 204 | $AcceptLabelResponse = @("Confidential","Highly Confidential") 205 | If ($LabelResponse -in $AcceptLabelResponse) { 206 | #write output to the screen, build the access review based on the function and post it to the Graph API. 207 | write-output "Microsoft 365 Group '$displayname' current has Sensitivity Label '$labelresponse' assigned, creating Access Review type $labelresponse!" 208 | $accessReviewBody = Build-AccessReviewRequestBody -id $id -displayname $displayname -LabelResponse $labelresponse 209 | $accessReviewCreateUrl = "https://graph.microsoft.com/beta/identityGovernance/accessReviews/definitions/" 210 | $accessReviewResponse = Invoke-RestMethod -Method Post -Uri $accessReviewCreateUrl -Body $accessReviewBody -ContentType 'application/json' -Headers $headers -UseBasicParsing 211 | write-output "AR Applied for Microsoft 365 group: $displayname" 212 | } 213 | else { 214 | #write-output 215 | write-output "Group $displayname has Sensitivity Label 'Default' applied, skipping Access Review Creation." 216 | } 217 | } 218 | #If the group does not have a label applied the following section will be ran. 219 | Else { 220 | #write-output 221 | write-output "Group $displayname has no Sensitivity Label applied, skipping Access Review Creation." 222 | } 223 | } 224 | 225 | #If the group already has an access review the next section will be ran. 226 | else { 227 | #write-output 228 | write-output "Group $displayname already has an Access Review Applied, skipping Access Review Creation." 229 | } 230 | 231 | } 232 | $ApiReviewsUrl=$AccessReviewsResponse.'@odata.nextlink' 233 | } 234 | } 235 | $ApiGroupUrl=$GroupResponse.'@odata.nextlink' 236 | } 237 | -------------------------------------------------------------------------------- /AdminLifecycleManagement/Configure-MsGraphPermissions.ps1: -------------------------------------------------------------------------------- 1 | # Connect to Microsoft Graph with Global Administrator Permissions 2 | #Connect-MgGraph -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All,RoleManagement.ReadWrite.Directory" 3 | 4 | # You will be prompted for the Name of you Managed Identity 5 | $MdId_Name = Read-Host "Name of your Managed Identity" 6 | $MdId_ID = (Get-MgServicePrincipal -Filter "displayName eq '$MdId_Name'").id 7 | 8 | # Adding Microsoft Graph permissions 9 | $graphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'" 10 | 11 | # Add the required Graph scopes 12 | $graphScopes = @( 13 | "User.ReadWrite.All" 14 | "EntitlementManagement.ReadWrite.All" 15 | "Mail.Send" 16 | ) 17 | 18 | ForEach($scope in $graphScopes) { 19 | $appRole = $graphApp.AppRoles | Where-Object {$_.Value -eq $scope} 20 | # Check if permissions isn't already assigned 21 | $assignedAppRole = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $MdId_ID | Where-Object { $_.AppRoleId -eq $appRole.Id -and $_.ResourceDisplayName -eq "Microsoft Graph"} 22 | 23 | if ($null -eq $assignedAppRole) { 24 | New-MgServicePrincipalAppRoleAssignment -PrincipalId $MdId_ID -ServicePrincipalId $MdId_ID -ResourceId $graphApp.Id -AppRoleId $appRole.Id 25 | } 26 | Else { 27 | write-host "Scope $scope already assigned" 28 | } 29 | } 30 | 31 | #Add Office 365 Exchange Online Permissions for the App Registration 32 | $ExoApp = Get-MgServicePrincipal -Filter "AppId eq '00000002-0000-0ff1-ce00-000000000000'" 33 | $AppPermission = $ExoApp.AppRoles | Where-Object {$_.DisplayName -eq "Manage Exchange As Application"} 34 | 35 | $AppRoleAssignment = @{ 36 | "PrincipalId" = $MdId_ID 37 | "ResourceId" = $ExoApp.Id 38 | "AppRoleId" = $AppPermission.Id 39 | } 40 | 41 | New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $MdId_ID -BodyParameter $AppRoleAssignment 42 | -------------------------------------------------------------------------------- /AdminLifecycleManagement/Configure-MsGraphPermissions_AdminAccount_Offboarding.ps1: -------------------------------------------------------------------------------- 1 | # Connect to Microsoft Graph with Global Administrator Permissions 2 | Connect-MgGraph -Scopes "Application.Read.All,AppRoleAssignment.ReadWrite.All,RoleManagement.ReadWrite.Directory" 3 | 4 | # You will be prompted for the Name of you Managed Identity 5 | $MdId_Name = Read-Host "Name of your Managed Identity" 6 | $MdId_ID = (Get-MgServicePrincipal -Filter "displayName eq '$MdId_Name'").id 7 | 8 | # Adding Microsoft Graph permissions 9 | $graphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'" 10 | 11 | # Add the required Graph scopes 12 | $graphScopes = @( 13 | "User.EnableDisableAccount.All" 14 | ) 15 | 16 | ForEach($scope in $graphScopes) { 17 | $appRole = $graphApp.AppRoles | Where-Object {$_.Value -eq $scope} 18 | # Check if permissions isn't already assigned 19 | $assignedAppRole = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $MdId_ID | Where-Object { $_.AppRoleId -eq $appRole.Id -and $_.ResourceDisplayName -eq "Microsoft Graph"} 20 | 21 | if ($null -eq $assignedAppRole) { 22 | New-MgServicePrincipalAppRoleAssignment -PrincipalId $MdId_ID -ServicePrincipalId $MdId_ID -ResourceId $graphApp.Id -AppRoleId $appRole.Id 23 | } 24 | Else { 25 | write-host "Scope $scope already assigned" 26 | } 27 | } -------------------------------------------------------------------------------- /AdminLifecycleManagement/Configure-MsGraphPermissions_AdminAccount_PostOffboarding.ps1: -------------------------------------------------------------------------------- 1 | # Connect to Microsoft Graph with Global Administrator Permissions 2 | Connect-MgGraph -Scopes "Application.Read.All,AppRoleAssignment.ReadWrite.All,RoleManagement.ReadWrite.Directory" 3 | 4 | # You will be prompted for the Name of you Managed Identity 5 | $MdId_Name = Read-Host "Name of your Managed Identity" 6 | $MdId_ID = (Get-MgServicePrincipal -Filter "displayName eq '$MdId_Name'").id 7 | 8 | # Adding Microsoft Graph permissions 9 | $graphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'" 10 | 11 | # Add the required Graph scopes 12 | $graphScopes = @( 13 | "User.ReadWrite.All" 14 | ) 15 | 16 | ForEach($scope in $graphScopes) { 17 | $appRole = $graphApp.AppRoles | Where-Object {$_.Value -eq $scope} 18 | # Check if permissions isn't already assigned 19 | $assignedAppRole = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $MdId_ID | Where-Object { $_.AppRoleId -eq $appRole.Id -and $_.ResourceDisplayName -eq "Microsoft Graph"} 20 | 21 | if ($null -eq $assignedAppRole) { 22 | New-MgServicePrincipalAppRoleAssignment -PrincipalId $MdId_ID -ServicePrincipalId $MdId_ID -ResourceId $graphApp.Id -AppRoleId $appRole.Id 23 | } 24 | Else { 25 | write-host "Scope $scope already assigned" 26 | } 27 | } -------------------------------------------------------------------------------- /AdminLifecycleManagement/LA-099-Request-Admin-Account.json: -------------------------------------------------------------------------------- 1 | { 2 | "definition": { 3 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 4 | "actions": { 5 | "Condition": { 6 | "actions": { 7 | "Condition_2": { 8 | "actions": { 9 | "Compose_-_UserPrincipalName": { 10 | "inputs": "adm.@{replace(body('Parse_JSON_-_Get_all_user_details')?['userPrincipalName'],'@identity-man.eu','@jacobsaa.onmicrosoft.com')}", 11 | "runAfter": { 12 | "Parse_JSON_-_Get_all_user_details": [ 13 | "Succeeded" 14 | ] 15 | }, 16 | "type": "Compose" 17 | }, 18 | "Delay_-_20_seconds": { 19 | "inputs": { 20 | "interval": { 21 | "count": 20, 22 | "unit": "Second" 23 | } 24 | }, 25 | "runAfter": { 26 | "HTTP_-_Create_admin_account": [ 27 | "Succeeded" 28 | ] 29 | }, 30 | "type": "Wait" 31 | }, 32 | "HTTP_-_Create_admin_account": { 33 | "inputs": { 34 | "authentication": { 35 | "audience": "https://graph.microsoft.com", 36 | "type": "ManagedServiceIdentity" 37 | }, 38 | "body": "@variables('RequestBody')", 39 | "headers": { 40 | "Content-type": "application/json" 41 | }, 42 | "method": "POST", 43 | "uri": "https://graph.microsoft.com/beta/users" 44 | }, 45 | "runAfter": { 46 | "Set_variable_-_Request_Body": [ 47 | "Succeeded" 48 | ] 49 | }, 50 | "runtimeConfiguration": { 51 | "contentTransfer": { 52 | "transferMode": "Chunked" 53 | } 54 | }, 55 | "type": "Http" 56 | }, 57 | "HTTP_-_Generate_TAP_for_admin": { 58 | "inputs": { 59 | "authentication": { 60 | "audience": "https://graph.microsoft.com", 61 | "type": "ManagedServiceIdentity" 62 | }, 63 | "body": { 64 | "isUsableOnce": true, 65 | "lifetimeInMinutes": 1440 66 | }, 67 | "headers": { 68 | "Content-Type": "application/json" 69 | }, 70 | "method": "POST", 71 | "uri": "https://graph.microsoft.com/beta/users/@{outputs('Compose_-_UserPrincipalName')}/authentication/temporaryAccessPassMethods" 72 | }, 73 | "runAfter": { 74 | "Delay_-_20_seconds": [ 75 | "Succeeded" 76 | ] 77 | }, 78 | "runtimeConfiguration": { 79 | "contentTransfer": { 80 | "transferMode": "Chunked" 81 | } 82 | }, 83 | "type": "Http" 84 | }, 85 | "HTTP_-_Get_all_user_details": { 86 | "inputs": { 87 | "authentication": { 88 | "audience": "https://graph.microsoft.com", 89 | "type": "ManagedServiceIdentity" 90 | }, 91 | "method": "GET", 92 | "uri": "https://graph.microsoft.com/beta/users/@{triggerBody()?['Requestor']?['ObjectId']}?$select=id,displayName,givenName,surname,accountEnabled,userPrincipalName,mailNickname,employeeId" 93 | }, 94 | "runtimeConfiguration": { 95 | "contentTransfer": { 96 | "transferMode": "Chunked" 97 | } 98 | }, 99 | "type": "Http" 100 | }, 101 | "HTTP_-_Send_an_email": { 102 | "inputs": { 103 | "authentication": { 104 | "audience": "https://graph.microsoft.com", 105 | "type": "ManagedServiceIdentity" 106 | }, 107 | "body": { 108 | "message": { 109 | "body": { 110 | "content": "

Hi @{body('Parse_JSON_-_Get_all_user_details')?['givenName']},

Your request for an admin account has been approved.
You can sign-in with your admin account by using the following details and instruction:

Username: @{outputs('Compose_-_UserPrincipalName')}
Temporary Access Pass: @{body('Parse_JSON_-_Generate_TAP_for_admin')?['temporaryAccessPass']}

You can now go to MySecurityInfo to use your temporary acccess pass to register for a passwordless method (preferably a Security Key or a Passkey), be aware that we don't use passwords so there is no need anymore to know or reset your password.

At last, please don't share these account details with anyone else but you.

Kind Regards,

IdentityMan Helpdesk

", 111 | "contentType": "HTML" 112 | }, 113 | "importance": "High", 114 | "subject": "Your IdentityMan Admin account has been created", 115 | "toRecipients": [ 116 | { 117 | "emailAddress": { 118 | "address": "@{body('Parse_JSON_-_Get_all_user_details')?['userPrincipalName']}" 119 | } 120 | } 121 | ] 122 | } 123 | }, 124 | "method": "POST", 125 | "uri": "https://graph.microsoft.com/v1.0/users/77ca8b96-893b-4f5b-a3f0-369beeaedf5a/sendMail" 126 | }, 127 | "runAfter": { 128 | "Parse_JSON_-_Generate_TAP_for_admin": [ 129 | "Succeeded" 130 | ] 131 | }, 132 | "runtimeConfiguration": { 133 | "contentTransfer": { 134 | "transferMode": "Chunked" 135 | } 136 | }, 137 | "type": "Http" 138 | }, 139 | "Parse_JSON_-_Generate_TAP_for_admin": { 140 | "inputs": { 141 | "content": "@body('HTTP_-_Generate_TAP_for_admin')", 142 | "schema": { 143 | "properties": { 144 | "@@odata.context": { 145 | "type": "string" 146 | }, 147 | "createdDateTime": { 148 | "type": "string" 149 | }, 150 | "id": { 151 | "type": "string" 152 | }, 153 | "isUsable": { 154 | "type": "boolean" 155 | }, 156 | "isUsableOnce": { 157 | "type": "boolean" 158 | }, 159 | "lifetimeInMinutes": { 160 | "type": "integer" 161 | }, 162 | "methodUsabilityReason": { 163 | "type": "string" 164 | }, 165 | "startDateTime": { 166 | "type": "string" 167 | }, 168 | "temporaryAccessPass": { 169 | "type": "string" 170 | } 171 | }, 172 | "type": "object" 173 | } 174 | }, 175 | "runAfter": { 176 | "HTTP_-_Generate_TAP_for_admin": [ 177 | "Succeeded" 178 | ] 179 | }, 180 | "type": "ParseJson" 181 | }, 182 | "Parse_JSON_-_Get_all_user_details": { 183 | "inputs": { 184 | "content": "@body('HTTP_-_Get_all_user_details')", 185 | "schema": { 186 | "properties": { 187 | "@@odata.context": { 188 | "type": "string" 189 | }, 190 | "accountEnabled": { 191 | "type": "boolean" 192 | }, 193 | "displayName": { 194 | "type": "string" 195 | }, 196 | "employeeId": {}, 197 | "givenName": { 198 | "type": "string" 199 | }, 200 | "id": { 201 | "type": "string" 202 | }, 203 | "mailNickname": { 204 | "type": "string" 205 | }, 206 | "surname": { 207 | "type": "string" 208 | }, 209 | "userPrincipalName": { 210 | "type": "string" 211 | } 212 | }, 213 | "type": "object" 214 | } 215 | }, 216 | "runAfter": { 217 | "HTTP_-_Get_all_user_details": [ 218 | "Succeeded" 219 | ] 220 | }, 221 | "type": "ParseJson" 222 | }, 223 | "Set_variable_-_Request_Body": { 224 | "inputs": { 225 | "name": "RequestBody", 226 | "value": "{\n \"displayName\": \"@{body('Parse_JSON_-_Get_all_user_details')?['displayName']} - Admin\",\n \"givenName\": \"@{body('Parse_JSON_-_Get_all_user_details')?['givenName']}\",\n \"surname\": \"@{body('Parse_JSON_-_Get_all_user_details')?['surname']}\",\n \"accountEnabled\": \"@{body('Parse_JSON_-_Get_all_user_details')?['accountEnabled']}\",\n \"userPrincipalName\": \"@{outputs('Compose_-_UserPrincipalName')}\",\n \"mailNickname\": \"@{body('Parse_JSON_-_Get_all_user_details')?['mailNickname']}\",\n \"employeeId\": \"A@{body('Parse_JSON_-_Get_all_user_details')?['employeeId']}\",\n \"passwordProfile\" : {\n \"forceChangePasswordNextSignIn\": false,\n \"password\": \"@{variables('GeneratedPassword')}\"\n }\n}" 227 | }, 228 | "runAfter": { 229 | "Compose_-_UserPrincipalName": [ 230 | "Succeeded" 231 | ] 232 | }, 233 | "type": "SetVariable" 234 | } 235 | }, 236 | "else": { 237 | "actions": {} 238 | }, 239 | "expression": { 240 | "and": [ 241 | { 242 | "equals": [ 243 | "@triggerBody()?['Stage']", 244 | "assignmentRequestGranted" 245 | ] 246 | } 247 | ] 248 | }, 249 | "type": "If" 250 | } 251 | }, 252 | "else": { 253 | "actions": {} 254 | }, 255 | "expression": { 256 | "and": [ 257 | { 258 | "equals": [ 259 | "@triggerBody()?['AccessPackageCatalog']?['Id']", 260 | "a6461768-60ea-4a25-9863-6ec4cba55e93" 261 | ] 262 | } 263 | ] 264 | }, 265 | "runAfter": { 266 | "Initialize_variable_-_GeneratedPassword": [ 267 | "Succeeded" 268 | ] 269 | }, 270 | "type": "If" 271 | }, 272 | "HTTP_-_Resume_an_access_package_assignment_request": { 273 | "inputs": { 274 | "authentication": { 275 | "audience": "https://graph.microsoft.com", 276 | "type": "ManagedServiceIdentity" 277 | }, 278 | "body": { 279 | "data": { 280 | "@@odata.type": "microsoft.graph.accessPackageAssignmentRequestCallbackData", 281 | "customExtensionStageInstanceDetail": "Admin account has been created", 282 | "customExtensionStageInstanceId": "@{triggerBody()?['CustomExtensionStageInstanceId']}", 283 | "stage": "assignmentRequestGranted" 284 | }, 285 | "source": "IdentityMan.AdminAccountRequest", 286 | "type": "microsoft.graph.accessPackageCustomExtensionStage.assignmentRequestGranted" 287 | }, 288 | "headers": { 289 | "Content-Type": "application/json" 290 | }, 291 | "method": "POST", 292 | "uri": "https://graph.microsoft.com/v1.0/identityGovernance/entitlementManagement/assignmentRequests/@{triggerBody()?['AccessPackageAssignmentRequestId']}/resume" 293 | }, 294 | "operationOptions": "DisableAsyncPattern", 295 | "runAfter": { 296 | "Condition": [ 297 | "Succeeded" 298 | ] 299 | }, 300 | "runtimeConfiguration": { 301 | "contentTransfer": { 302 | "transferMode": "Chunked" 303 | } 304 | }, 305 | "type": "Http" 306 | }, 307 | "HTTP_-_Resume_and_deny_an_access_package_assignment_request": { 308 | "inputs": { 309 | "authentication": { 310 | "audience": "https://graph.microsoft.com", 311 | "type": "ManagedServiceIdentity" 312 | }, 313 | "body": { 314 | "data": { 315 | "@@odata.type": "microsoft.graph.accessPackageAssignmentRequestCallbackData", 316 | "customExtensionStageInstanceDetail": "Admin account creation failed", 317 | "customExtensionStageInstanceId": "", 318 | "stage": "assignmentRequestGranted" 319 | }, 320 | "source": "IdentityMan.AdminAccountRequest", 321 | "type": "microsoft.graph.accessPackageCustomExtensionStage.assignmentRequestGranted" 322 | }, 323 | "headers": { 324 | "Content-Type": "application/json" 325 | }, 326 | "method": "POST", 327 | "uri": "https://graph.microsoft.com/v1.0/identityGovernance/entitlementManagement/assignmentRequests/@{triggerBody()?['AccessPackageAssignmentRequestId']}/resume" 328 | }, 329 | "operationOptions": "DisableAsyncPattern", 330 | "runAfter": { 331 | "Condition": [ 332 | "TimedOut", 333 | "Skipped", 334 | "Failed" 335 | ] 336 | }, 337 | "runtimeConfiguration": { 338 | "contentTransfer": { 339 | "transferMode": "Chunked" 340 | } 341 | }, 342 | "type": "Http" 343 | }, 344 | "Initialize_variable_-_GeneratedPassword": { 345 | "inputs": { 346 | "variables": [ 347 | { 348 | "name": "GeneratedPassword", 349 | "type": "string", 350 | "value": "@{guid()}" 351 | } 352 | ] 353 | }, 354 | "runAfter": { 355 | "Initialize_variable_-_Request_Body": [ 356 | "Succeeded" 357 | ] 358 | }, 359 | "type": "InitializeVariable" 360 | }, 361 | "Initialize_variable_-_Request_Body": { 362 | "inputs": { 363 | "variables": [ 364 | { 365 | "name": "RequestBody", 366 | "type": "string" 367 | } 368 | ] 369 | }, 370 | "runAfter": {}, 371 | "type": "InitializeVariable" 372 | } 373 | }, 374 | "contentVersion": "1.0.0.0", 375 | "outputs": {}, 376 | "parameters": { 377 | "$connections": { 378 | "defaultValue": {}, 379 | "type": "Object" 380 | } 381 | }, 382 | "triggers": { 383 | "manual": { 384 | "inputs": { 385 | "schema": { 386 | "properties": { 387 | "AccessPackage": { 388 | "properties": { 389 | "Description": { 390 | "description": "AccessPackage-Description", 391 | "type": "string" 392 | }, 393 | "DisplayName": { 394 | "description": "AccessPackage-DisplayName", 395 | "type": "string" 396 | }, 397 | "Id": { 398 | "description": "AccessPackage-Id", 399 | "type": "string" 400 | } 401 | }, 402 | "type": "object" 403 | }, 404 | "AccessPackageAssignmentRequestId": { 405 | "type": "string" 406 | }, 407 | "AccessPackageCatalog": { 408 | "properties": { 409 | "Description": { 410 | "description": "AccessPackageCatalog-Description", 411 | "type": "string" 412 | }, 413 | "DisplayName": { 414 | "description": "AccessPackageCatalog-DisplayName", 415 | "type": "string" 416 | }, 417 | "Id": { 418 | "description": "AccessPackageCatalog-Id", 419 | "type": "string" 420 | } 421 | }, 422 | "type": "object" 423 | }, 424 | "Answers": { 425 | "type": "array" 426 | }, 427 | "Assignment": { 428 | "properties": { 429 | "AssignmentPolicy": { 430 | "properties": { 431 | "DisplayName": { 432 | "description": "AssignmentPolicy-DisplayName", 433 | "type": "string" 434 | }, 435 | "Id": { 436 | "description": "AssignmentPolicy-Id", 437 | "type": "string" 438 | } 439 | }, 440 | "type": "object" 441 | }, 442 | "Id": { 443 | "description": "Assignment-Id", 444 | "type": "string" 445 | }, 446 | "State": { 447 | "description": "Assignment-State", 448 | "type": "string" 449 | }, 450 | "Status": { 451 | "description": "Assignment-Status", 452 | "type": "string" 453 | }, 454 | "Target": { 455 | "properties": { 456 | "ConnectedOrganization": { 457 | "properties": { 458 | "Description": { 459 | "description": "Assignment-Target-ConnectedOrganization-Description", 460 | "type": "string" 461 | }, 462 | "DisplayName": { 463 | "description": "Assignment-Target-ConnectedOrganization-DisplayName", 464 | "type": "string" 465 | }, 466 | "Id": { 467 | "description": "Assignment-Target-ConnectedOrganization-Id", 468 | "type": "string" 469 | } 470 | }, 471 | "type": "object" 472 | }, 473 | "DisplayName": { 474 | "description": "Assignment-Target-DisplayName", 475 | "type": "string" 476 | }, 477 | "Id": { 478 | "description": "Assignment-Target-Id", 479 | "type": "string" 480 | }, 481 | "ObjectId": { 482 | "description": "Assignment-Target-ObjectId", 483 | "type": "string" 484 | } 485 | }, 486 | "type": "object" 487 | } 488 | }, 489 | "type": "object" 490 | }, 491 | "CallbackConfiguration": { 492 | "properties": { 493 | "DurationBeforeTimeout": { 494 | "type": "string" 495 | } 496 | }, 497 | "type": "object" 498 | }, 499 | "CallbackUriPath": { 500 | "type": "string" 501 | }, 502 | "CustomExtensionStageInstanceId": { 503 | "type": "string" 504 | }, 505 | "RequestType": { 506 | "type": "string" 507 | }, 508 | "Requestor": { 509 | "properties": { 510 | "DisplayName": { 511 | "description": "Requestor-DisplayName", 512 | "type": "string" 513 | }, 514 | "Id": { 515 | "description": "Requestor-Id", 516 | "type": "string" 517 | }, 518 | "ObjectId": { 519 | "description": "Requestor-ObjectId", 520 | "type": "string" 521 | } 522 | }, 523 | "type": "object" 524 | }, 525 | "Stage": { 526 | "type": "string" 527 | }, 528 | "State": { 529 | "type": "string" 530 | }, 531 | "Status": { 532 | "type": "string" 533 | } 534 | }, 535 | "type": "object" 536 | } 537 | }, 538 | "kind": "Http", 539 | "operationOptions": "IncludeAuthorizationHeadersInOutputs", 540 | "type": "Request" 541 | } 542 | } 543 | }, 544 | "parameters": { 545 | "$connections": { 546 | "value": {} 547 | } 548 | } 549 | } 550 | -------------------------------------------------------------------------------- /AdminLifecycleManagement/LA-AdminAccount-Offboarding.json: -------------------------------------------------------------------------------- 1 | { 2 | "definition": { 3 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 4 | "contentVersion": "1.0.0.0", 5 | "triggers": { 6 | "manual": { 7 | "type": "Request", 8 | "kind": "Http", 9 | "inputs": { 10 | "schema": { 11 | "properties": { 12 | "data": { 13 | "properties": { 14 | "callbackUriPath": { 15 | "description": "CallbackUriPath used for Resume Action", 16 | "title": "Data.CallbackUriPath", 17 | "type": "string" 18 | }, 19 | "subject": { 20 | "properties": { 21 | "displayName": { 22 | "description": "DisplayName of the Subject", 23 | "title": "Subject.DisplayName", 24 | "type": "string" 25 | }, 26 | "email": { 27 | "description": "Email of the Subject", 28 | "title": "Subject.Email", 29 | "type": "string" 30 | }, 31 | "id": { 32 | "description": "Id of the Subject", 33 | "title": "Subject.Id", 34 | "type": "string" 35 | }, 36 | "manager": { 37 | "properties": { 38 | "displayName": { 39 | "description": "DisplayName parameter for Manager", 40 | "title": "Manager.DisplayName", 41 | "type": "string" 42 | }, 43 | "email": { 44 | "description": "Mail parameter for Manager", 45 | "title": "Manager.Mail", 46 | "type": "string" 47 | }, 48 | "id": { 49 | "description": "Id parameter for Manager", 50 | "title": "Manager.Id", 51 | "type": "string" 52 | } 53 | }, 54 | "type": "object" 55 | }, 56 | "userPrincipalName": { 57 | "description": "UserPrincipalName of the Subject", 58 | "title": "Subject.UserPrincipalName", 59 | "type": "string" 60 | } 61 | }, 62 | "type": "object" 63 | }, 64 | "task": { 65 | "properties": { 66 | "displayName": { 67 | "description": "DisplayName for Task Object", 68 | "title": "Task.DisplayName", 69 | "type": "string" 70 | }, 71 | "id": { 72 | "description": "Id for Task Object", 73 | "title": "Task.Id", 74 | "type": "string" 75 | } 76 | }, 77 | "type": "object" 78 | }, 79 | "taskProcessingResult": { 80 | "properties": { 81 | "createdDateTime": { 82 | "description": "CreatedDateTime for TaskProcessingResult Object", 83 | "title": "TaskProcessingResult.CreatedDateTime", 84 | "type": "string" 85 | }, 86 | "id": { 87 | "description": "Id for TaskProcessingResult Object", 88 | "title": "TaskProcessingResult.Id", 89 | "type": "string" 90 | } 91 | }, 92 | "type": "object" 93 | }, 94 | "workflow": { 95 | "properties": { 96 | "displayName": { 97 | "description": "DisplayName for Workflow Object", 98 | "title": "Workflow.DisplayName", 99 | "type": "string" 100 | }, 101 | "id": { 102 | "description": "Id for Workflow Object", 103 | "title": "Workflow.Id", 104 | "type": "string" 105 | }, 106 | "workflowVerson": { 107 | "description": "WorkflowVersion for Workflow Object", 108 | "title": "Workflow.WorkflowVersion", 109 | "type": "integer" 110 | } 111 | }, 112 | "type": "object" 113 | } 114 | }, 115 | "type": "object" 116 | }, 117 | "source": { 118 | "description": "Context in which an event happened", 119 | "title": "Request.Source", 120 | "type": "string" 121 | }, 122 | "type": { 123 | "description": "Value describing the type of event related to the originating occurrence.", 124 | "title": "Request.Type", 125 | "type": "string" 126 | } 127 | }, 128 | "type": "object" 129 | } 130 | } 131 | } 132 | }, 133 | "actions": { 134 | "HTTP_-_Callback": { 135 | "type": "Http", 136 | "inputs": { 137 | "uri": "https://graph.microsoft.com/beta@{triggerBody()?['data']?['callbackUriPath']}", 138 | "method": "POST", 139 | "body": { 140 | "data": { 141 | "operationStatus": "Completed" 142 | }, 143 | "source": "sample", 144 | "type": "lifecycleEvent" 145 | }, 146 | "authentication": { 147 | "audience": "https://graph.microsoft.com", 148 | "type": "ManagedServiceIdentity" 149 | } 150 | }, 151 | "runAfter": { 152 | "For_each_-_Admin_Account_found": [ 153 | "Succeeded" 154 | ] 155 | } 156 | }, 157 | "HTTP_-_Get_account_details_from_MS_Graph": { 158 | "type": "Http", 159 | "inputs": { 160 | "uri": "https://graph.microsoft.com/beta/users/@{triggerBody()?['data']?['subject']?['id']}?$select=id,employeeId,userPrincipalName,accountEnabled", 161 | "method": "GET", 162 | "authentication": { 163 | "type": "ManagedServiceIdentity", 164 | "audience": "https://graph.microsoft.com" 165 | } 166 | }, 167 | "runAfter": {}, 168 | "runtimeConfiguration": { 169 | "contentTransfer": { 170 | "transferMode": "Chunked" 171 | } 172 | } 173 | }, 174 | "Parse_JSON_-_Get_account_details_from_MS_Graph": { 175 | "type": "ParseJson", 176 | "inputs": { 177 | "content": "@body('HTTP_-_Get_account_details_from_MS_Graph')", 178 | "schema": { 179 | "type": "object", 180 | "properties": { 181 | "@@odata.context": { 182 | "type": "string" 183 | }, 184 | "id": { 185 | "type": "string" 186 | }, 187 | "employeeId": { 188 | "type": "string" 189 | }, 190 | "userPrincipalName": { 191 | "type": "string" 192 | }, 193 | "accountEnabled": { 194 | "type": "boolean" 195 | } 196 | } 197 | } 198 | }, 199 | "runAfter": { 200 | "HTTP_-_Get_account_details_from_MS_Graph": [ 201 | "Succeeded" 202 | ] 203 | } 204 | }, 205 | "HTTP_-_Get_admin_details_from_MS_Graph": { 206 | "type": "Http", 207 | "inputs": { 208 | "uri": "https://graph.microsoft.com/v1.0/users?$filter=startswith(employeeId,'A@{body('Parse_JSON_-_Get_account_details_from_MS_Graph')?['employeeId']}')&$count=true&$select=id,employeeId,userPrincipalName,accountEnabled", 209 | "method": "GET", 210 | "headers": { 211 | "ConsistencyLevel\n": "eventual\n" 212 | }, 213 | "authentication": { 214 | "type": "ManagedServiceIdentity", 215 | "audience": "https://graph.microsoft.com" 216 | } 217 | }, 218 | "runAfter": { 219 | "Parse_JSON_-_Get_account_details_from_MS_Graph": [ 220 | "Succeeded" 221 | ] 222 | }, 223 | "runtimeConfiguration": { 224 | "contentTransfer": { 225 | "transferMode": "Chunked" 226 | } 227 | } 228 | }, 229 | "Parse_JSON_-_Get_admin_account_details_from_MS_Graph": { 230 | "type": "ParseJson", 231 | "inputs": { 232 | "content": "@body('HTTP_-_Get_admin_details_from_MS_Graph')", 233 | "schema": { 234 | "type": "object", 235 | "properties": { 236 | "@@odata.context": { 237 | "type": "string" 238 | }, 239 | "value": { 240 | "type": "array", 241 | "items": { 242 | "type": "object", 243 | "properties": { 244 | "id": { 245 | "type": "string" 246 | }, 247 | "employeeId": { 248 | "type": "string" 249 | }, 250 | "userPrincipalName": { 251 | "type": "string" 252 | }, 253 | "accountEnabled": { 254 | "type": "boolean" 255 | } 256 | }, 257 | "required": [ 258 | "id", 259 | "employeeId", 260 | "userPrincipalName", 261 | "accountEnabled" 262 | ] 263 | } 264 | } 265 | } 266 | } 267 | }, 268 | "runAfter": { 269 | "HTTP_-_Get_admin_details_from_MS_Graph": [ 270 | "Succeeded" 271 | ] 272 | } 273 | }, 274 | "For_each_-_Admin_Account_found": { 275 | "type": "Foreach", 276 | "foreach": "@outputs('Parse_JSON_-_Get_admin_account_details_from_MS_Graph')?['body']?['value']", 277 | "actions": { 278 | "HTTP_-_Disable_admin_account": { 279 | "type": "Http", 280 | "inputs": { 281 | "uri": "https://graph.microsoft.com/v1.0/users/@{item()?['userPrincipalName']}", 282 | "method": "PATCH", 283 | "body": { 284 | "accountEnabled": false 285 | }, 286 | "authentication": { 287 | "type": "ManagedServiceIdentity", 288 | "audience": "https://graph.microsoft.com" 289 | } 290 | }, 291 | "runtimeConfiguration": { 292 | "contentTransfer": { 293 | "transferMode": "Chunked" 294 | } 295 | } 296 | } 297 | }, 298 | "runAfter": { 299 | "Parse_JSON_-_Get_admin_account_details_from_MS_Graph": [ 300 | "Succeeded" 301 | ] 302 | } 303 | } 304 | }, 305 | "outputs": {}, 306 | "parameters": { 307 | "$connections": { 308 | "type": "Object", 309 | "defaultValue": {} 310 | } 311 | } 312 | }, 313 | "parameters": { 314 | "$connections": { 315 | "type": "Object", 316 | "value": {} 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /AdminLifecycleManagement/LA-AdminAccount-PostOffboarding.json: -------------------------------------------------------------------------------- 1 | { 2 | "definition": { 3 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 4 | "contentVersion": "1.0.0.0", 5 | "triggers": { 6 | "manual": { 7 | "type": "Request", 8 | "kind": "Http", 9 | "inputs": { 10 | "schema": { 11 | "properties": { 12 | "data": { 13 | "properties": { 14 | "callbackUriPath": { 15 | "description": "CallbackUriPath used for Resume Action", 16 | "title": "Data.CallbackUriPath", 17 | "type": "string" 18 | }, 19 | "subject": { 20 | "properties": { 21 | "displayName": { 22 | "description": "DisplayName of the Subject", 23 | "title": "Subject.DisplayName", 24 | "type": "string" 25 | }, 26 | "email": { 27 | "description": "Email of the Subject", 28 | "title": "Subject.Email", 29 | "type": "string" 30 | }, 31 | "id": { 32 | "description": "Id of the Subject", 33 | "title": "Subject.Id", 34 | "type": "string" 35 | }, 36 | "manager": { 37 | "properties": { 38 | "displayName": { 39 | "description": "DisplayName parameter for Manager", 40 | "title": "Manager.DisplayName", 41 | "type": "string" 42 | }, 43 | "email": { 44 | "description": "Mail parameter for Manager", 45 | "title": "Manager.Mail", 46 | "type": "string" 47 | }, 48 | "id": { 49 | "description": "Id parameter for Manager", 50 | "title": "Manager.Id", 51 | "type": "string" 52 | } 53 | }, 54 | "type": "object" 55 | }, 56 | "userPrincipalName": { 57 | "description": "UserPrincipalName of the Subject", 58 | "title": "Subject.UserPrincipalName", 59 | "type": "string" 60 | } 61 | }, 62 | "type": "object" 63 | }, 64 | "task": { 65 | "properties": { 66 | "displayName": { 67 | "description": "DisplayName for Task Object", 68 | "title": "Task.DisplayName", 69 | "type": "string" 70 | }, 71 | "id": { 72 | "description": "Id for Task Object", 73 | "title": "Task.Id", 74 | "type": "string" 75 | } 76 | }, 77 | "type": "object" 78 | }, 79 | "taskProcessingResult": { 80 | "properties": { 81 | "createdDateTime": { 82 | "description": "CreatedDateTime for TaskProcessingResult Object", 83 | "title": "TaskProcessingResult.CreatedDateTime", 84 | "type": "string" 85 | }, 86 | "id": { 87 | "description": "Id for TaskProcessingResult Object", 88 | "title": "TaskProcessingResult.Id", 89 | "type": "string" 90 | } 91 | }, 92 | "type": "object" 93 | }, 94 | "workflow": { 95 | "properties": { 96 | "displayName": { 97 | "description": "DisplayName for Workflow Object", 98 | "title": "Workflow.DisplayName", 99 | "type": "string" 100 | }, 101 | "id": { 102 | "description": "Id for Workflow Object", 103 | "title": "Workflow.Id", 104 | "type": "string" 105 | }, 106 | "workflowVerson": { 107 | "description": "WorkflowVersion for Workflow Object", 108 | "title": "Workflow.WorkflowVersion", 109 | "type": "integer" 110 | } 111 | }, 112 | "type": "object" 113 | } 114 | }, 115 | "type": "object" 116 | }, 117 | "source": { 118 | "description": "Context in which an event happened", 119 | "title": "Request.Source", 120 | "type": "string" 121 | }, 122 | "type": { 123 | "description": "Value describing the type of event related to the originating occurrence.", 124 | "title": "Request.Type", 125 | "type": "string" 126 | } 127 | }, 128 | "type": "object" 129 | } 130 | } 131 | } 132 | }, 133 | "actions": { 134 | "For_each_-_Admin_Account_found": { 135 | "type": "Foreach", 136 | "foreach": "@outputs('Parse_JSON_-_Get_admin_account_details_from_MS_Graph')?['body']?['value']", 137 | "actions": { 138 | "HTTP_-_Delete_admin_account": { 139 | "type": "Http", 140 | "inputs": { 141 | "uri": "https://graph.microsoft.com/v1.0/users/@{item()?['userPrincipalName']}", 142 | "method": "DELETE", 143 | "authentication": { 144 | "audience": "https://graph.microsoft.com", 145 | "type": "ManagedServiceIdentity" 146 | } 147 | }, 148 | "runtimeConfiguration": { 149 | "contentTransfer": { 150 | "transferMode": "Chunked" 151 | } 152 | } 153 | } 154 | }, 155 | "runAfter": { 156 | "Parse_JSON_-_Get_admin_account_details_from_MS_Graph": [ 157 | "Succeeded" 158 | ] 159 | } 160 | }, 161 | "HTTP_-_Callback": { 162 | "type": "Http", 163 | "inputs": { 164 | "uri": "https://graph.microsoft.com/beta@{triggerBody()?['data']?['callbackUriPath']}", 165 | "method": "POST", 166 | "body": { 167 | "data": { 168 | "operationStatus": "Completed" 169 | }, 170 | "source": "sample", 171 | "type": "lifecycleEvent" 172 | }, 173 | "authentication": { 174 | "audience": "https://graph.microsoft.com", 175 | "type": "ManagedServiceIdentity" 176 | } 177 | }, 178 | "runAfter": { 179 | "For_each_-_Admin_Account_found": [ 180 | "Succeeded" 181 | ] 182 | } 183 | }, 184 | "HTTP_-_Get_account_details_from_MS_Graph": { 185 | "type": "Http", 186 | "inputs": { 187 | "uri": "https://graph.microsoft.com/beta/users/@{triggerBody()?['data']?['subject']?['id']}?$select=id,employeeId,userPrincipalName,accountEnabled", 188 | "method": "GET", 189 | "authentication": { 190 | "audience": "https://graph.microsoft.com", 191 | "type": "ManagedServiceIdentity" 192 | } 193 | }, 194 | "runAfter": {}, 195 | "runtimeConfiguration": { 196 | "contentTransfer": { 197 | "transferMode": "Chunked" 198 | } 199 | } 200 | }, 201 | "HTTP_-_Get_admin_account_details_from_MS_Graph": { 202 | "type": "Http", 203 | "inputs": { 204 | "uri": "https://graph.microsoft.com/v1.0/users?$filter=startswith(employeeId,'A@{body('Parse_JSON_-_Get_account_details_from_MS_Graph')?['employeeId']}')&$count=true&$select=id,employeeId,userPrincipalName,accountEnabled", 205 | "method": "GET", 206 | "headers": { 207 | "ConsistencyLevel\n": "eventual\n" 208 | }, 209 | "authentication": { 210 | "audience": "https://graph.microsoft.com", 211 | "type": "ManagedServiceIdentity" 212 | } 213 | }, 214 | "runAfter": { 215 | "Parse_JSON_-_Get_account_details_from_MS_Graph": [ 216 | "Succeeded" 217 | ] 218 | }, 219 | "runtimeConfiguration": { 220 | "contentTransfer": { 221 | "transferMode": "Chunked" 222 | } 223 | } 224 | }, 225 | "Parse_JSON_-_Get_account_details_from_MS_Graph": { 226 | "type": "ParseJson", 227 | "inputs": { 228 | "content": "@body('HTTP_-_Get_account_details_from_MS_Graph')", 229 | "schema": { 230 | "properties": { 231 | "@@odata.context": { 232 | "type": "string" 233 | }, 234 | "accountEnabled": { 235 | "type": "boolean" 236 | }, 237 | "employeeId": { 238 | "type": "string" 239 | }, 240 | "id": { 241 | "type": "string" 242 | }, 243 | "userPrincipalName": { 244 | "type": "string" 245 | } 246 | }, 247 | "type": "object" 248 | } 249 | }, 250 | "runAfter": { 251 | "HTTP_-_Get_account_details_from_MS_Graph": [ 252 | "Succeeded" 253 | ] 254 | } 255 | }, 256 | "Parse_JSON_-_Get_admin_account_details_from_MS_Graph": { 257 | "type": "ParseJson", 258 | "inputs": { 259 | "content": "@body('HTTP_-_Get_admin_account_details_from_MS_Graph')", 260 | "schema": { 261 | "type": "object", 262 | "properties": { 263 | "@@odata.context": { 264 | "type": "string" 265 | }, 266 | "value": { 267 | "type": "array", 268 | "items": { 269 | "type": "object", 270 | "properties": { 271 | "id": { 272 | "type": "string" 273 | }, 274 | "employeeId": { 275 | "type": "string" 276 | }, 277 | "userPrincipalName": { 278 | "type": "string" 279 | }, 280 | "accountEnabled": { 281 | "type": "boolean" 282 | } 283 | }, 284 | "required": [ 285 | "id", 286 | "employeeId", 287 | "userPrincipalName", 288 | "accountEnabled" 289 | ] 290 | } 291 | } 292 | } 293 | } 294 | }, 295 | "runAfter": { 296 | "HTTP_-_Get_admin_account_details_from_MS_Graph": [ 297 | "Succeeded" 298 | ] 299 | } 300 | } 301 | }, 302 | "outputs": {}, 303 | "parameters": { 304 | "$connections": { 305 | "type": "Object", 306 | "defaultValue": {} 307 | } 308 | } 309 | }, 310 | "parameters": { 311 | "$connections": { 312 | "type": "Object", 313 | "value": {} 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /AzureAD-GroupWriteBackV2.ps1: -------------------------------------------------------------------------------- 1 | import-module ADSync 2 | $precedenceValue = Read-Host -Prompt "Enter a unique sync rule precedence value [0-99]" 3 | 4 | New-ADSyncRule ` 5 | -Name 'In from AAD - Group SOAinAAD Delete WriteBackOutOfScope and SoftDelete' ` 6 | -Identifier 'cb871f2d-0f01-4c32-a333-ff809145b947' ` 7 | -Description 'Delete AD groups that fall out of scope of Group Writeback or get Soft Deleted in Azure AD' ` 8 | -Direction 'Inbound' ` 9 | -Precedence $precedenceValue ` 10 | -PrecedenceAfter '00000000-0000-0000-0000-000000000000' ` 11 | -PrecedenceBefore '00000000-0000-0000-0000-000000000000' ` 12 | -SourceObjectType 'group' ` 13 | -TargetObjectType 'group' ` 14 | -Connector 'b891884f-051e-4a83-95af-2544101c9083' ` 15 | -LinkType 'Join' ` 16 | -SoftDeleteExpiryInterval 0 ` 17 | -ImmutableTag '' ` 18 | -OutVariable syncRule 19 | 20 | Add-ADSyncAttributeFlowMapping ` 21 | -SynchronizationRule $syncRule[0] ` 22 | -Destination 'reasonFiltered' ` 23 | -FlowType 'Expression' ` 24 | -ValueMergeType 'Update' ` 25 | -Expression 'IIF((IsPresent([reasonFiltered]) = True) && (InStr([reasonFiltered], "WriteBackOutOfScope") > 0 || InStr([reasonFiltered], "SoftDelete") > 0), "DeleteThisGroupInAD", [reasonFiltered])' ` 26 | -OutVariable syncRule 27 | 28 | New-Object ` 29 | -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition' ` 30 | -ArgumentList 'cloudMastered','true','EQUAL' ` 31 | -OutVariable condition0 32 | 33 | Add-ADSyncScopeConditionGroup ` 34 | -SynchronizationRule $syncRule[0] ` 35 | -ScopeConditions @($condition0[0]) ` 36 | -OutVariable syncRule 37 | 38 | New-Object ` 39 | -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.JoinCondition' ` 40 | -ArgumentList 'cloudAnchor','cloudAnchor',$false ` 41 | -OutVariable condition0 42 | 43 | Add-ADSyncJoinConditionGroup ` 44 | -SynchronizationRule $syncRule[0] ` 45 | -JoinConditions @($condition0[0]) ` 46 | -OutVariable syncRule 47 | 48 | Add-ADSyncRule ` 49 | -SynchronizationRule $syncRule[0] 50 | 51 | Get-ADSyncRule ` 52 | -Identifier 'cb871f2d-0f01-4c32-a333-ff809145b947' 53 | -------------------------------------------------------------------------------- /Configure-AuthenticationMethods.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script is able to change / provision the phone number of the end user used by MFA / SMS Signin 4 | Written by: Pim Jacobs (https://identity-man.eu) 5 | 6 | .PARAMETER Token Required 7 | The token only string for a Bearer token. 8 | 9 | .PARAMETER UPN Required 10 | The UPN for which you want to add or change the phonenumber 11 | 12 | .PARAMETER ActionType Optional 13 | The actiontype for changes, which can either be Add, Update or Delete as action. 14 | 15 | .PARAMETER PhoneNumber Optional 16 | Enter the international phone number of the end user i.e. "+310612345678" 17 | 18 | .PARAMETER PhoneType Optional 19 | Choose between three values i.e. Mobile, AlternateMobile or Office 20 | 21 | .PARAMETER SMSSignin Optional 22 | The actiontype for the sms sign-in feature, which can either be Add, Update or Delete as action 23 | 24 | .EXAMPLE 25 | To read current settings 26 | Configure-MFAMethods.ps1 -Token -UPN 'username@identity-man.eu' 27 | 28 | To update, add or delete settings 29 | Configure-MFAMethods.ps1 -Token -UPN 'username@identity-man.eu' -ActionType '' -PhoneNumber '<+310612345678>' -PhoneType '' 30 | 31 | To enable or disable the SMSSignIn feature (only when the user is allowed to use this feature). 32 | Configure-MFAMethods.ps1 -Token -UPN 'username@identity-man.eu' -SMSSignIn '' 33 | #> 34 | 35 | [CmdletBinding()] 36 | Param ( 37 | [Parameter(Mandatory=$true)] 38 | [String]$Token, 39 | [Parameter(Mandatory=$true)] 40 | [String]$UPN, 41 | [Parameter(Mandatory=$false)] 42 | [ValidateSet("Add", "Update", "Delete")] 43 | [String]$ActionType, 44 | [Parameter(Mandatory=$false)] 45 | [String]$PhoneNumber, 46 | [Parameter(Mandatory=$false)] 47 | [ValidateSet("Mobile", "AlternateMobile", "Office")] 48 | [String]$PhoneType, 49 | [Parameter(Mandatory=$false)] 50 | [ValidateSet("Enable", "Disable")] 51 | [String]$SMSSignIn 52 | ) 53 | 54 | $ErrorActionPreference = 'Stop'; 55 | 56 | $graphApiVersion = "beta"; 57 | $resource = "authentication/phoneMethods"; 58 | $headers = @{ 59 | "Authorization" = "Bearer $($Token)"; 60 | "Content-Type" = "application/json"; 61 | } 62 | 63 | #Try to see if the user is currently enrolled and if so retrieve current value 64 | $currentusersetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)" -Method get -Headers $headers; 65 | $Currentsettings = $currentusersetting | ConvertTo-Json 66 | write-host "Current Authentication method settings for user $UPN." -ForegroundColor Yellow 67 | write-host $Currentsettings -ForegroundColor Yellow 68 | #endregion 69 | 70 | if ($ActionType -eq "Update"){ 71 | $Method = "put" 72 | } 73 | 74 | if ($ActionType -eq "Delete"){ 75 | $Method = "Delete" 76 | } 77 | 78 | if ($ActionType){ 79 | $UpdateUserSetting = @{ phonetype=$phonetype;phonenumber=$phonenumber} 80 | $UpdateUserSetting = ConvertTo-Json -InputObject $UpdateUserSetting 81 | 82 | if ($ActionType -eq "Add") { 83 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)" -Method Post -Headers $headers -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 84 | } 85 | 86 | if (($ActionType -eq "Update" -or $ActionType -eq "Delete") -and $PhoneType -like "Mobile") { 87 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/3179e48a-750b-4051-897c-87b9720928f7" -Method $Method -Headers $headers -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 88 | } 89 | 90 | if (($ActionType -eq "Update" -or $ActionType -eq "Delete") -and $PhoneType -like "AlternateMobile") { 91 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/b6332ec1-7057-4abe-9331-3d72feddfe41" -Method $Method -Headers $headers -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 92 | } 93 | 94 | if (($ActionType -eq "Update" -or $ActionType -eq "Delete") -and $PhoneType -like "Office") { 95 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/e37fc753-ff3b-4958-9484-eaa9425c82bc" -Method $Method -Headers $headers -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 96 | } 97 | $newusersettings = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)" -Method get -Headers $headers; 98 | $newusersettings = $newusersettings | ConvertTo-Json 99 | write-host "New Authentication method settings for user $UPN." -ForegroundColor Green 100 | write-host $newusersettings -ForegroundColor Green 101 | } 102 | 103 | if (!$ActionType){ 104 | write-host "No settings changed for $UPN!" -ForegroundColor Yellow 105 | } 106 | 107 | if ($SMSSignIn){ 108 | if ($SMSSignIn -eq "Enable") { 109 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/3179e48a-750b-4051-897c-87b9720928f7/enableSmsSignIn" -Method Post -Headers $headers -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 110 | } 111 | if ($SMSSignIn -eq "Disable") { 112 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/3179e48a-750b-4051-897c-87b9720928f7/disableSmsSignIn" -Method Post -Headers $headers -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 113 | } 114 | $newusersettings = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)" -Method get -Headers $headers; 115 | $newusersettings = $newusersettings | ConvertTo-Json 116 | write-host "New Authentication method settings for user $UPN." -ForegroundColor Green 117 | write-host $newusersettings -ForegroundColor Green 118 | } 119 | 120 | if (!$SMSSignIn){ 121 | write-host "No SMSSignIn settings changed for $UPN!" -ForegroundColor Yellow 122 | } 123 | -------------------------------------------------------------------------------- /Configure-AuthenticationMethods_including_token_request.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script is able to change / provision the phone number of the end user used by MFA / SMS Signin 4 | Written by: Pim Jacobs (https://identity-man.eu) 5 | 6 | .PARAMETER UPN Required 7 | The UPN for which you want to add or change the phonenumber 8 | 9 | .PARAMETER ActionType Optional 10 | The actiontype for changes, which can either be Add, Update or Delete as action. 11 | 12 | .PARAMETER PhoneNumber Optional 13 | Enter the international phone number of the end user i.e. "+310612345678" 14 | 15 | .PARAMETER PhoneType Optional 16 | Choose between three values i.e. Mobile, AlternateMobile or Office 17 | 18 | .PARAMETER SMSSignin Optional 19 | The actiontype for the sms sign-in feature, which can either be Add, Update or Delete as action 20 | 21 | .EXAMPLE 22 | To read current settings 23 | Configure-MFAMethods.ps1 -UPN 'username@identity-man.eu' 24 | 25 | To update, add or delete settings 26 | Configure-MFAMethods.ps1 -UPN 'username@identity-man.eu' -ActionType '' -PhoneNumber '<+310612345678>' -PhoneType '' 27 | 28 | To enable or disable the SMSSignIn feature (only when the user is allowed to use this feature). 29 | Configure-MFAMethods.ps1 -UPN 'username@identity-man.eu' -SMSSignIn '' 30 | #> 31 | 32 | [CmdletBinding()] 33 | Param ( 34 | [Parameter(Mandatory=$true)] 35 | [String]$UPN, 36 | [Parameter(Mandatory=$false)] 37 | [ValidateSet("Add", "Update", "Delete")] 38 | [String]$ActionType, 39 | [Parameter(Mandatory=$false)] 40 | [String]$PhoneNumber, 41 | [Parameter(Mandatory=$false)] 42 | [ValidateSet("Mobile", "AlternateMobile", "Office")] 43 | [String]$PhoneType, 44 | [Parameter(Mandatory=$false)] 45 | [ValidateSet("Enable", "Disable")] 46 | [String]$SMSSignIn 47 | ) 48 | 49 | # ===================================================================================================================================== 50 | 51 | #To request a token make sure you've installed the MSAL Module with the PS command Install-Module -Name MSAL.PS 52 | 53 | # Update this info 54 | $tenantDomain = 'tenantname.onmicrosoft.com' #Change to your tenant domain (tenantname.onmicrosoft.com) 55 | $clientId = '00000000-0000-0000-0000-000000000000' #Change to your AppID / ClientId 56 | 57 | # ===================================================================================================================================== 58 | 59 | function New-Auth { 60 | 61 | param($aR) 62 | 63 | #Try silently getting a new token 64 | if ($aR) { 65 | $user = $aR.Account.Username 66 | $aR = $null 67 | $aR = Get-MsalToken -TenantId $tenantDomain -ClientId $clientId -RedirectUri 'urn:ietf:wg:oauth:2.0:oob' -LoginHint $user 68 | } else { 69 | # Interactive auth required 70 | $aR = Get-MsalToken -TenantId $tenantDomain -ClientId $clientId -RedirectUri 'urn:ietf:wg:oauth:2.0:oob' -Interactive 71 | } 72 | return $aR 73 | } 74 | 75 | function New-AuthHeaders{ 76 | 77 | $aH = $null 78 | $aH = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' 79 | $aH.Add('Authorization', 'Bearer ' + $authResult.AccessToken) 80 | $aH.Add('Content-Type','application/json') 81 | $aH.Add('Accept','application/json, text/plain') 82 | 83 | return $aH 84 | 85 | } 86 | 87 | function Test-TokenValidity { 88 | 89 | if ($authResult) { 90 | # We have an auth context 91 | if ($authResult.ExpiresOn.LocalDateTime -gt (Get-Date)) { 92 | 93 | # Token is still valid, nothing to do here. 94 | $remaining = $authResult.ExpiresOn.LocalDateTime - (Get-Date) 95 | Write-Host "Access Token valid for $remaining" -ForegroundColor Green 96 | 97 | } else { 98 | # Token expired, try to get a new one silently from the token cache 99 | Write-Host 'Access Token expired, getting new token silently' -ForegroundColor Green 100 | $script:authResult = New-Auth $authResult 101 | $script:authHeaders = New-AuthHeaders 102 | 103 | } 104 | 105 | } else { 106 | # No auth context, go interactive 107 | Write-Host "We need to authenticate first, select a user with the appropriate permissions" -ForegroundColor Green 108 | $script:authResult = New-Auth 109 | $script:authHeaders = New-AuthHeaders 110 | } 111 | 112 | } 113 | 114 | $ErrorActionPreference = 'Stop'; 115 | 116 | #Verify Token and refresh Token if expired or not yet requested. 117 | Test-TokenValidity 118 | 119 | #MSGraphSettings 120 | $graphApiVersion = "beta"; 121 | $resource = "authentication/phoneMethods"; 122 | 123 | #Try to see if the user is currently enrolled and if so retrieve current value 124 | $currentusersetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)" -Method get -Headers $AuthHeaders; 125 | $Currentsettings = $currentusersetting | ConvertTo-Json 126 | write-host "Current Authentication method settings for user $UPN." -ForegroundColor Yellow 127 | write-host $Currentsettings -ForegroundColor Yellow 128 | #endregion 129 | 130 | if ($ActionType -eq "Update"){ 131 | $Method = "put" 132 | } 133 | 134 | if ($ActionType -eq "Delete"){ 135 | $Method = "Delete" 136 | } 137 | 138 | if ($ActionType){ 139 | $UpdateUserSetting = @{ phonetype=$phonetype;phonenumber=$phonenumber} 140 | $UpdateUserSetting = ConvertTo-Json -InputObject $UpdateUserSetting 141 | 142 | if ($ActionType -eq "Add") { 143 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)" -Method Post -Headers $AuthHeaders -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 144 | } 145 | 146 | if (($ActionType -eq "Update" -or $ActionType -eq "Delete") -and $PhoneType -like "Mobile") { 147 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/3179e48a-750b-4051-897c-87b9720928f7" -Method $Method -Headers $AuthHeaders -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 148 | } 149 | 150 | if (($ActionType -eq "Update" -or $ActionType -eq "Delete") -and $PhoneType -like "AlternateMobile") { 151 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/b6332ec1-7057-4abe-9331-3d72feddfe41" -Method $Method -Headers $AuthHeaders -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 152 | } 153 | 154 | if (($ActionType -eq "Update" -or $ActionType -eq "Delete") -and $PhoneType -like "Office") { 155 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/e37fc753-ff3b-4958-9484-eaa9425c82bc" -Method $Method -Headers $AuthHeaders -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 156 | } 157 | $newusersettings = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)" -Method get -Headers $AuthHeaders; 158 | $newusersettings = $newusersettings | ConvertTo-Json 159 | write-host "New Authentication method settings for user $UPN." -ForegroundColor Green 160 | write-host $newusersettings -ForegroundColor Green 161 | } 162 | 163 | if (!$ActionType){ 164 | write-host "No settings changed for $UPN!" -ForegroundColor Yellow 165 | } 166 | 167 | if ($SMSSignIn){ 168 | if ($SMSSignIn -eq "Enable") { 169 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/3179e48a-750b-4051-897c-87b9720928f7/enableSmsSignIn" -Method Post -Headers $AuthHeaders -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 170 | } 171 | if ($SMSSignIn -eq "Disable") { 172 | $ExecuteUpdateUserSetting = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)/3179e48a-750b-4051-897c-87b9720928f7/disableSmsSignIn" -Method Post -Headers $AuthHeaders -Body $UpdateUserSetting -ErrorAction Stop -UseBasicParsing 173 | } 174 | $newusersettings = Invoke-RestMethod -Uri "https://graph.microsoft.com/$($graphApiVersion)/users/$($UPN)/$($resource)" -Method get -Headers $AuthHeaders; 175 | $newusersettings = $newusersettings | ConvertTo-Json 176 | write-host "New Authentication method settings for user $UPN." -ForegroundColor Green 177 | write-host $newusersettings -ForegroundColor Green 178 | } 179 | 180 | if (!$SMSSignIn){ 181 | write-host "No SMSSignIn settings changed for $UPN!" -ForegroundColor Yellow 182 | } 183 | -------------------------------------------------------------------------------- /Guest-IdentityLifecycleManagement.ps1: -------------------------------------------------------------------------------- 1 | #Configure tenant variables 2 | $AppClientId = Get-AutomationVariable -Name 'ApplicationID' 3 | $TenantId = Get-AutomationVariable -Name 'TenantID' 4 | $ClientSecret= Get-AutomationVariable -Name 'ClientSecret' 5 | 6 | #Configure connection to Graph API and make sure to retrieve access token 7 | $RequestBody = @{client_id=$AppClientId;client_secret=$ClientSecret;grant_type="client_credentials";scope="https://graph.microsoft.com/.default";} 8 | $OAuthResponse = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token -Body $RequestBody 9 | $AccessToken = $OAuthResponse.access_token 10 | 11 | #Form request headers with the acquired $AccessToken 12 | $headers = @{'Content-Type'="application/json";'Authorization'="Bearer $AccessToken"} 13 | 14 | #This defines the filter we are applying to the guest acccount Graph call. 15 | $ApiUserUrl = "https://graph.microsoft.com/beta/users?`$filter=userType in ('Guest')&`$select=displayName,id,accountEnabled,userPrincipalName,signInActivity,userType,CreatedDateTime,ExternalUserState" 16 | 17 | #Reset variables 18 | $Today = Get-Date 19 | $DeletedUserCount = 0 20 | $DisabledUserCount = 0 21 | $ActiveUserCount = 0 22 | $DeletedUsers = @() 23 | $DisabledUsers = @() 24 | $ActiveUsers = @() 25 | 26 | #Perform pagination if next page link (odata.nextlink) returned. 27 | While ($ApiUserUrl -ne $Null) { 28 | #Retrieve all guest users with their properties 29 | $UserResponse = Invoke-WebRequest -Method GET -Uri $ApiUserUrl -ContentType "application/json" -Headers $headers –UseBasicParsing | ConvertFrom-Json 30 | #If the user response contains a value continue the script 31 | if($UserResponse.value) { 32 | $Users = $UserResponse.value 33 | #for each guest user found run the following loop 34 | ForEach($User in $Users) { 35 | #define variables or reset them 36 | $id = $user.id 37 | $count = 0 38 | 39 | #This defines the Graph call to retrieve if the user is a member of any groups. 40 | $ApiMemberOfUrl = "https://graph.microsoft.com/beta/users/$id/memberOf/microsoft.graph.group?" 41 | 42 | #Perform pagination if next page link (odata.nextlink) returned. 43 | While ($ApiMemberOfUrl -ne $Null) { 44 | #retrieve all group memberships 45 | $MemberOfResponse = Invoke-WebRequest -Method GET -Uri $ApiMemberOfUrl -ContentType "application/json" -Headers $headers –UseBasicParsing | ConvertFrom-Json 46 | $MemberOfGroups = $MemberOfResponse.value 47 | 48 | #if the user is a member of groups, exclude the dynamic groups from the count and fill count per user 49 | ForEach ($MemberOfGroup in $MemberOfGroups) { 50 | $GroupType = "" 51 | $GroupType = $MemberOfGroup.groupTypes 52 | 53 | if ($GroupType -notcontains 'DynamicMembership') { 54 | $count = $count + 1 55 | } 56 | } 57 | 58 | #if the count is equal to 0 this means the user doesn't have group memberships so we can either disable or delete the account based on activity 59 | if ($count -eq 0) { 60 | #First we check the activity of te account and check the state of the invite of the guest user. 61 | $DaysInvited = (New-TimeSpan -Start $User.CreatedDateTime -End $Today).Days 62 | $LastSignInDateTime = if($User.signInActivity.lastSignInDateTime) { [DateTime]$User.signInActivity.lastSignInDateTime } Else {$null} 63 | $ExternalUserState = $user.ExternalUserState 64 | $accountEnabled = $user.accountEnabled 65 | 66 | #if the lastsignindatetime is empty, the user hasn't accepted their invite and this is alrady the case for 30 days, cleanup the account. 67 | If (($LastSignInDateTime -eq $null) -and ($ExternalUserState -eq "PendingAcceptance") -and ($DaysInvited -gt 30)) { 68 | $userprincipalname = $User.userPrincipalName 69 | $ApiDeleteUserUrl = "https://graph.microsoft.com/beta/users/$id" 70 | $DeleteUserResponse = Invoke-WebRequest -Method DELETE -Uri $ApiDeleteUserUrl -ContentType "application/json" -Headers $headers –UseBasicParsing | ConvertFrom-Json 71 | $DeletedUserCount = $DeletedUserCount + 1 72 | $DeletedUsers = $DeletedUsers + "$userprincipalname is invited for more than 30 days ago and hasn't accepted yet, start deletion of guest account!`n" 73 | } 74 | 75 | #If the lastsignindatetime is empty (because this value was only there for 1,5 year) and the interactive sign-in happend before that time let's make sure to put a value in the system of -200 days. 76 | if ($LastSignInDateTime) { 77 | $DaysInactive = (New-TimeSpan -Start $LastSignInDateTime -End $Today).Days 78 | } 79 | else { 80 | $LastSignInDateTime = (get-date).AddDays(-200) 81 | $DaysInactive = (New-TimeSpan -Start $LastSignInDateTime -End $Today).Days 82 | } 83 | 84 | #If the lastsignindatetime is empty, the user did accept the invite and therefore can access the system but didn't use the account for more than 150 days but less than 179 let's disable the account. 85 | if (($LastSignInDateTime -ne $null) -and ($accountEnabled -ne $False) -and ($ExternalUserState -ne "PendingAcceptance") -and ($DaysInactive -gt 150) -and ($DaysInactive -lt 179)) { 86 | $userprincipalname = $User.userPrincipalName 87 | $ApiBlockUserUrl = "https://graph.microsoft.com/beta/users/$id" 88 | $Body = @{accountEnabled = "false"} 89 | $BlockUserResponse = Invoke-WebRequest -Method PATCH -Uri $ApiBlockUserUrl -ContentType "application/json" -body ($body | convertto-json -depth 5) -Headers $headers –UseBasicParsing | ConvertFrom-Json 90 | $DisabledUserCount = $DisabledUserCount + 1 91 | $DisabledUsers = $DisabledUsers + "$userprincipalname is inactive for more than 150 days, start disablement of guest account!`n" 92 | 93 | } 94 | 95 | #If the lastsignindatetime is empty, the user did accept the invite and therefore can access the system but didn't use the account for more than 180 days let's disable the account. 96 | if (($LastSignInDateTime -ne $null) -and ($ExternalUserState -ne "PendingAcceptance") -and ($DaysInactive -gt 180)) { 97 | $userprincipalname = $User.userPrincipalName 98 | $ApiDeleteUserUrl = "https://graph.microsoft.com/beta/users/$id" 99 | $DeleteUserResponse = Invoke-WebRequest -Method DELETE -Uri $ApiDeleteUserUrl -ContentType "application/json" -Headers $headers –UseBasicParsing | ConvertFrom-Json 100 | $DeletedUserCount = $DeletedUserCount + 1 101 | $DeletedUsers = $DeletedUsers + "$userprincipalname is inactive for more than 180 days, start deletion of guest account!`n" 102 | } 103 | } 104 | 105 | #If the user still has group memberships an access review should eventually trigger the removal of that membership whereby the user falls in scope for this deletion. 106 | Else { 107 | $userprincipalname = $User.userPrincipalName 108 | $ActiveUserCount = $ActiveUserCount + 1 109 | $ActiveUsers = $ActiveUsers + "$userprincipalname still has $count group memberships, skipping guest user for deletion!`n" 110 | } 111 | 112 | $ApiMemberOfUrl=$MemberOfResponse.'@odata.nextlink' 113 | } 114 | } 115 | } 116 | 117 | $ApiUserUrl=$UserResponse.'@odata.nextlink' 118 | } 119 | 120 | #Report summarized blocked and deleted accounts within the Output 121 | Write-output "The following $DisabledUserCount accounts are disabled:" 122 | Write-output "$DisabledUsers" 123 | Write-output "The following $DeletedUserCount accounts are deleted:" 124 | Write-output "$DeletedUsers" 125 | Write-output "The following $ActiveUserCount accounts are active and therefore not deleted or blocked:" 126 | Write-output "$ActiveUsers" 127 | -------------------------------------------------------------------------------- /IdentityLifecycleManagement/la-001-identity-provisioning-api.json: -------------------------------------------------------------------------------- 1 | { 2 | "definition": { 3 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 4 | "actions": { 5 | "Create_Job_-_RB-001-TransformHRUserInputforAPI": { 6 | "inputs": { 7 | "body": { 8 | "properties": { 9 | "parameters": { 10 | "UserDetails": "@body('Parse_JSON_-__Get_BambooHR_Employee_Info_Output')?['employees']" 11 | } 12 | } 13 | }, 14 | "host": { 15 | "connection": { 16 | "name": "@parameters('$connections')['azureautomation']['connectionId']" 17 | } 18 | }, 19 | "method": "put", 20 | "path": "/subscriptions/@{encodeURIComponent('faaeca84-60af-4e98-8846-ebf0944a5fba')}/resourceGroups/@{encodeURIComponent('rg-mwf-ipa-runbooks-p-weu-01')}/providers/Microsoft.Automation/automationAccounts/@{encodeURIComponent('aa-mwf-ipa-runbooks-p-weu-01')}/jobs", 21 | "queries": { 22 | "runbookName": "RB-001-TransformHRUserInputforAPI", 23 | "wait": true, 24 | "x-ms-api-version": "2015-10-31" 25 | } 26 | }, 27 | "runAfter": { 28 | "Parse_JSON_-__Get_BambooHR_Employee_Info_Output": [ 29 | "Succeeded" 30 | ] 31 | }, 32 | "type": "ApiConnection" 33 | }, 34 | "For_each_-_File_found_in_iparun-date_directory": { 35 | "actions": { 36 | "Get_blob_content_-_JSON_to_POST_to_API": { 37 | "inputs": { 38 | "host": { 39 | "connection": { 40 | "name": "@parameters('$connections')['azureblob']['connectionId']" 41 | } 42 | }, 43 | "method": "get", 44 | "path": "/v2/datasets/@{encodeURIComponent(encodeURIComponent('stmwfipapweuukreciy'))}/files/@{encodeURIComponent(encodeURIComponent(items('For_each_-_File_found_in_iparun-date_directory')['Path']))}/content", 45 | "queries": { 46 | "inferContentType": true 47 | } 48 | }, 49 | "runAfter": {}, 50 | "type": "ApiConnection" 51 | }, 52 | "HTTP_-_Post_to_Entra_ID_Provisioning_API": { 53 | "inputs": { 54 | "authentication": { 55 | "audience": "https://graph.microsoft.com", 56 | "type": "ManagedServiceIdentity" 57 | }, 58 | "body": "@body('Get_blob_content_-_JSON_to_POST_to_API')", 59 | "headers": { 60 | "Content-Type": "application/scim+json" 61 | }, 62 | "method": "POST", 63 | "uri": "@variables('EntraIDProvisioningAPIEndpoint')" 64 | }, 65 | "runAfter": { 66 | "Get_blob_content_-_JSON_to_POST_to_API": [ 67 | "Succeeded" 68 | ] 69 | }, 70 | "type": "Http" 71 | } 72 | }, 73 | "foreach": "@body('Parse_JSON_-_Lists_Blob_Files_Within_Container_iparun-date')", 74 | "runAfter": { 75 | "Parse_JSON_-_Lists_Blob_Files_Within_Container_iparun-date": [ 76 | "Succeeded" 77 | ] 78 | }, 79 | "type": "Foreach" 80 | }, 81 | "Get_Job_Output_-_RB-001-TransformHRUserInputforAPI": { 82 | "inputs": { 83 | "host": { 84 | "connection": { 85 | "name": "@parameters('$connections')['azureautomation']['connectionId']" 86 | } 87 | }, 88 | "method": "get", 89 | "path": "/subscriptions/@{encodeURIComponent('faaeca84-60af-4e98-8846-ebf0944a5fba')}/resourceGroups/@{encodeURIComponent('rg-mwf-ipa-runbooks-p-weu-01')}/providers/Microsoft.Automation/automationAccounts/@{encodeURIComponent('aa-mwf-ipa-runbooks-p-weu-01')}/jobs/@{encodeURIComponent(body('Create_Job_-_RB-001-TransformHRUserInputforAPI')?['properties']?['jobId'])}/output", 90 | "queries": { 91 | "x-ms-api-version": "2015-10-31" 92 | } 93 | }, 94 | "runAfter": { 95 | "Create_Job_-_RB-001-TransformHRUserInputforAPI": [ 96 | "Succeeded" 97 | ] 98 | }, 99 | "type": "ApiConnection" 100 | }, 101 | "Get_secret_-_BambooHR": { 102 | "inputs": { 103 | "host": { 104 | "connection": { 105 | "name": "@parameters('$connections')['keyvault']['connectionId']" 106 | } 107 | }, 108 | "method": "get", 109 | "path": "/secrets/@{encodeURIComponent('BambooSecret')}/value" 110 | }, 111 | "runAfter": { 112 | "Initialize_variable_-_EntraIDProvisioningAPIEndpoint": [ 113 | "Succeeded" 114 | ] 115 | }, 116 | "type": "ApiConnection" 117 | }, 118 | "HTTP_-_Get_BambooHR_Employee_Info": { 119 | "inputs": { 120 | "authentication": { 121 | "password": "notrequired", 122 | "type": "Basic", 123 | "username": "@body('Get_secret_-_BambooHR')?['value']" 124 | }, 125 | "headers": { 126 | "accept": "application/json" 127 | }, 128 | "method": "GET", 129 | "uri": "https://api.bamboohr.com/api/gateway.php/jacobsaa/v1/employees/directory" 130 | }, 131 | "runAfter": { 132 | "Get_secret_-_BambooHR": [ 133 | "Succeeded" 134 | ] 135 | }, 136 | "type": "Http" 137 | }, 138 | "HTTP_-_Get_BambooHR_Employee_Info_(V2)": { 139 | "inputs": { 140 | "authentication": { 141 | "password": "notrequired", 142 | "type": "Basic", 143 | "username": "@body('Get_secret_-_BambooHR')?['value']" 144 | }, 145 | "body": { 146 | "fields": [ 147 | "DisplayName", 148 | "LastName", 149 | "FirstName", 150 | "middleName", 151 | "JobTitle", 152 | "Department", 153 | "division", 154 | "supervisorEid", 155 | "Country", 156 | "location", 157 | "homeEmail", 158 | "mobilePhone", 159 | "workPhone", 160 | "hireDate", 161 | "terminationDate" 162 | ] 163 | }, 164 | "headers": { 165 | "Content-Type": "application/json" 166 | }, 167 | "method": "POST", 168 | "uri": "https://api.bamboohr.com/api/gateway.php/jacobsaa/v1/reports/custom?format=JSON&onlyCurrent=false" 169 | }, 170 | "runAfter": { 171 | "HTTP_-_Get_BambooHR_Employee_Info": [ 172 | "Succeeded" 173 | ] 174 | }, 175 | "type": "Http" 176 | }, 177 | "Initialize_variable_-_EntraIDProvisioningAPIEndpoint": { 178 | "inputs": { 179 | "variables": [ 180 | { 181 | "name": "EntraIDProvisioningAPIEndpoint", 182 | "type": "string", 183 | "value": "https://graph.microsoft.com/beta/servicePrincipals/9cd12299-c5db-479f-b831-d8193fa09aae/synchronization/jobs/API2AD.ad7aaf9de4784d3f99aace450535d9cc.09eca88a-f131-4013-8be1-d97c2f22b793/bulkUpload" 184 | } 185 | ] 186 | }, 187 | "runAfter": {}, 188 | "type": "InitializeVariable" 189 | }, 190 | "Lists_Blob_Files_-_Within_Container_iparun-date": { 191 | "inputs": { 192 | "host": { 193 | "connection": { 194 | "name": "@parameters('$connections')['azureblob']['connectionId']" 195 | } 196 | }, 197 | "method": "get", 198 | "path": "/v2/datasets/@{encodeURIComponent(encodeURIComponent('stmwfipapweuukreciy'))}/foldersV2/@{encodeURIComponent(encodeURIComponent(body('Parse_JSON_-_Get_Job_Output_RB-001-TransformHRUserInputforAPI')?['containername']))}", 199 | "queries": { 200 | "nextPageMarker": "", 201 | "useFlatListing": false 202 | } 203 | }, 204 | "runAfter": { 205 | "Parse_JSON_-_Get_Job_Output_RB-001-TransformHRUserInputforAPI": [ 206 | "Succeeded" 207 | ] 208 | }, 209 | "type": "ApiConnection" 210 | }, 211 | "Parse_JSON_-_Get_Job_Output_RB-001-TransformHRUserInputforAPI": { 212 | "inputs": { 213 | "content": "@body('Get_Job_Output_-_RB-001-TransformHRUserInputforAPI')", 214 | "schema": { 215 | "properties": { 216 | "containername": { 217 | "type": "string" 218 | } 219 | }, 220 | "type": "object" 221 | } 222 | }, 223 | "runAfter": { 224 | "Get_Job_Output_-_RB-001-TransformHRUserInputforAPI": [ 225 | "Succeeded" 226 | ] 227 | }, 228 | "type": "ParseJson" 229 | }, 230 | "Parse_JSON_-_Lists_Blob_Files_Within_Container_iparun-date": { 231 | "inputs": { 232 | "content": "@body('Lists_Blob_Files_-_Within_Container_iparun-date')?['value']", 233 | "schema": { 234 | "items": { 235 | "properties": { 236 | "DisplayName": { 237 | "type": "string" 238 | }, 239 | "ETag": { 240 | "type": "string" 241 | }, 242 | "FileLocator": { 243 | "type": "string" 244 | }, 245 | "Id": { 246 | "type": "string" 247 | }, 248 | "IsFolder": { 249 | "type": "boolean" 250 | }, 251 | "LastModified": { 252 | "type": "string" 253 | }, 254 | "LastModifiedBy": {}, 255 | "MediaType": { 256 | "type": "string" 257 | }, 258 | "Name": { 259 | "type": "string" 260 | }, 261 | "Path": { 262 | "type": "string" 263 | }, 264 | "Size": { 265 | "type": "integer" 266 | } 267 | }, 268 | "required": [ 269 | "Id", 270 | "Name", 271 | "DisplayName", 272 | "Path", 273 | "LastModified", 274 | "Size", 275 | "MediaType", 276 | "IsFolder", 277 | "ETag", 278 | "FileLocator", 279 | "LastModifiedBy" 280 | ], 281 | "type": "object" 282 | }, 283 | "type": "array" 284 | } 285 | }, 286 | "runAfter": { 287 | "Lists_Blob_Files_-_Within_Container_iparun-date": [ 288 | "Succeeded" 289 | ] 290 | }, 291 | "type": "ParseJson" 292 | }, 293 | "Parse_JSON_-__Get_BambooHR_Employee_Info_Output": { 294 | "inputs": { 295 | "content": "@body('HTTP_-_Get_BambooHR_Employee_Info_(V2)')", 296 | "schema": { 297 | "properties": { 298 | "employees": { 299 | "items": { 300 | "properties": { 301 | "country": { 302 | "type": [ 303 | "string", 304 | "null" 305 | ] 306 | }, 307 | "department": { 308 | "type": [ 309 | "string", 310 | "null" 311 | ] 312 | }, 313 | "displayName": { 314 | "type": [ 315 | "string", 316 | "null" 317 | ] 318 | }, 319 | "division": { 320 | "type": [ 321 | "string", 322 | "null" 323 | ] 324 | }, 325 | "firstName": { 326 | "type": [ 327 | "string", 328 | "null" 329 | ] 330 | }, 331 | "hireDate": { 332 | "type": [ 333 | "string", 334 | "null" 335 | ] 336 | }, 337 | "homeEmail": {}, 338 | "id": { 339 | "type": [ 340 | "string", 341 | "null" 342 | ] 343 | }, 344 | "jobTitle": { 345 | "type": [ 346 | "string", 347 | "null" 348 | ] 349 | }, 350 | "lastName": { 351 | "type": [ 352 | "string", 353 | "null" 354 | ] 355 | }, 356 | "location": { 357 | "type": [ 358 | "string", 359 | "null" 360 | ] 361 | }, 362 | "middleName": { 363 | "type": [ 364 | "string", 365 | "null" 366 | ] 367 | }, 368 | "mobilePhone": { 369 | "type": [ 370 | "string", 371 | "null" 372 | ] 373 | }, 374 | "supervisor": { 375 | "type": [ 376 | "string", 377 | "null" 378 | ] 379 | }, 380 | "supervisorEid": { 381 | "type": [ 382 | "string", 383 | "null" 384 | ] 385 | }, 386 | "terminationDate": { 387 | "type": [ 388 | "string", 389 | "null" 390 | ] 391 | }, 392 | "workPhone": { 393 | "type": [ 394 | "string", 395 | "null" 396 | ] 397 | } 398 | }, 399 | "required": [ 400 | "id", 401 | "displayName", 402 | "lastName", 403 | "firstName", 404 | "middleName", 405 | "jobTitle", 406 | "department", 407 | "division", 408 | "country", 409 | "location", 410 | "homeEmail", 411 | "mobilePhone", 412 | "workPhone", 413 | "hireDate", 414 | "terminationDate" 415 | ], 416 | "type": "object" 417 | }, 418 | "type": "array" 419 | }, 420 | "fields": { 421 | "items": { 422 | "properties": { 423 | "id": { 424 | "type": "string" 425 | }, 426 | "name": { 427 | "type": "string" 428 | }, 429 | "type": { 430 | "type": "string" 431 | } 432 | }, 433 | "required": [ 434 | "id", 435 | "type", 436 | "name" 437 | ], 438 | "type": "object" 439 | }, 440 | "type": "array" 441 | }, 442 | "title": { 443 | "type": "string" 444 | } 445 | }, 446 | "type": "object" 447 | } 448 | }, 449 | "runAfter": { 450 | "HTTP_-_Get_BambooHR_Employee_Info_(V2)": [ 451 | "Succeeded" 452 | ] 453 | }, 454 | "runtimeConfiguration": { 455 | "staticResult": { 456 | "name": "Parse_JSON_AFAS_Output0", 457 | "staticResultOptions": "Disabled" 458 | } 459 | }, 460 | "type": "ParseJson" 461 | } 462 | }, 463 | "contentVersion": "1.0.0.0", 464 | "outputs": {}, 465 | "parameters": { 466 | "$connections": { 467 | "defaultValue": {}, 468 | "type": "Object" 469 | } 470 | }, 471 | "staticResults": { 472 | "Parse_JSON_AFAS_Output0": { 473 | "outputs": { 474 | "body": "" 475 | }, 476 | "status": "Succeeded" 477 | } 478 | }, 479 | "triggers": { 480 | "Recurrence": { 481 | "evaluatedRecurrence": { 482 | "frequency": "Hour", 483 | "interval": 1, 484 | "startTime": "2023-07-24T00:30:00Z", 485 | "timeZone": "W. Europe Standard Time" 486 | }, 487 | "recurrence": { 488 | "frequency": "Hour", 489 | "interval": 1, 490 | "startTime": "2023-07-24T00:30:00Z", 491 | "timeZone": "W. Europe Standard Time" 492 | }, 493 | "type": "Recurrence" 494 | } 495 | } 496 | }, 497 | "parameters": { 498 | "$connections": { 499 | "value": { 500 | "azureautomation": { 501 | "connectionId": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/resourceGroups/rg-mwf-ipa-core-p-weu-01/providers/Microsoft.Web/connections/azureautomation", 502 | "connectionName": "azureautomation", 503 | "connectionProperties": { 504 | "authentication": { 505 | "type": "ManagedServiceIdentity" 506 | } 507 | }, 508 | "id": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/providers/Microsoft.Web/locations/westeurope/managedApis/azureautomation" 509 | }, 510 | "azureblob": { 511 | "connectionId": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/resourceGroups/rg-mwf-ipa-core-p-weu-01/providers/Microsoft.Web/connections/azureblob", 512 | "connectionName": "azureblob", 513 | "connectionProperties": { 514 | "authentication": { 515 | "type": "ManagedServiceIdentity" 516 | } 517 | }, 518 | "id": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/providers/Microsoft.Web/locations/westeurope/managedApis/azureblob" 519 | }, 520 | "keyvault": { 521 | "connectionId": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/resourceGroups/rg-mwf-ipa-core-p-weu-01/providers/Microsoft.Web/connections/keyvault", 522 | "connectionName": "keyvault", 523 | "connectionProperties": { 524 | "authentication": { 525 | "type": "ManagedServiceIdentity" 526 | } 527 | }, 528 | "id": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/providers/Microsoft.Web/locations/westeurope/managedApis/keyvault" 529 | } 530 | } 531 | } 532 | } 533 | } 534 | -------------------------------------------------------------------------------- /LifecycleWorkflows/RB-100-EnableAccountAndMbxInAD.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter (Mandatory = $true)] 3 | [object]$UserPrincipalname 4 | ) 5 | 6 | #Import Modules 7 | Import-Module ActiveDirectory 8 | Add-PSSnapin *RecipientManagement 9 | 10 | #Set date time 11 | $datetime = Get-Date 12 | 13 | #Retrieve the user details 14 | $User = Get-AdUser -Filter {UserPrincipalName -eq $UserPrincipalname} 15 | 16 | #Enable account and set description 17 | Set-AdUser -identity $User.SamAccountName -Enabled $true 18 | Set-AdUser -identity $User.SamAccountName -Description "Enabled by LifeCycle Workflows onboarding Flow on: $datetime" 19 | 20 | $pos = $UserPrincipalname.IndexOf("@") 21 | $mailvalue = $UserPrincipalname.Substring(0, $pos) 22 | 23 | #Provision mailbox details in on-prem AD 24 | Enable-RemoteMailbox -identity $user.SamAccountName -RemoteRoutingAddress "$mailvalue@jacobsaa.mail.onmicrosoft.com" -alias $mailvalue -PrimarySMTPAddress $UserPrincipalname 25 | -------------------------------------------------------------------------------- /LifecycleWorkflows/RB-400-ConvertMailboxEXO.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter (Mandatory = $true)] 3 | [object]$UserPrincipalname, 4 | [Parameter (Mandatory = $true)] 5 | [object]$ManagerMail 6 | ) 7 | 8 | #Import Modules 9 | Import-Module ExchangeOnlineManagement 10 | 11 | #Connect to Exchange Online with Managed Identity 12 | Connect-ExchangeOnline -ManagedIdentity -Organization jacobsaa.onmicrosoft.com 13 | 14 | #Convert mailbox in Exchange Online to shared 15 | Set-Mailbox -identity $UserPrincipalname -type shared 16 | Write-output "Mailbox converted to shared" 17 | 18 | #Add manager to user mailbox in Exchange Online with 'Full Access' 19 | Add-MailboxPermission -Identity $UserPrincipalname -User $ManagerMail -AccessRights FullAccess -InheritanceType All 20 | Write-output "Manager added to mailbox" 21 | -------------------------------------------------------------------------------- /LifecycleWorkflows/RB-400-DisableAccountAndConvertMbxInAD.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter (Mandatory = $true)] 3 | [object]$UserPrincipalname 4 | ) 5 | 6 | #Import Modules 7 | Import-Module ActiveDirectory 8 | Add-PSSnapin *RecipientManagement 9 | 10 | #Define Variables 11 | $datetime = Get-Date 12 | 13 | #Disable user account and configure description 14 | $User = Get-AdUser -Filter {UserPrincipalName -eq $UserPrincipalname} 15 | Set-AdUser -identity $User.SamAccountName -Enabled $false 16 | Set-AdUser -identity $User.SamAccountName -Description "Disabled by LifeCycle Workflows offboarding Flow on: $datetime" 17 | 18 | #Convert mailbox in on-premises Active Directory to shared 19 | Set-RemoteMailbox -identity $user.SamAccountName -Type Shared 20 | -------------------------------------------------------------------------------- /LifecycleWorkflows/RB-500-DeleteAccountInAD.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter (Mandatory = $true)] 3 | [object]$UserPrincipalname 4 | ) 5 | 6 | #Import Modules 7 | Import-Module ActiveDirectory 8 | 9 | #Retrieve & Delete user account 10 | $User = Get-AdUser -Filter {UserPrincipalName -eq $UserPrincipalname} 11 | Remove-ADUser -Identity $User.SamAccountName -Confirm:$False 12 | -------------------------------------------------------------------------------- /NordicVirtualSummit/LA-99-NordicsVirtualSummit: -------------------------------------------------------------------------------- 1 | { 2 | "definition": { 3 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 4 | "actions": { 5 | "Compose_Variables": { 6 | "inputs": { 7 | "AdaptiveCardReceivers": "Pim.Jacobs@jacobsaa.nl", 8 | "AdaptiveCardTitle": "Cleanup of expired client secrets or certificates is required!", 9 | "AdaptiveCardTitle2": "ACTION REQUIRED: Your Service Principal needs attention!" 10 | }, 11 | "runAfter": {}, 12 | "type": "Compose" 13 | }, 14 | "Main": { 15 | "actions": { 16 | "Create_Job_RB-99-NordicsVirtualSummit": { 17 | "inputs": { 18 | "host": { 19 | "connection": { 20 | "name": "@parameters('$connections')['azureautomation']['connectionId']" 21 | } 22 | }, 23 | "method": "put", 24 | "path": "/subscriptions/@{encodeURIComponent('faaeca84-60af-4e98-8846-ebf0944a5fba')}/resourceGroups/@{encodeURIComponent('RG-Generic')}/providers/Microsoft.Automation/automationAccounts/@{encodeURIComponent('IdentityMan-Automation')}/jobs", 25 | "queries": { 26 | "runbookName": "RB-99-NordicsVirtualSummit", 27 | "wait": true, 28 | "x-ms-api-version": "2015-10-31" 29 | } 30 | }, 31 | "runAfter": {}, 32 | "type": "ApiConnection" 33 | }, 34 | "For_each_App": { 35 | "actions": { 36 | "Condition": { 37 | "actions": { 38 | "Post_adaptive_card_for_Already_Expired_secret_or_certificate": { 39 | "inputs": { 40 | "body": { 41 | "messageBody": "{\n \"type\": \"AdaptiveCard\",\n \"body\": [\n {\n \"type\": \"TextBlock\",\n \"size\": \"Large\",\n \"weight\": \"Bolder\",\n \"text\": \"@{outputs('Compose_Variables')['AdaptiveCardTitle']}\",\n \"wrap\": true,\n \"style\": \"heading\"\n },\n {\n \"type\": \"TextBlock\",\n \"wrap\": true,\n \"text\": \"The application @{items('For_each_App')?['AppDisplayName']} has client secrets or certificates which are already expired.\",\n \"weight\": \"Default\"\n },\n {\n \"type\": \"TextBlock\",\n \"text\": \"The following information has been detected for this app:\",\n \"wrap\": true,\n \"weight\": \"Bolder\"\n },\n {\n \"type\": \"FactSet\",\n \"facts\": [\n {\n \"title\": \"Display Name:\",\n \"value\": \"@{items('For_each_App')?['AppDisplayName']}\"\n },\n {\n \"title\": \"Application ID:\",\n \"value\": \"@{items('For_each_App')?['AppId']}\"\n },\n {\n \"title\": \"Secret ID:\",\n \"value\": \"@{items('For_each_App')?['SecretId']}\"\n },\n {\n \"title\": \"Certificate ID:\",\n \"value\": \"@{items('For_each_App')?['CertificateId']}\"\n },\n {\n \"title\": \"Expiration Date:\",\n \"value\": \"@{items('For_each_App')?['Expires']}\"\n }\n ]\n },\n {\n \"type\": \"TextBlock\",\n \"wrap\": true,\n \"text\": \"It's time to get your environment cleaned up, please click on the button below to go directly to the affected Service Principal and cleanup the expired secret(s) or certificate(s)!\"\n }\n ],\n \"actions\": [\n {\n \"type\": \"Action.OpenUrl\",\n \"title\": \"Go to the Service Principal\",\n \"data\": false,\n \"url\": \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Credentials/appId/@{items('For_each_App')?['AppId']}/isMSAApp/\",\n \"iconUrl\": \"https://www.freeiconspng.com/uploads/rocket-icon-png-21.png\"\n }\n ],\n \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n \"version\": \"1.4\"\n}", 42 | "recipient": "@{outputs('Compose_Variables')['AdaptiveCardReceivers']}" 43 | }, 44 | "host": { 45 | "connection": { 46 | "name": "@parameters('$connections')['teams']['connectionId']" 47 | } 48 | }, 49 | "method": "post", 50 | "path": "/v1.0/teams/conversation/adaptivecard/poster/Flow bot/location/@{encodeURIComponent('Chat with Flow bot')}" 51 | }, 52 | "runAfter": {}, 53 | "type": "ApiConnection" 54 | } 55 | }, 56 | "else": { 57 | "actions": { 58 | "Post_adaptive_card_for_secret_or_certificate_which_is_due_to_expire": { 59 | "inputs": { 60 | "body": { 61 | "messageBody": "{\n \"type\": \"AdaptiveCard\",\n \"body\": [\n {\n \"type\": \"TextBlock\",\n \"size\": \"Large\",\n \"weight\": \"Bolder\",\n \"text\": \"@{outputs('Compose_Variables')['AdaptiveCardTitle2']}\",\n \"wrap\": true,\n \"style\": \"heading\",\n \"color\": \"Attention\"\n },\n {\n \"type\": \"TextBlock\",\n \"wrap\": true,\n \"text\": \"The application @{items('For_each_App')?['AppDisplayName']} has expiring client secrets or certificates, it therefore urgently requires your attention.\",\n \"weight\": \"Default\"\n },\n {\n \"type\": \"TextBlock\",\n \"text\": \"The following information has been detected for this app:\",\n \"wrap\": true,\n \"weight\": \"Bolder\"\n },\n {\n \"type\": \"FactSet\",\n \"facts\": [\n {\n \"title\": \"Display Name:\",\n \"value\": \"@{items('For_each_App')?['AppDisplayName']}\"\n },\n {\n \"title\": \"Application ID:\",\n \"value\": \"@{items('For_each_App')?['AppId']}\"\n },\n {\n \"title\": \"Secret ID:\",\n \"value\": \"@{items('For_each_App')?['SecretId']}\"\n },\n {\n \"title\": \"Certificate ID:\",\n \"value\": \"@{items('For_each_App')?['CertificateId']}\"\n },\n {\n \"title\": \"Expiration Date:\",\n \"value\": \"@{items('For_each_App')?['Expires']}\"\n }\n ]\n },\n {\n \"type\": \"TextBlock\",\n \"wrap\": true,\n \"text\": \"Please click on the button below to go directly to the affected Service Principal and make sure measurements are taken!\"\n }\n ],\n \"actions\": [\n {\n \"type\": \"Action.OpenUrl\",\n \"title\": \"Go to the Service Principal\",\n \"data\": false,\n \"url\": \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Credentials/appId/@{items('For_each_App')?['AppId']}/isMSAApp/\",\n \"iconUrl\": \"https://www.freeiconspng.com/uploads/rocket-icon-png-21.png\"\n }\n ],\n \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n \"version\": \"1.4\"\n}", 62 | "recipient": "@{outputs('Compose_Variables')['AdaptiveCardReceivers']}" 63 | }, 64 | "host": { 65 | "connection": { 66 | "name": "@parameters('$connections')['teams']['connectionId']" 67 | } 68 | }, 69 | "method": "post", 70 | "path": "/v1.0/teams/conversation/adaptivecard/poster/Flow bot/location/@{encodeURIComponent('Chat with Flow bot')}" 71 | }, 72 | "runAfter": {}, 73 | "type": "ApiConnection" 74 | } 75 | } 76 | }, 77 | "expression": { 78 | "and": [ 79 | { 80 | "equals": [ 81 | "@items('For_each_app')?['AlreadyExpired']", 82 | true 83 | ] 84 | } 85 | ] 86 | }, 87 | "runAfter": {}, 88 | "type": "If" 89 | }, 90 | "Delay_untill_next_for_each_for_2_seconds_(due_to_throttling)": { 91 | "inputs": { 92 | "interval": { 93 | "count": 5, 94 | "unit": "Second" 95 | } 96 | }, 97 | "runAfter": { 98 | "Condition": [ 99 | "Succeeded" 100 | ] 101 | }, 102 | "type": "Wait" 103 | } 104 | }, 105 | "foreach": "@body('Parse_JSON_RB-99-NordicsVirtualSummit_Content')?['results']", 106 | "runAfter": { 107 | "Parse_JSON_RB-99-NordicsVirtualSummit_Content": [ 108 | "Succeeded" 109 | ] 110 | }, 111 | "runtimeConfiguration": { 112 | "concurrency": { 113 | "repetitions": 1 114 | } 115 | }, 116 | "type": "Foreach" 117 | }, 118 | "Get_blob_content_from_Job_Output": { 119 | "inputs": { 120 | "host": { 121 | "connection": { 122 | "name": "@parameters('$connections')['azureblob']['connectionId']" 123 | } 124 | }, 125 | "method": "get", 126 | "path": "/v2/datasets/@{encodeURIComponent(encodeURIComponent('AccountNameFromSettings'))}/GetFileContentByPath", 127 | "queries": { 128 | "inferContentType": false, 129 | "path": "/nvsjson/@{body('Parse_JSON_from_Job_output')?['blobfile']}", 130 | "queryParametersSingleEncoded": true 131 | } 132 | }, 133 | "runAfter": { 134 | "Parse_JSON_from_Job_output": [ 135 | "Succeeded" 136 | ] 137 | }, 138 | "type": "ApiConnection" 139 | }, 140 | "Get_job_output_RB-99-NordicsVirtualSummit": { 141 | "inputs": { 142 | "host": { 143 | "connection": { 144 | "name": "@parameters('$connections')['azureautomation']['connectionId']" 145 | } 146 | }, 147 | "method": "get", 148 | "path": "/subscriptions/@{encodeURIComponent('faaeca84-60af-4e98-8846-ebf0944a5fba')}/resourceGroups/@{encodeURIComponent('RG-Generic')}/providers/Microsoft.Automation/automationAccounts/@{encodeURIComponent('IdentityMan-Automation')}/jobs/@{encodeURIComponent(body('Create_Job_RB-99-NordicsVirtualSummit')?['properties']?['jobId'])}/output", 149 | "queries": { 150 | "x-ms-api-version": "2015-10-31" 151 | } 152 | }, 153 | "runAfter": { 154 | "Create_Job_RB-99-NordicsVirtualSummit": [ 155 | "Succeeded" 156 | ] 157 | }, 158 | "type": "ApiConnection" 159 | }, 160 | "Parse_JSON_RB-99-NordicsVirtualSummit_Content": { 161 | "inputs": { 162 | "content": "@json(decodeBase64(body('Get_blob_content_from_Job_Output')['$content']))", 163 | "schema": { 164 | "properties": { 165 | "results": { 166 | "items": [ 167 | { 168 | "properties": { 169 | "AlreadyExpired": { 170 | "type": "boolean" 171 | }, 172 | "AppDisplayName": { 173 | "type": "string" 174 | }, 175 | "AppId": { 176 | "type": "string" 177 | }, 178 | "CertificateID": { 179 | "type": "string" 180 | }, 181 | "Expires": { 182 | "type": "string" 183 | }, 184 | "SecretId": { 185 | "type": "string" 186 | } 187 | }, 188 | "required": [ 189 | "AppDisplayName", 190 | "AppId", 191 | "AlreadyExpired", 192 | "SecretId", 193 | "CertificateID", 194 | "Expires" 195 | ], 196 | "type": "object" 197 | } 198 | ], 199 | "type": "array" 200 | } 201 | }, 202 | "required": [ 203 | "results" 204 | ] 205 | } 206 | }, 207 | "runAfter": { 208 | "Get_blob_content_from_Job_Output": [ 209 | "Succeeded" 210 | ] 211 | }, 212 | "type": "ParseJson" 213 | }, 214 | "Parse_JSON_from_Job_output": { 215 | "inputs": { 216 | "content": "@concat('{',split(body('Get_job_output_RB-99-NordicsVirtualSummit'),'{')[1])", 217 | "schema": { 218 | "properties": { 219 | "blobfile": { 220 | "type": "string" 221 | } 222 | }, 223 | "type": "object" 224 | } 225 | }, 226 | "runAfter": { 227 | "Get_job_output_RB-99-NordicsVirtualSummit": [ 228 | "Succeeded" 229 | ] 230 | }, 231 | "type": "ParseJson" 232 | } 233 | }, 234 | "runAfter": { 235 | "Compose_Variables": [ 236 | "Succeeded" 237 | ] 238 | }, 239 | "type": "Scope" 240 | } 241 | }, 242 | "contentVersion": "1.0.0.0", 243 | "outputs": {}, 244 | "parameters": { 245 | "$connections": { 246 | "defaultValue": {}, 247 | "type": "Object" 248 | } 249 | }, 250 | "triggers": { 251 | "Recurrence": { 252 | "evaluatedRecurrence": { 253 | "frequency": "Month", 254 | "interval": 3 255 | }, 256 | "recurrence": { 257 | "frequency": "Month", 258 | "interval": 3 259 | }, 260 | "type": "Recurrence" 261 | } 262 | } 263 | }, 264 | "parameters": { 265 | "$connections": { 266 | "value": { 267 | "azureautomation": { 268 | "connectionId": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/resourceGroups/RG-NordicsVirtualSummit/providers/Microsoft.Web/connections/azureautomation", 269 | "connectionName": "azureautomation", 270 | "id": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/providers/Microsoft.Web/locations/westeurope/managedApis/azureautomation" 271 | }, 272 | "azureblob": { 273 | "connectionId": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/resourceGroups/RG-NordicsVirtualSummit/providers/Microsoft.Web/connections/azureblob", 274 | "connectionName": "azureblob", 275 | "id": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/providers/Microsoft.Web/locations/westeurope/managedApis/azureblob" 276 | }, 277 | "teams": { 278 | "connectionId": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/resourceGroups/RG-NordicsVirtualSummit/providers/Microsoft.Web/connections/teams", 279 | "connectionName": "teams", 280 | "id": "/subscriptions/faaeca84-60af-4e98-8846-ebf0944a5fba/providers/Microsoft.Web/locations/westeurope/managedApis/teams" 281 | } 282 | } 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /NordicVirtualSummit/RB-99-NordicsVirtualSummit-Saml: -------------------------------------------------------------------------------- 1 | #Define variables 2 | $tenantId = Get-AutomationVariable -Name 'TenantName' 3 | $AppID = Get-AutomationVariable -Name 'NVSApplicationID' 4 | $AuthCertificate = Get-AutomationCertificate -Name 'NVS Service Principal' 5 | $StorageAccountName = Get-AutomationVariable -Name 'NVSStorageAccountName' 6 | $StorageAccountKey = Get-AutomationVariable -Name 'NVSStorageAccountKey' 7 | $datetimerun = Get-Date -Format "yyyyMMddHHmm" 8 | $CurrentDateTime = get-date 9 | $DateIn30Days = (get-date).AddDays(+30) 10 | 11 | #define arrays 12 | $objOut = @() 13 | 14 | #Connect to Graph 15 | Connect-MgGraph -CertificateThumbprint $AuthCertificate.thumbprint -TenantId $tenantId -ClientId $AppID 16 | #Select-MgProfile -Name "beta" 17 | 18 | #Retrieve all Service Principals 19 | $ServicePrincipals = Get-MgServicePrincipal -aLL 20 | 21 | Foreach ($ServicePrincipal in $ServicePrincipals) { 22 | $ServicePrincpalDisplayName = $ServicePrincipal.displayname 23 | $ServicePrincpalId = $ServicePrincipal.AppId 24 | $AppObjectId = $ServicePrincipal.Id 25 | $PasswordCredentials = $ServicePrincipal.passwordcredentials 26 | 27 | if ($PasswordCredentials) { 28 | Foreach ($PasswordCredential in $PasswordCredentials) { 29 | $FederatedSSOCertDateTime = $PasswordCredential.EndDateTime 30 | $FederatedSSOCertDateTime = [DateTime]$FederatedSSOCertDateTime 31 | $FederatedSSOCertId = $PasswordCredential.KeyId 32 | if ($ServicePrincipal.AccountEnabled -eq $true) { 33 | if ($FederatedSSOCertDateTime -lt $DateIn30Days) { 34 | if ($FederatedSSOCertDateTime -lt $CurrentDateTime) { 35 | $AlreadyExpired = $True 36 | # Create a JSON object for output to next step in Logic App workflow 37 | $objOut += [pscustomobject]@{ 38 | AppDisplayName = $ServicePrincpalDisplayName 39 | AppId = $ServicePrincpalId 40 | AlreadyExpired = $AlreadyExpired 41 | Expires = $FederatedSSOCertDateTime 42 | CertificateID = $FederatedSSOCertId 43 | AppObjectId = $AppObjectId 44 | } 45 | #write-output "$ServicePrincpalDisplayName has an expired Federated SSO Certificate with ID $FederatedSSOCertId!" 46 | } 47 | else { 48 | $AlreadyExpired = $False 49 | # Create a JSON object for output to next step in Logic App workflow 50 | $objOut += [pscustomobject]@{ 51 | AppDisplayName = $ServicePrincpalDisplayName 52 | AppId = $ServicePrincpalId 53 | AlreadyExpired = $AlreadyExpired 54 | SecretId = $AppSecretId 55 | Expires = $FederatedSSOCertDateTime 56 | CertificateID = $FederatedSSOCertId 57 | AppObjectId = $AppObjectId 58 | } 59 | #write-output "$ServicePrincpalDisplayName has an Federated SSO Certificate active with ID $FederatedSSOCertId which will expire in 30 days!" 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | $objOutTotal = [pscustomobject]@{ 68 | results = $objOut 69 | } 70 | 71 | $objOutTotalFile = New-TemporaryFile 72 | 73 | $Context = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $StorageAccountKey 74 | $MyRawString = $objOutTotal | ConvertTo-Json -Depth 99 75 | $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False 76 | [System.IO.File]::WriteAllLines($objOutTotalFile.FullName, $MyRawString, $Utf8NoBomEncoding) 77 | 78 | $filenameblob = "rb-99-NordicsVirtualSummit-Saml"+$datetimerun+".json" 79 | $storeblob = Set-AzureStorageBlobContent -Context $Context -Container nvsjson -File $objOutTotalFile.FullName -Blob $filenameblob -Properties @{"ContentEncoding" = "UTF-8"} 80 | 81 | $params = @{ 82 | "blobfile"=$filenameblob; 83 | } 84 | 85 | Write-Output ( $params | ConvertTo-Json -Depth 99) 86 | -------------------------------------------------------------------------------- /NordicVirtualSummit/RB-99-NordicsVirtualSummit-Saml.ps1: -------------------------------------------------------------------------------- 1 | #Define variables 2 | $tenantId = Get-AutomationVariable -Name 'TenantName' 3 | $AppID = Get-AutomationVariable -Name 'NVSApplicationID' 4 | $AuthCertificate = Get-AutomationCertificate -Name 'NVS Service Principal' 5 | $StorageAccountName = Get-AutomationVariable -Name 'NVSStorageAccountName' 6 | $StorageAccountKey = Get-AutomationVariable -Name 'NVSStorageAccountKey' 7 | $datetimerun = Get-Date -Format "yyyyMMddHHmm" 8 | $CurrentDateTime = get-date 9 | $DateIn30Days = (get-date).AddDays(+30) 10 | 11 | #define arrays 12 | $objOut = @() 13 | 14 | #Connect to Graph 15 | Connect-MgGraph -CertificateThumbprint $AuthCertificate.thumbprint -TenantId $tenantId -ClientId $AppID 16 | #Select-MgProfile -Name "beta" 17 | 18 | #Retrieve all Service Principals 19 | $ServicePrincipals = Get-MgServicePrincipal -aLL 20 | 21 | Foreach ($ServicePrincipal in $ServicePrincipals) { 22 | $ServicePrincpalDisplayName = $ServicePrincipal.displayname 23 | $ServicePrincpalId = $ServicePrincipal.AppId 24 | $AppObjectId = $ServicePrincipal.Id 25 | $PasswordCredentials = $ServicePrincipal.passwordcredentials 26 | 27 | if ($PasswordCredentials) { 28 | Foreach ($PasswordCredential in $PasswordCredentials) { 29 | $FederatedSSOCertDateTime = $PasswordCredential.EndDateTime 30 | $FederatedSSOCertDateTime = [DateTime]$FederatedSSOCertDateTime 31 | $FederatedSSOCertId = $PasswordCredential.KeyId 32 | if ($ServicePrincipal.AccountEnabled -eq $true) { 33 | if ($FederatedSSOCertDateTime -lt $DateIn30Days) { 34 | if ($FederatedSSOCertDateTime -lt $CurrentDateTime) { 35 | $AlreadyExpired = $True 36 | # Create a JSON object for output to next step in Logic App workflow 37 | $objOut += [pscustomobject]@{ 38 | AppDisplayName = $ServicePrincpalDisplayName 39 | AppId = $ServicePrincpalId 40 | AlreadyExpired = $AlreadyExpired 41 | Expires = $FederatedSSOCertDateTime 42 | CertificateID = $FederatedSSOCertId 43 | AppObjectId = $AppObjectId 44 | } 45 | #write-output "$ServicePrincpalDisplayName has an expired Federated SSO Certificate with ID $FederatedSSOCertId!" 46 | } 47 | else { 48 | $AlreadyExpired = $False 49 | # Create a JSON object for output to next step in Logic App workflow 50 | $objOut += [pscustomobject]@{ 51 | AppDisplayName = $ServicePrincpalDisplayName 52 | AppId = $ServicePrincpalId 53 | AlreadyExpired = $AlreadyExpired 54 | SecretId = $AppSecretId 55 | Expires = $FederatedSSOCertDateTime 56 | CertificateID = $FederatedSSOCertId 57 | AppObjectId = $AppObjectId 58 | } 59 | #write-output "$ServicePrincpalDisplayName has an Federated SSO Certificate active with ID $FederatedSSOCertId which will expire in 30 days!" 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | $objOutTotal = [pscustomobject]@{ 68 | results = $objOut 69 | } 70 | 71 | $objOutTotalFile = New-TemporaryFile 72 | 73 | $Context = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $StorageAccountKey 74 | $MyRawString = $objOutTotal | ConvertTo-Json -Depth 99 75 | $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False 76 | [System.IO.File]::WriteAllLines($objOutTotalFile.FullName, $MyRawString, $Utf8NoBomEncoding) 77 | 78 | $filenameblob = "rb-99-NordicsVirtualSummit-Saml"+$datetimerun+".json" 79 | $storeblob = Set-AzureStorageBlobContent -Context $Context -Container nvsjson -File $objOutTotalFile.FullName -Blob $filenameblob -Properties @{"ContentEncoding" = "UTF-8"} 80 | 81 | $params = @{ 82 | "blobfile"=$filenameblob; 83 | } 84 | 85 | Write-Output ( $params | ConvertTo-Json -Depth 99) 86 | -------------------------------------------------------------------------------- /NordicVirtualSummit/RB-99-NordicsVirtualSummit.ps1: -------------------------------------------------------------------------------- 1 | #Define variables 2 | $tenantId = Get-AutomationVariable -Name 'TenantName' 3 | $AppID = Get-AutomationVariable -Name 'NVSApplicationID' 4 | $AuthCertificate = Get-AutomationCertificate -Name 'NVS Service Principal' 5 | $StorageAccountName = Get-AutomationVariable -Name 'NVSStorageAccountName' 6 | $StorageAccountKey = Get-AutomationVariable -Name 'NVSStorageAccountKey' 7 | $datetimerun = Get-Date -Format "yyyyMMddHHmm" 8 | $CurrentDateTime = get-date 9 | $DateIn30Days = (get-date).AddDays(+30) 10 | 11 | #define arrays 12 | $objOut = @() 13 | 14 | #Connect to Graph 15 | Connect-MgGraph -CertificateThumbprint $AuthCertificate.thumbprint -TenantId $tenantId -ClientId $AppID 16 | #Select-MgProfile -Name "beta" 17 | 18 | #Retrieve all applications 19 | $Applications = Get-MgApplication -All 20 | 21 | Foreach ($Application in $Applications) { 22 | $AppDisplayName = $Application.displayname 23 | $AppId = $Application.AppId 24 | $PasswordCredentials = $Application.passwordcredentials 25 | $KeyCredentials = $Application.KeyCredentials 26 | 27 | if ($PasswordCredentials) { 28 | Foreach ($PasswordCredential in $PasswordCredentials) { 29 | $AppSecretDateTime = $PasswordCredential.EndDateTime 30 | $AppSecretDateTime = [DateTime]$AppSecretDateTime 31 | $AppSecretId = $PasswordCredential.KeyId 32 | if ($AppSecretDateTime -lt $DateIn30Days) { 33 | 34 | if ($AppSecretDateTime -lt $CurrentDateTime) { 35 | $AlreadyExpired = $True 36 | # Create a JSON object for output to next step in Logic App workflow 37 | $objOut += [pscustomobject]@{ 38 | AppDisplayName = $AppDisplayName 39 | AppId = $AppId 40 | AlreadyExpired = $AlreadyExpired 41 | SecretId = $AppSecretId 42 | Expires = $AppSecretDateTime 43 | CertificateID = "NA" 44 | } 45 | #write-host "$AppDisplayName has expired client secret with ID $AppSecretId!" 46 | } 47 | else { 48 | $AlreadyExpired = $False 49 | # Create a JSON object for output to next step in Logic App workflow 50 | $objOut += [pscustomobject]@{ 51 | AppDisplayName = $AppDisplayName 52 | AppId = $AppId 53 | AlreadyExpired = $AlreadyExpired 54 | SecretId = $AppSecretId 55 | Expires = $AppSecretDateTime 56 | CertificateID = "NA" 57 | } 58 | #write-host "$AppDisplayName client secret with ID $AppSecretId will expire in 30 days!" 59 | } 60 | } 61 | } 62 | } 63 | 64 | if ($KeyCredentials) { 65 | Foreach ($KeyCredential in $KeyCredentials) { 66 | $AppCertificateDateTime = $KeyCredential.EndDateTime 67 | $AppCertificateDateTime = [DateTime]$AppCertificateDateTime 68 | $AppCertificateId = $KeyCredential.KeyId 69 | if ($AppCertificateDateTime -lt $DateIn30Days) { 70 | 71 | if ($AppCertificateDateTime -lt $CurrentDateTime) { 72 | $AlreadyExpired = $True 73 | # Create a JSON object for output to next step in Logic App workflow 74 | $objOut += [pscustomobject]@{ 75 | AppDisplayName = $AppDisplayName 76 | AppId = $AppId 77 | AlreadyExpired = $AlreadyExpired 78 | SecretId = "NA" 79 | CertificateID = $AppCertificateId 80 | Expires = $AppCertificateDateTime 81 | } 82 | #write-host "$AppDisplayName has expired authentication certificate with ID $AppCertificateId!" 83 | } 84 | else { 85 | $AlreadyExpired = $False 86 | # Create a JSON object for output to next step in Logic App workflow 87 | $objOut += [pscustomobject]@{ 88 | AppDisplayName = $AppDisplayName 89 | AppId = $AppId 90 | AlreadyExpired = $AlreadyExpired 91 | SecretId = "NA" 92 | CertificateID = $AppCertificateId 93 | Expires = $AppCertificateDateTime 94 | } 95 | #write-host "$AppDisplayName authentication certificate with ID $AppCertificateId will expire in 30 days!" 96 | } 97 | } 98 | } 99 | } 100 | 101 | } 102 | 103 | $objOutTotal = [pscustomobject]@{ 104 | results = $objOut 105 | } 106 | 107 | $objOutTotalFile = New-TemporaryFile 108 | 109 | $Context = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $StorageAccountKey 110 | $MyRawString = $objOutTotal | ConvertTo-Json -Depth 99 111 | $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False 112 | [System.IO.File]::WriteAllLines($objOutTotalFile.FullName, $MyRawString, $Utf8NoBomEncoding) 113 | 114 | $filenameblob = "rb-99-NordicsVirtualSummit"+$datetimerun+".json" 115 | $storeblob = Set-AzureStorageBlobContent -Context $Context -Container nvsjson -File $objOutTotalFile.FullName -Blob $filenameblob -Properties @{"ContentEncoding" = "UTF-8"} 116 | 117 | $params = @{ 118 | "blobfile"=$filenameblob; 119 | } 120 | 121 | Write-Output ( $params | ConvertTo-Json -Depth 99) 122 | -------------------------------------------------------------------------------- /Pre-ConfigureAuthMethods V2/AuthMethodsImport.csv: -------------------------------------------------------------------------------- 1 | UPN,Mobile,OfficePhone,AlternateMobile,ForcedUpdate,ForcedRemoval 2 | Johny.bravo@identity-man.eu,+310612345678,+310485123456,+310618765432,True,True 3 | donald.duck@identity-man.eu,+310687654321,+310486123456,+310623456789,True,True 4 | -------------------------------------------------------------------------------- /Pre-ConfigureAuthMethods V2/Pre-ConfigureAuthMethods.ps1: -------------------------------------------------------------------------------- 1 | #install Microsoft.Graph module and dependancies and import required modules 2 | Install-Module Microsoft.Graph -AllowClobber -Force 3 | Import-Module Microsoft.Graph.Authentication 4 | 5 | #Connect based on a service principal 6 | Connect-MgGraph -ClientId 00000000-0000-0000-0000-000000000000 -TenantId 00000000-0000-0000-0000-000000000000 -CertificateThumbprint 0000000000000000000000000000000000000000 7 | Select-MgProfile -Name "beta" 8 | 9 | #if you want to connect interactive, comment the above two lines and use the ones mentioned below 10 | #Connect-MgGraph -Scopes "UserAuthenticationMethod.ReadWrite.All" 11 | #Select-MgProfile -Name "beta" 12 | 13 | #import-csv file 14 | $users = Import-Csv -Path "C:\Temp\AuthMethodsImport.csv" -Delimiter "," 15 | 16 | Foreach ($User in $Users) { 17 | Write-Host "Configuring authentication methods for user" $user.upn -ForegroundColor Green 18 | $results = Get-MgUserAuthenticationPhoneMethod -UserId $user.upn 19 | 20 | #Retrieve mobileresults from Results 21 | $mobileresult = $results | Where-Object {$_.phonetype -eq "Mobile"} 22 | 23 | #Reconfigure mobile field if a new value is presented. 24 | if ($User.Mobile) { 25 | if ($mobileresult.PhoneType -eq "Mobile") { 26 | if ($User.ForcedUpdate -eq $true) { 27 | Try { 28 | Remove-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneAuthenticationMethodId $mobileresult.Id -Erroraction Stop 29 | New-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneType Mobile -PhoneNumber $User.Mobile | Out-Null 30 | } 31 | Catch { 32 | Write-Host "Failed to update mobile authentication method as it's configured as the default for" $user.upn -ForegroundColor Yellow 33 | } 34 | } 35 | } 36 | Else { 37 | #NOTE: If the user is enabled for SMS Sign-in this number is automatically enabled for SMS Sign-in. 38 | New-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneType Mobile -PhoneNumber $User.Mobile | Out-Null 39 | } 40 | } 41 | 42 | Else { 43 | if (($User.ForcedRemoval -eq $true) -and ($mobileresult)) { 44 | Try { 45 | Remove-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneAuthenticationMethodId $mobileresult.Id -Erroraction Stop 46 | } 47 | Catch { 48 | Write-Host "Failed to delete mobile authentication method as it's configured as the default for" $user.upn -ForegroundColor Yellow 49 | } 50 | 51 | } 52 | } 53 | 54 | #Retrieve Alternatemobileresults from Results 55 | $Alternatemobileresult = $results | Where-Object {$_.phonetype -eq "AlternateMobile"} 56 | 57 | #Reconfigure Alternatemobile field if a new value is presented. 58 | if ($User.AlternateMobile) { 59 | if ($User.mobile) { 60 | if ($Alternatemobileresult.PhoneType -eq "AlternateMobile") { 61 | if ($User.ForcedUpdate -eq $true) { 62 | Try { 63 | Remove-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneAuthenticationMethodId $Alternatemobileresult.Id -Erroraction Stop 64 | New-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneType AlternateMobile -PhoneNumber $User.AlternateMobile | out-null 65 | } 66 | Catch { 67 | Write-Host "Failed to update Alternate Mobile authentication method as it's configured as the default for" $user.upn -ForegroundColor Yellow 68 | } 69 | } 70 | } 71 | Else { 72 | New-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneType AlternateMobile -PhoneNumber $User.AlternateMobile | Out-Null 73 | } 74 | } 75 | Else { 76 | Write-Host "Failed to update Alternate Mobile method as Mobile method is mandatory and not set for" $user.upn -ForegroundColor Yellow 77 | } 78 | } 79 | 80 | Else { 81 | if (($User.ForcedRemoval -eq $true) -and ($Alternatemobileresult)) { 82 | Try { 83 | Remove-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneAuthenticationMethodId $Alternatemobileresult.Id -Erroraction Stop 84 | } 85 | Catch { 86 | Write-Host "Failed to delete Alternate Mobile authentication method as it's configured as the default for" $user.upn -ForegroundColor Yellow 87 | } 88 | } 89 | } 90 | 91 | #Retrieve OfficePhoneResults from Results 92 | $OfficePhoneResults = $results | Where-Object {$_.phonetype -eq "Office"} 93 | 94 | #Reconfigure Office field if a new value is presented. 95 | if ($User.OfficePhone) { 96 | if ($OfficePhoneResults.PhoneType -eq "Office") { 97 | if ($User.ForcedUpdate -eq $true) { 98 | Try { 99 | Remove-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneAuthenticationMethodId $OfficePhoneResults.Id -Erroraction Stop 100 | New-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneType Office -PhoneNumber $User.OfficePhone | Out-Null 101 | } 102 | Catch { 103 | Write-Host "Failed to update Office Phone authentication method as it's configured as the default for" $user.upn -ForegroundColor Yellow 104 | } 105 | } 106 | } 107 | Else { 108 | New-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneType Office -PhoneNumber $User.OfficePhone | Out-Null 109 | } 110 | } 111 | 112 | Else { 113 | if (($User.ForcedRemoval -eq $true) -and ($OfficePhoneResults)) { 114 | Try { 115 | Remove-MgUserAuthenticationPhoneMethod -UserId $User.UPN -PhoneAuthenticationMethodId $OfficePhoneResults.Id -Erroraction Stop 116 | } 117 | Catch { 118 | Write-Host "Failed to delete Office Phone authentication method as it's configured as the default for" $user.upn -ForegroundColor Yellow 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /WPNinjas-Demo-GuestLifeCycle: -------------------------------------------------------------------------------- 1 | #Retrieve variables from automation account 2 | $tenantId = Get-AutomationVariable -Name 'TenantID' 3 | $AppID = Get-AutomationVariable -Name 'ApplicationID' 4 | $AuthCertificate = Get-AutomationCertificate -Name 'WPNinjas Service Principal' 5 | 6 | #Connect to environment 7 | Connect-AzAccount -ServicePrincipal -Tenant $tenantId -ApplicationId $AppID -CertificateThumbprint $AuthCertificate.thumbprint 8 | Connect-AzureAD -TenantId $tenantId -ApplicationId $AppID -CertificateThumbprint $AuthCertificate.thumbprint 9 | 10 | #Retrieve all user and get todays current date 11 | $GuestUsers = Get-AzureADUser -All $true | where {(($_.usertype -like "guest") -or ($_.userPrincipalName -like "*#EXT#@*")) -and ($_.UserState -eq "Accepted")} 12 | $Today = Get-Date 13 | 14 | Foreach ($GuestUser in $GuestUsers) { 15 | $UserGroupMembership = Get-AzureADUserMembership -ObjectId $GuestUser.ObjectId 16 | $UserGroupMembership = $UserGroupMembership.count 17 | If ($UserGroupMembership -eq '0') { 18 | $userprincipalname = $GuestUser.mail 19 | [string]$WorkspaceID = 'd4c05fa0-1652-45e8-9ed0-bda450c81ee2' 20 | $QuerySignInCount = 'SigninLogs | where TimeGenerated > ago(60d) | where UserPrincipalName == "' + $UserPrincipalName + '" | order by TimeGenerated desc nulls last | limit 1' 21 | $ResultsSignInCount = Invoke-AzOperationalInsightsQuery -WorkspaceId $WorkspaceID -Query $QuerySignInCount 22 | $AADSigninDate = $ResultsSignInCount.Results.TimeGenerated 23 | 24 | if ($AADSigninDate -like "") { 25 | $AADSigninDate = get-date 26 | $AADSigninDate = $AADSigninDate.AddDays(-62) 27 | } 28 | 29 | #Gather differences 30 | $DaysInactive = (New-TimeSpan -Start $AADSigninDate -End $Today).Days 31 | #write-output "Account $UserPrincipalname is inactive for $DaysInactive days" 32 | 33 | if (($DaysInactive -gt 30) -and ($DaysInactive -lt 60)) { 34 | write-output "Account $UserPrincipalname is inactive for $DaysInactive days, disabling the account" 35 | #Set-AzureADUser -ObjectId $guestuser.ObjectId -AccountEnabled $false 36 | } 37 | 38 | if ($DaysInactive -gt 60) { 39 | write-output "Account $UserPrincipalname is inactive for $DaysInactive days, removing the account" 40 | #Remove-AzureADUser -ObjectId $guestuser.ObjectId 41 | } 42 | } 43 | Else { 44 | write-output "Account $UserPrincipalname still has group memberships, skipping." 45 | } 46 | } 47 | --------------------------------------------------------------------------------