If your company benefits from using dynamic groups, then you probably know that feature takes its toll – proper maintenance and updates. It usually happens when there are some organizational changes – updated job titles or another department, just to name a few. The purpose of this script is to take that burden away and make updates at once to every single dynamic group that matches the scope of the adjustment.
Benefits and nuances of the script:
- can find and update any string value – doesn’t matter if you update a property name or a property value;
- saves a CSV log file with all values before and after the update;
- only takes a few seconds to run and adjust up to 20 groups at once;
- 20 group number comes from a Graph API Batch limitation, more on that here – https://learn.microsoft.com/en-us/graph/json-batching
- retains membership rule formatting if you used one, more on that here – https://365basics.com/aad-dynamic-group-example-and-formatting-tips/
- dry or wet run depending on the switch (see the important note below).
Important! This line (#113) of code is responsible for either dry or wet run of the script. I left it commented on purpose and by default.
# $GroupAdjustments = Invoke-RestMethod @Parameters
Please note, this script assumes you already have Az.Accounts cmdlet installed and connected to it, and of course have proper rights in the system.
# Data input $OldValue = Read-host "Old value to find and eventually replace (include double quotes if those are part of the string)" $NewValue = Read-host "New value to replace the old value with (include double quotes if those are part of the string)" # Start timer $elapsed = [System.Diagnostics.Stopwatch]::StartNew() # Graph API authentication $resource = "https://graph.microsoft.com" $context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext $accessToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $resource).AccessToken # Get all groups through Graph API - select id,displayName,membershipRule $uri = "https://graph.microsoft.com/v1.0/groups?$select=id,displayName,membershipRule" $Groups = Invoke-RestMethod -Method 'Get' -Uri $uri -Headers @{ Authorization = "Bearer " + $accessToken } # Add found groups to AllGroups collection $AllGroups = @() $AllGroups+=$Groups.value # Keep getting (Graph API is subject to paging) all groups through Graph API - select id,displayName,membershipRule while($Groups.'@odata.nextLink' -ne $null) { $Groups = Invoke-RestMethod -Method 'Get' -Uri $Groups.'@odata.nextLink' -Headers @{ Authorization = "Bearer " + $accessToken } # Keep adding found groups to AllGroups collection $AllGroups+=$Groups.value } # Filter groups and leave those with Dynamic rules $AllGroups = $AllGroups | Where-Object {($_.membershipRule -ne $null)} # Collection to store found groups $Collection = @() # Index variable $i = 1 ForEach ($Group in $AllGroups) { # Add the following to the collection - number, display name, id, if CustomAttribute2 is used, if CustomAttribute4 is used, old rule before the change, new rule after the change $OldRule = $Group.membershipRule $Rule = $Group.membershipRule $outObject = "" | Select Number,id,DisplayName,OldRule,NewRule $outObject."id" = $Group.id $outObject."Number" = $i $outObject."DisplayName" = $Group.DisplayName If (($Group.membershipRule -match $OldValue)) { $Rule = $Rule.replace($OldValue, $NewValue) $Process = $true } Else { $Process = $false } $outObject."OldRule" = $OldRule $outObject."NewRule" = $Rule # Only add those groups to the collection that will be processed If ($Process -eq $true) { $i = $i + 1 $Collection += $outObject } } # Save collection to a CSV file $OutputFile = "C:\Updates to Dynamic Groups $(get-date -f yyyy-MM-dd_HH-mm-ss).csv" $Collection | Export-CSV $OutputFile -NoTypeInformation # Collection for batch requests $myBatchRequests = @() Foreach ($item in $Collection | Select-Object -First 20) { Write-host Write-host $item.DisplayName -ForegroundColor Cyan Write-host $item.OldRule -ForegroundColor Red Write-host $item.NewRule -ForegroundColor Green # Calculate url for each group $url = "/groups/"+$item.id # $item.DisplayName # Body $myRequest = @{ id = $item.Number method = "PATCH" url = $url body = @{ membershipRule = $item.NewRule } headers = @{ "Content-Type" = "application/json" } } $myBatchRequests += $myRequest } # Combine batch requests for Graph API batching $allBatchRequests = @{ requests = $myBatchRequests } # Convert body to JSON for Graph API $batchBody = $allBatchRequests | ConvertTo-Json -Depth 5 # Core parameters for Graph API POST request $Parameters = @{ ContentType = 'application/json' Method = 'POST' Body = $batchBody Uri = 'https://graph.microsoft.com/v1.0/$batch' Headers = @{ Authorization = "Bearer " + $accessToken } } # Execute Graph API POST request that changes Membership Rules in Dynamic groups # $GroupAdjustments = Invoke-RestMethod @Parameters # Stop timer Write-Host "Total Time: $($elapsed.Elapsed.ToString())"
RESULT
Pingback: PowerShell - nullify extension attributes for all users in AAD using Graph API - Office 365 Basics