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
}