Saltar al contenido
Microsoft scripts

Scripts de Entra

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.

Antes de correrloNO EXCLUIDA no significa que la política le aplique a esa cuenta: el script no evalúa a quién incluye, condiciones ni controles, solo la exclusión. Microsoft recomienda excluirlas de las políticas que bloqueen o restrinjan el inicio de sesión. Las políticas en solo informe no se revisan porque no bloquean. Los grupos con membresía oculta pueden no verse sin un permiso adicional.

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.csv

Ve 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

  1. Ten a la mano los UPN de tus cuentas de emergencia.
  2. Corre el script con -CuentasEmergencia y -RutaCsv e inicia sesión con una cuenta con el rol Lector global.
  3. Busca en el CSV las filas con NO EXCLUIDA o CUENTA NO ENCONTRADA.
  4. 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
}

Relacionados