Public/Permissions/Invoke-365TuneTestTeams.ps1
|
function Invoke-365TuneTestTeams { <# .SYNOPSIS Tests whether Teams Reader Entra ID role is correctly assigned to the 365TUNE Enterprise App. .DESCRIPTION Checks that the 365TUNE Service Principal has the Teams Reader directory role assigned in Entra ID. Read-only - makes no changes. .EXAMPLE Invoke-365TuneTestTeams .NOTES Author : Metawise Consulting LLC Module : 365TUNE Version : 2.3.4 #> [CmdletBinding()] param( [switch]$SkipAuth ) $displayNameProd = "365TUNE - Security and Compliance" $displayNameBeta = "365TUNE - Security and Compliance - Beta" $teamsRoleName = "Teams Reader" $context = Get-AzContext Write-Host "`n======================================================" -ForegroundColor Cyan Write-Host " 365TUNE - Test Teams Permissions" -ForegroundColor Cyan Write-Host "======================================================`n" -ForegroundColor Cyan # Step 1 - Modules Write-Host "[1/4] Checking required modules..." -ForegroundColor Cyan if (-not (Get-Module -ListAvailable -Name "Az.Accounts")) { Install-Module -Name "Az.Accounts" -Scope CurrentUser } Import-Module Az.Accounts Write-Host " [OK] Modules ready." -ForegroundColor Green # Step 2 - Authenticate Write-Host "`n[2/4] 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) { # Cloud Shell always has MSI loaded; the portal also injects the user's context. # Switch to the first non-MSI context if available, otherwise try interactive login. $context = Get-AzContext if (-not $context -or $context.Account.Type -eq 'ManagedService') { $userContext = Get-AzContext -ListAvailable | Where-Object { $_.Account.Type -ne 'ManagedService' } | Select-Object -First 1 if ($userContext) { Write-Host " Switching to user context..." -ForegroundColor Yellow Set-AzContext -Context $userContext | Out-Null } else { # az CLI in Cloud Shell is always authenticated as the portal user. Write-Host " Bridging from Azure CLI session..." -ForegroundColor Yellow try { $armToken = az account get-access-token --resource https://management.azure.com --query accessToken -o tsv 2>$null $graphToken = az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv 2>$null $tenantId = az account show --query tenantId -o tsv 2>$null $accountId = az account show --query user.name -o tsv 2>$null if ($armToken -and $tenantId -and $accountId) { Connect-AzAccount -AccessToken $armToken -GraphAccessToken $graphToken -TenantId $tenantId -AccountId $accountId -WarningAction SilentlyContinue | Out-Null } } catch { Write-Verbose "Azure CLI bridge failed: $_" } } } } else { Disconnect-AzAccount -ErrorAction SilentlyContinue | Out-Null Connect-AzAccount -WarningAction SilentlyContinue | Out-Null } } $context = Get-AzContext if (-not $context) { throw "Not authenticated." } if ($context.Account.Type -eq 'ManagedService') { throw "Authenticated as Managed Service Identity. Run 'Connect-AzAccount' in Cloud Shell then re-run." } Write-Host " Tenant : $($context.Tenant.Id)" -ForegroundColor Gray Write-Host " Account : $($context.Account.Id)" -ForegroundColor Gray Write-Host " [OK] Authenticated." -ForegroundColor Green # Step 3 - Resolve SP and role via Graph Write-Host "`n[3/4] Resolving IDs via Graph..." -ForegroundColor Cyan try { $graphTokenObj = Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com" -ErrorAction Stop $graphToken = if ($graphTokenObj.Token -is [System.Security.SecureString]) { [System.Net.NetworkCredential]::new("", $graphTokenObj.Token).Password } else { $graphTokenObj.Token } } catch { $graphToken = az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv 2>$null if (-not $graphToken) { throw } } $headers = @{ Authorization = "Bearer $graphToken"; "Content-Type" = "application/json" } $sp = Find-365TuneSP -Name $displayNameProd -Headers $headers if (-not $sp) { Write-Host " '$displayNameProd' not found - trying Beta..." -ForegroundColor Yellow $sp = Find-365TuneSP -Name $displayNameBeta -Headers $headers } if (-not $sp) { throw "Service Principal not found. Ensure the 365TUNE app has been consented to in this tenant." } $spId = $sp.id Write-Host " Display Name : $($sp.displayName)" Write-Host " Object ID : $spId" $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 -ErrorAction Stop -TimeoutSec 30 $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 " [OK] IDs resolved." -ForegroundColor Green # Step 4 - Check assignment Write-Host "`n[4/4] Checking Teams role assignment..." -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 -ErrorAction Stop -TimeoutSec 30 $existing = $existingResponse.value | Select-Object -First 1 Write-Host "" if ($existing) { Write-Host " [OK] Teams Reader role ASSIGNED" -ForegroundColor Green } else { Write-Host " [FAIL] Teams Reader role MISSING" -ForegroundColor Red } Write-Host "" Write-Host "======================================================" -ForegroundColor Cyan if ($existing) { Write-Host " Teams permissions OK. [OK]" -ForegroundColor Green } else { Write-Host " Teams permissions INCOMPLETE. Run Invoke-365TuneConnectTeams." -ForegroundColor Red } Write-Host "======================================================`n" -ForegroundColor Cyan return [bool]$existing } |