Public/Permissions/Invoke-365TuneRevokeTeams.ps1
|
function Invoke-365TuneRevokeTeams { <# .SYNOPSIS Revokes the Teams Reader Entra ID role from the 365TUNE Enterprise App. .DESCRIPTION Removes the "Teams Reader" directory role assignment from the 365TUNE Service Principal. Safe to re-run — exits cleanly if no assignment found. Works in both local PowerShell and Azure Cloud Shell. Uses Microsoft Graph REST API — no additional modules required beyond Az.Accounts. Your account must have Global Administrator or Privileged Role Administrator rights. .EXAMPLE Invoke-365TuneRevokeTeams .NOTES Author : Metawise Consulting LLC Module : 365TUNE Version : 2.1.6 #> [CmdletBinding()] param( [switch]$SkipAuth ) $displayNameProd = "365TUNE - Security and Compliance" $displayNameBeta = "365TUNE - Security and Compliance - Beta" $teamsRoleName = "Teams Reader" Write-Host "`n══════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " 365TUNE — Revoke Teams Permissions" -ForegroundColor Cyan Write-Host "══════════════════════════════════════════════════════`n" -ForegroundColor Cyan # Step 1 — Check modules Write-Host "[1/4] Checking required modules..." -ForegroundColor Cyan foreach ($module in @("Az.Accounts")) { if (-not (Get-Module -ListAvailable -Name $module)) { Write-Host " Installing $module..." -ForegroundColor Yellow Install-Module -Name $module -Force -Scope CurrentUser -AllowClobber } } Import-Module Az.Accounts Write-Host " ✅ Modules ready." -ForegroundColor Green # Detect Cloud Shell $inCloudShell = ($env:ACC_CLOUD -eq "PROD") -or ($env:POWERSHELL_DISTRIBUTION_CHANNEL -like "*CloudShell*") -or ($env:AZUREPS_HOST_ENVIRONMENT -like "*cloud-shell*") # Step 2 — Authenticate Write-Host "`n[2/4] Authenticating..." -ForegroundColor Cyan if (-not $SkipAuth) { if ($inCloudShell) { Write-Host " Cloud Shell detected — using existing session." -ForegroundColor Gray } else { Disconnect-AzAccount -ErrorAction SilentlyContinue | Out-Null Connect-AzAccount -WarningAction SilentlyContinue | Out-Null } } $context = Get-AzContext if (-not $context) { throw "Not authenticated. Please try again." } Write-Host " Tenant : $($context.Tenant.Id)" -ForegroundColor Gray Write-Host " Account : $($context.Account.Id)" -ForegroundColor Gray Write-Host " ✅ Authenticated." -ForegroundColor Green # Step 3 — Resolve IDs via Graph Write-Host "`n[3/4] Resolving IDs..." -ForegroundColor Cyan $graphTokenObj = Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com" if ($graphTokenObj.Token -is [System.Security.SecureString]) { $graphToken = [System.Net.NetworkCredential]::new("", $graphTokenObj.Token).Password } else { $graphToken = $graphTokenObj.Token } $headers = @{ Authorization = "Bearer $graphToken"; "Content-Type" = "application/json" } # Find 365TUNE SP function Find-365TuneSP ($name) { $encoded = [Uri]::EscapeDataString("displayName eq '$name'") $response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=$encoded" -Headers $headers -Method GET $response.value | Select-Object -First 1 } $sp = Find-365TuneSP $displayNameProd if (-not $sp) { Write-Host " '$displayNameProd' not found — trying Beta..." -ForegroundColor Yellow $sp = Find-365TuneSP $displayNameBeta } if (-not $sp) { throw "Service Principal not found. Tried '$displayNameProd' and '$displayNameBeta'. Ensure the app has been consented to in this tenant." } $displayName = $sp.displayName $spId = $sp.id Write-Host " Display Name : $displayName" Write-Host " Object ID : $spId" # Find Teams Reader role definition $encoded = [Uri]::EscapeDataString("displayName eq '$teamsRoleName'") $roleResponse = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions?`$filter=$encoded" -Headers $headers -Method GET $roleDef = $roleResponse.value | Select-Object -First 1 if (-not $roleDef) { throw "Entra ID role '$teamsRoleName' not found." } $roleDefId = $roleDef.id Write-Host " Role : $($roleDef.displayName)" Write-Host " ✅ IDs resolved." -ForegroundColor Green # Step 4 — Remove Teams Reader role Write-Host "`n[4/4] Removing $teamsRoleName role..." -ForegroundColor Cyan $existingFilter = [Uri]::EscapeDataString("principalId eq '$spId' and roleDefinitionId eq '$roleDefId' and directoryScopeId eq '/'") $existingResponse = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?`$filter=$existingFilter" -Headers $headers -Method GET $existing = $existingResponse.value | Select-Object -First 1 if (-not $existing) { Write-Host "`n══════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " No Teams permissions found — nothing to revoke. ✅" -ForegroundColor Green Write-Host "══════════════════════════════════════════════════════`n" -ForegroundColor Cyan return } try { Invoke-RestMethod ` -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments/$($existing.id)" ` -Headers $headers -Method DELETE -ErrorAction Stop Write-Host " ✅ $teamsRoleName role removed." -ForegroundColor Green } catch { if ($_.Exception.Message -like "*NotFound*" -or $_.Exception.Message -like "*does not exist*") { Write-Warning " ⚠️ Role assignment not found — already removed" } else { throw } } Write-Host "`n══════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " 365TUNE Teams permissions revoked. ✅" -ForegroundColor Green Write-Host " Tenant : $($context.Tenant.Id)" -ForegroundColor Green Write-Host " Account : $($context.Account.Id)" -ForegroundColor Green Write-Host "══════════════════════════════════════════════════════`n" -ForegroundColor Cyan } |