Exclusiones de cuentas de emergencia
Revisa si tus cuentas de emergencia están excluidas de cada política de acceso condicional habilitada, de forma directa, por grupo o por rol. Evita que una política te deje fuera del tenant.
Ejemplo en México
Hotelera en Cancún, 500 personas
Un consultor agregó políticas nuevas para exigir dispositivos compatibles, y TI quiere asegurarse de no quedarse sin acceso si falla el servicio de MFA en plena temporada alta.
.\get-exclusiones-cuentas-emergencia.ps1 -CuentasEmergencia emergencia1@empresa.example,emergencia2@empresa.example -RutaCsv .\exclusiones.csvVe que las dos cuentas no están excluidas de una política nueva. Las agrega con el grupo de emergencia y repite la prueba de acceso.
Cómo implementarlo
- Ten a la mano los UPN de tus cuentas de emergencia.
- Corre el script con -CuentasEmergencia y -RutaCsv e inicia sesión con una cuenta con el rol Lector global.
- Busca en el CSV las filas con NO EXCLUIDA o CUENTA NO ENCONTRADA.
- Agrega la cuenta o su grupo a las exclusiones de esas políticas, y prueba que la cuenta de emergencia pueda entrar.
Ver el código completo de get-exclusiones-cuentas-emergencia.ps1
<#
.SYNOPSIS
¿Tus cuentas de emergencia están excluidas de todas las políticas de acceso condicional habilitadas?
.DESCRIPTION
Para cada cuenta revisa cada política habilitada y busca la exclusión directa, por grupo o por rol.
Las políticas en solo informe no se revisan porque no bloquean, y tampoco las que solo aplican a identidades de carga de trabajo.
Una cuenta de emergencia sujeta a una política puede quedar inservible justo cuando la necesitas.
Solo lectura: no cambia nada en el tenant.
.PARAMETER CuentasEmergencia
Nombres principales (UPN) de las cuentas de emergencia.
.PARAMETER RutaCsv
Ruta del CSV de salida. Si se omite, solo se muestra en pantalla.
.EXAMPLE
.\get-exclusiones-cuentas-emergencia.ps1 -CuentasEmergencia emergencia1@empresa.example,emergencia2@empresa.example -RutaCsv .\exclusiones.csv
.NOTES
Permisos: Policy.Read.All, Directory.Read.All
Rol mínimo: Lector global (Global Reader)
Licencia: Microsoft Entra ID P1
Licencia del código: MIT. Se entrega TAL CUAL, sin garantía. Pruébalo primero en un tenant de pruebas.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string[]]$CuentasEmergencia,
[string]$RutaCsv
)
$ErrorActionPreference = 'Stop'
$permisos = @('Policy.Read.All', 'Directory.Read.All')
if (-not (Get-Command Connect-MgGraph -ErrorAction SilentlyContinue)) {
throw 'Falta el módulo. Instálalo con: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser'
}
Connect-MgGraph -Scopes $permisos -NoWelcome
function Get-TodasLasPaginas {
param([string]$Uri)
while ($Uri) {
$pagina = Invoke-MgGraphRequest -Method GET -Uri $Uri
$pagina.value
$Uri = $pagina.'@odata.nextLink'
}
}
$base = 'https://graph.microsoft.com/v1.0'
$politicas = @(Get-TodasLasPaginas "$base/identity/conditionalAccess/policies" | Where-Object {
$u = $_.conditions.users
# Una política sin usuarios incluidos es de identidades de carga de trabajo: no aplica a una cuenta.
$_.state -eq 'enabled' -and ($u.includeUsers -or $u.includeGroups -or $u.includeRoles -or $u.includeGuestsOrExternalUsers)
})
$filas = foreach ($cuenta in $CuentasEmergencia) {
$usuario = $null
try {
$usuario = Invoke-MgGraphRequest -Method GET -Uri "$base/users/$([uri]::EscapeDataString($cuenta))?`$select=id,displayName,userPrincipalName"
} catch {
# Solo "no existe" se reporta; un error de permisos no debe parecer un resultado.
if ("$_" -notmatch 'NotFound|Not Found|404') { throw }
}
if (-not $usuario.id) {
[pscustomobject]@{ Cuenta = $cuenta; Politica = $null; Resultado = 'CUENTA NO ENCONTRADA'; ExcluidaPor = $null }
continue
}
$membresias = @(Get-TodasLasPaginas "$base/users/$($usuario.id)/transitiveMemberOf")
foreach ($p in $politicas) {
$u = $p.conditions.users
$via = @(
if (@($u.excludeUsers) -contains $usuario.id) { 'Directa' }
foreach ($m in $membresias) {
if ($m.id -and @($u.excludeGroups) -contains $m.id) { "Grupo: $($m.displayName)" }
if ($m.roleTemplateId -and @($u.excludeRoles) -contains $m.roleTemplateId) { "Rol: $($m.displayName)" }
}
)
[pscustomobject]@{
Cuenta = $usuario.userPrincipalName
Politica = $p.displayName
Resultado = if ($via) { 'Excluida' } else { 'NO EXCLUIDA' }
ExcluidaPor = $via -join '; '
}
}
}
$pendientes = @($filas | Where-Object Resultado -ne 'Excluida')
Write-Host "Políticas habilitadas revisadas: $($politicas.Count). Hallazgos: $($pendientes.Count)"
if ($RutaCsv) {
# Excel ejecutaría como fórmula un nombre que empiece con estos caracteres.
foreach ($fila in $filas) { foreach ($c in $fila.PSObject.Properties) { if ($c.Value -is [string] -and $c.Value -match '^[=+\-@]') { $c.Value = "'" + $c.Value } } }
# UTF-8 con BOM para que Excel en español muestre bien los acentos.
$filas | Export-Csv -Path $RutaCsv -NoTypeInformation -Encoding utf8BOM
Write-Host "CSV guardado en $RutaCsv"
} else {
$filas | Format-Table -AutoSize
}