Public/Permissions/Invoke-365TuneRevokeAzure.ps1
|
function Invoke-365TuneRevokeAzure { <# .SYNOPSIS Revokes Reader permissions previously granted to the 365TUNE Enterprise App in Azure. .DESCRIPTION Removes Reader access from root scope "/" and AAD IAM scope "/providers/Microsoft.aadiam". Safe to re-run - exits cleanly if no assignments are found. Run from local PowerShell or Cloud Shell. Your account must have Global Administrator rights and "Access management for Azure resources" enabled in Entra ID > Properties. .EXAMPLE Invoke-365TuneRevokeAzure .NOTES Author : Metawise Consulting LLC Module : 365TUNE Version : 1.9.0 #> [CmdletBinding()] param( [switch]$SkipAuth ) $displayName = "365TUNE - Security and Compliance" Write-Host "`n======================================================" -ForegroundColor Cyan Write-Host " 365TUNE - Revoke Azure Permissions" -ForegroundColor Cyan Write-Host "======================================================`n" -ForegroundColor Cyan # Step 1 - Check modules Write-Host "[1/5] Checking required modules..." -ForegroundColor Cyan foreach ($module in @("Az.Accounts", "Az.Resources")) { if (-not (Get-Module -ListAvailable -Name $module)) { Write-Host " Installing $module..." -ForegroundColor Yellow Install-Module -Name $module -Force -Scope CurrentUser } } Import-Module Az.Accounts, Az.Resources Write-Host " [OK] Modules ready." -ForegroundColor Green # Step 2 - Authenticate Write-Host "`n[2/5] Authenticating..." -ForegroundColor Cyan if (-not $SkipAuth) { $inCloudShell = ($env:ACC_CLOUD -eq "PROD") -or ($env:POWERSHELL_DISTRIBUTION_CHANNEL -like "*CloudShell*") -or ($env:AZUREPS_HOST_ENVIRONMENT -like "*cloud-shell*") if ($inCloudShell) { Connect-AzAccount -Identity -WarningAction SilentlyContinue | Out-Null } else { Disconnect-AzAccount -ErrorAction SilentlyContinue | Out-Null Connect-AzAccount -WarningAction SilentlyContinue | Out-Null } } $context = Get-AzContext if (-not $context) { throw "Not authenticated. Run without -SkipAuth or log in manually first." } Write-Host " Tenant : $($context.Tenant.Id)" -ForegroundColor Gray Write-Host " Account : $($context.Account.Id)" -ForegroundColor Gray Write-Host " [OK] Authenticated." -ForegroundColor Green # Step 3 - Fetch Service Principal via Graph REST (avoids Az.MSGraph dependency issues) Write-Host "`n[3/5] Looking up 365TUNE Service Principal..." -ForegroundColor Cyan $graphToken = (Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com" -ErrorAction Stop) $graphTokenStr = if ($graphToken.Token -is [System.Security.SecureString]) { [System.Net.NetworkCredential]::new("", $graphToken.Token).Password } else { $graphToken.Token } $graphHeaders = @{ Authorization = "Bearer $graphTokenStr"; "Content-Type" = "application/json" } $spFilter = [Uri]::EscapeDataString("displayName eq '$displayName'") $spResponse = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=$spFilter" -Headers $graphHeaders -Method GET $sp = $spResponse.value | Select-Object -First 1 if (-not $sp) { throw "Service Principal '$displayName' not found in this tenant." } $servicePrincipalId = $sp.id Write-Host " Object ID : $servicePrincipalId" # Step 4 - Elevate first (required to see and remove aadiam-scoped assignments) Write-Host "`n[4/5] Elevating and reconnecting for fresh token..." -ForegroundColor Cyan Write-Host " (Elevation is temporary - removed at end of script)" Invoke-365TuneElevation # Short sleep + forced token refresh so elevation is reflected Start-Sleep -Seconds 10 # Query assignments via REST (bypasses subscription-scope limitation of Get-AzRoleAssignment) $mgmtToken = Get-AzAccessToken -ResourceUrl "https://management.azure.com" -ErrorAction Stop -Force $mgmtTokenStr = if ($mgmtToken.Token -is [System.Security.SecureString]) { [System.Net.NetworkCredential]::new("", $mgmtToken.Token).Password } else { $mgmtToken.Token } $mgmtHeaders = @{ Authorization = "Bearer $mgmtTokenStr"; "Content-Type" = "application/json" } $readerRoleId = "acdd72a7-3385-48ef-bd42-f606fba81ae7" $rootAssignment = (Invoke-RestMethod -Uri "https://management.azure.com/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&`$filter=principalId eq '$servicePrincipalId'" -Headers $mgmtHeaders -Method GET).value | Where-Object { $_.properties.scope -eq "/" -and $_.properties.roleDefinitionId -like "*$readerRoleId*" } $aadIamAssignment = (Invoke-RestMethod -Uri "https://management.azure.com/providers/Microsoft.aadiam/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&`$filter=principalId eq '$servicePrincipalId'" -Headers $mgmtHeaders -Method GET).value | Where-Object { $_.properties.roleDefinitionId -like "*$readerRoleId*" } if (-not $rootAssignment -and -not $aadIamAssignment) { Write-Host "`n======================================================" -ForegroundColor Cyan Write-Host " No assignments found - nothing to revoke. [OK]" -ForegroundColor Green Write-Host "======================================================`n" -ForegroundColor Cyan Remove-365TuneElevation return } $foundCount = (($rootAssignment ? 1 : 0) + ($aadIamAssignment ? 1 : 0)) Write-Host " Found $foundCount assignment(s) - proceeding with removal." -ForegroundColor Yellow # Step 5 - Remove Write-Host "`n[5/5] Removing Reader permissions..." -ForegroundColor Cyan try { Remove-AzRoleAssignment ` -ObjectId $servicePrincipalId ` -Scope "/" ` -RoleDefinitionName "Reader" ` -SkipClientSideScopeValidation ` -ErrorAction Stop Write-Host " [OK] Reader removed from '/'" -ForegroundColor Green } catch { if ($_.Exception.Message -like "*does not exist*" -or $_.Exception.Message -like "*NotFound*" -or $_.Exception.Message -like "*does not map*" -or $_.Exception.Message -like "*Forbidden*") { Write-Warning " [WARN] Reader at '/' not found - already removed" } else { throw } } if ($aadIamAssignment) { try { Remove-AzRoleAssignment ` -ObjectId $servicePrincipalId ` -RoleDefinitionName "Reader" ` -Scope "/providers/Microsoft.aadiam" ` -SkipClientSideScopeValidation ` -ErrorAction Stop Write-Host " [OK] Reader removed from '/providers/Microsoft.aadiam'" -ForegroundColor Green } catch { if ($_.Exception.Message -like "*does not exist*" -or $_.Exception.Message -like "*NotFound*" -or $_.Exception.Message -like "*does not map*" -or $_.Exception.Message -like "*Forbidden*") { Write-Warning " [WARN] Reader at '/providers/Microsoft.aadiam' not found - already removed" } else { throw } } } else { Write-Warning " [WARN] Reader at '/providers/Microsoft.aadiam' not found - already removed" } # Verify via REST $mgmtToken2 = Get-AzAccessToken -ResourceUrl "https://management.azure.com" -ErrorAction Stop $mgmtTokenStr2 = if ($mgmtToken2.Token -is [System.Security.SecureString]) { [System.Net.NetworkCredential]::new("", $mgmtToken2.Token).Password } else { $mgmtToken2.Token } $mgmtHeaders2 = @{ Authorization = "Bearer $mgmtTokenStr2"; "Content-Type" = "application/json" } $readerRoleId = "acdd72a7-3385-48ef-bd42-f606fba81ae7" $remainRoot = (Invoke-RestMethod -Uri "https://management.azure.com/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&`$filter=principalId eq '$servicePrincipalId'" -Headers $mgmtHeaders2 -Method GET).value | Where-Object { $_.properties.scope -eq "/" -and $_.properties.roleDefinitionId -like "*$readerRoleId*" } $remainAadiam = (Invoke-RestMethod -Uri "https://management.azure.com/providers/Microsoft.aadiam/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&`$filter=principalId eq '$servicePrincipalId'" -Headers $mgmtHeaders2 -Method GET).value | Where-Object { $_.properties.roleDefinitionId -like "*$readerRoleId*" } if (-not $remainRoot -and -not $remainAadiam) { Write-Host " [OK] Verified - no assignments remain." -ForegroundColor Green } else { Write-Warning " [WARN] Assignments still remain - check Azure Portal." } Remove-365TuneElevation Write-Host "`n======================================================" -ForegroundColor Cyan Write-Host " 365TUNE Azure permissions revoked. [OK]" -ForegroundColor Green Write-Host " Tenant : $($context.Tenant.Id)" -ForegroundColor Green Write-Host " Account : $($context.Account.Id)" -ForegroundColor Green Write-Host "======================================================`n" -ForegroundColor Cyan } |