From 07c25e897adffc153b7b0ff7d4e523b9f53f89f6 Mon Sep 17 00:00:00 2001 From: Tomas Kracmar Date: Tue, 14 Apr 2026 18:50:02 +0200 Subject: [PATCH] fix(assignments): use bulk /assign endpoint for removals on all types Settings Catalog and other bulk-assign types do not support DELETE on individual assignments. Removal now reloads existing assignments, filters out selected targets, sanitizes remaining payloads, and re-POSTs to //assign. This mirrors the add flow and works universally across all supported object types. --- Scripts/Bulk-AssignmentManager.ps1 | 58 +++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/Scripts/Bulk-AssignmentManager.ps1 b/Scripts/Bulk-AssignmentManager.ps1 index 566b002..700496d 100644 --- a/Scripts/Bulk-AssignmentManager.ps1 +++ b/Scripts/Bulk-AssignmentManager.ps1 @@ -648,6 +648,29 @@ elseif($action -eq "Remove assignments") exit 0 } + # Helper: compute TargetDesc for an assignment + function Get-AssignmentTargetDesc + { + param($Ass) + $tt = $Ass.target."@odata.type" + switch($tt) + { + "#microsoft.graph.groupAssignmentTarget" + { + $grp = $groups | Where-Object { $_.id -eq $Ass.target.groupId } | Select-Object -First 1 + return "Include: $(if($grp){$grp.displayName}else{$Ass.target.groupId})" + } + "#microsoft.graph.exclusionGroupAssignmentTarget" + { + $grp = $groups | Where-Object { $_.id -eq $Ass.target.groupId } | Select-Object -First 1 + return "Exclude: $(if($grp){$grp.displayName}else{$Ass.target.groupId})" + } + "#microsoft.graph.allLicensedUsersAssignmentTarget" { return "All Users" } + "#microsoft.graph.allDevicesAssignmentTarget" { return "All Devices" } + default { return "Unknown" } + } + } + # Execute $success = 0 $failed = 0 @@ -657,15 +680,42 @@ elseif($action -eq "Remove assignments") if($objAssignments.Count -eq 0) { continue } Write-Host "`nProcessing: $($obj."$($objectType.NameProp)")" -ForegroundColor Cyan - foreach($ass in $objAssignments) + try { - try + $existing = Invoke-GraphRequest "$($objectType.API)/$($obj.id)/assignments" + $remaining = @() + foreach($ea in $existing.value) + { + $desc = Get-AssignmentTargetDesc -Ass $ea + if($desc -in $selectedTargetDisplays) + { + continue + } + # Sanitize for re-post + $clean = $ea | ConvertTo-Json -Depth 50 | ConvertFrom-Json + if($clean.PSObject.Properties["id"]) { $clean.PSObject.Properties.Remove("id") } + if($clean.PSObject.Properties["source"]) { $clean.PSObject.Properties.Remove("source") } + if(-not $clean."@odata.type") + { + $clean | Add-Member -NotePropertyName "@odata.type" -NotePropertyValue $objectType.AssignmentODataType -Force + } + $remaining += $clean + } + + $assignPayload = @{ + $objectType.AssignmentsType = $remaining + } | ConvertTo-Json -Depth 50 -Compress + + $null = Invoke-GraphRequest "$($objectType.API)/$($obj.id)/assign" -HttpMethod POST -Content $assignPayload + foreach($ass in $objAssignments) { - $null = Invoke-GraphRequest "$($objectType.API)/$($obj.id)/assignments/$($ass.AssignmentId)" -HttpMethod DELETE Write-Host " OK: Removed $($ass.TargetDesc)" -ForegroundColor Green $success++ } - catch + } + catch + { + foreach($ass in $objAssignments) { Write-Host " ERROR: Failed to remove $($ass.TargetDesc). $($_.Exception.Message)" -ForegroundColor Red $failed++