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 " ✅ 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 " ✅ Authenticated." -ForegroundColor Green

    # Step 3 — Fetch Service Principal
    Write-Host "`n[3/5] Looking up 365TUNE Service Principal..." -ForegroundColor Cyan
    $sp = Get-AzADServicePrincipal -DisplayName $displayName | Select-Object Id, AppId, DisplayName
    if (-not $sp) {
        throw "Service Principal '$displayName' not found in this tenant."
    }
    $servicePrincipalId = $sp.Id
    Write-Host " Object ID : $servicePrincipalId"

    # Step 4 — Pre-flight check
    Write-Host "`n[4/5] Checking for existing assignments..." -ForegroundColor Cyan
    $existing       = Invoke-AzRestMethod `
        -Path   "providers/Microsoft.aadiam/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01" `
        -Method GET
    $existingValues = ($existing.Content | ConvertFrom-Json).value |
        Where-Object { $_.properties.principalId -eq $servicePrincipalId }

    if ($existingValues.Count -eq 0) {
        Write-Host "`n══════════════════════════════════════════════════════" -ForegroundColor Cyan
        Write-Host " No assignments found — nothing to revoke. ✅" -ForegroundColor Green
        Write-Host "══════════════════════════════════════════════════════`n" -ForegroundColor Cyan
        return
    }
    Write-Host " Found $($existingValues.Count) assignment(s) — proceeding with removal." -ForegroundColor Yellow

    # Step 5 — Elevate and remove
    Write-Host "`n[5/5] Elevating and removing Reader permissions..." -ForegroundColor Cyan
    Invoke-365TuneElevation

    try {
        Remove-AzRoleAssignment `
            -ObjectId           $servicePrincipalId `
            -Scope              "/" `
            -RoleDefinitionName "Reader" `
            -SkipClientSideScopeValidation `
            -ErrorAction Stop
        Write-Host " ✅ 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 " ⚠️ Reader at '/' not found — already removed"
        } else { throw }
    }

    $aadIamValues = $existingValues |
        Where-Object { $_.properties.scope -eq "/providers/Microsoft.aadiam" }

    if ($aadIamValues) {
        foreach ($assignment in $aadIamValues) {
            $del = Invoke-AzRestMethod `
                -Path   "providers/Microsoft.aadiam/providers/Microsoft.Authorization/roleAssignments/$($assignment.name)?api-version=2022-04-01" `
                -Method DELETE
            if ($del.StatusCode -in @(200, 204)) {
                Write-Host " ✅ Reader removed from '/providers/Microsoft.aadiam'" -ForegroundColor Green
            } else {
                Write-Warning " ⚠️ aadiam removal returned status $($del.StatusCode)"
            }
        }
    } else {
        Write-Warning " ⚠️ Reader at '/providers/Microsoft.aadiam' not found — already removed"
    }

    $remaining       = Invoke-AzRestMethod `
        -Path   "providers/Microsoft.aadiam/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01" `
        -Method GET
    $remainingValues = ($remaining.Content | ConvertFrom-Json).value |
        Where-Object { $_.properties.principalId -eq $servicePrincipalId }

    if ($remainingValues.Count -eq 0) {
        Write-Host " ✅ Verified — no assignments remain." -ForegroundColor Green
    } else {
        Write-Warning " ⚠️ $($remainingValues.Count) assignment(s) still remain — check Azure Portal."
    }

    Remove-365TuneElevation

    Write-Host "`n══════════════════════════════════════════════════════" -ForegroundColor Cyan
    Write-Host " 365TUNE Azure 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
}