<# .SYNOPSIS ¿Quién tiene roles de directorio activos y tiene MFA registrado? .DESCRIPTION Lista las asignaciones activas de roles de Microsoft Entra (permanentes y elegibles ya activadas en PIM), abre los grupos con rol para ver a sus miembros y cruza cada usuario con el reporte de registro de MFA. Las asignaciones elegibles que no están activadas no aparecen. Solo lectura: no cambia nada en el tenant. .PARAMETER RutaCsv Ruta del CSV de salida. Si se omite, solo se muestra en pantalla. .EXAMPLE .\get-admins-privilegiados.ps1 -RutaCsv .\admins.csv .NOTES Permisos: RoleManagement.Read.Directory, AuditLog.Read.All, User.Read.All, GroupMember.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( [string]$RutaCsv ) $ErrorActionPreference = 'Stop' $permisos = @('RoleManagement.Read.Directory', 'AuditLog.Read.All', 'User.Read.All', 'GroupMember.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, [hashtable]$Encabezados = @{}) while ($Uri) { $pagina = Invoke-MgGraphRequest -Method GET -Uri $Uri -Headers $Encabezados $pagina.value $Uri = $pagina.'@odata.nextLink' } } $base = 'https://graph.microsoft.com/v1.0' $roles = @{} foreach ($d in Get-TodasLasPaginas "$base/roleManagement/directory/roleDefinitions") { $roles[$d.id] = $d.displayName } $mfa = @{} foreach ($r in Get-TodasLasPaginas "$base/reports/authenticationMethods/userRegistrationDetails") { $mfa[$r.id] = [bool]$r.isMfaRegistered } function New-FilaUsuario { param($Usuario, [string]$Rol, [string]$Alcance, [string]$Via) [pscustomobject]@{ Nombre = $Usuario.displayName Cuenta = $Usuario.userPrincipalName Tipo = 'Usuario' Rol = $Rol Alcance = $Alcance AsignadoPor = $Via Habilitada = if ($Usuario.accountEnabled -eq $false) { 'No' } elseif ($Usuario.accountEnabled) { 'Sí' } else { 'Sin dato' } # El reporte de registro no incluye cuentas deshabilitadas. MfaRegistrado = if ($mfa.ContainsKey($Usuario.id)) { if ($mfa[$Usuario.id]) { 'Sí' } else { 'No' } } else { 'Sin dato' } } } $asignaciones = Get-TodasLasPaginas "$base/roleManagement/directory/roleAssignments?`$expand=principal" $filas = foreach ($a in $asignaciones) { $rol = if ($roles[$a.roleDefinitionId]) { $roles[$a.roleDefinitionId] } else { $a.roleDefinitionId } $alcance = if ($a.directoryScopeId -eq '/') { 'Todo el tenant' } else { $a.directoryScopeId } $principal = $a.principal switch ($principal.'@odata.type') { '#microsoft.graph.user' { New-FilaUsuario -Usuario $principal -Rol $rol -Alcance $alcance -Via 'Directo' } '#microsoft.graph.group' { # Con conversión OData y $select la API exige ConsistencyLevel y $count. $miembros = Get-TodasLasPaginas "$base/groups/$($principal.id)/transitiveMembers/microsoft.graph.user?`$count=true&`$select=id,displayName,userPrincipalName,accountEnabled" @{ ConsistencyLevel = 'eventual' } foreach ($m in $miembros) { New-FilaUsuario -Usuario $m -Rol $rol -Alcance $alcance -Via "Grupo: $($principal.displayName)" } } default { [pscustomobject]@{ Nombre = if ($principal.displayName) { $principal.displayName } else { $a.principalId } Cuenta = $a.principalId Tipo = if ($principal.'@odata.type' -eq '#microsoft.graph.servicePrincipal') { 'Aplicación' } else { 'Desconocido' } Rol = $rol Alcance = $alcance AsignadoPor = 'Directo' Habilitada = 'Sin dato' MfaRegistrado = 'No aplica' } } } } $filas = $filas | Sort-Object Rol, Nombre Write-Host "Asignaciones activas: $(@($filas).Count). Usuarios con rol y sin MFA: $(@($filas | Where-Object MfaRegistrado -eq 'No').Count)" if ($RutaCsv) { # Excel ejecutaría como fórmula un nombre que empiece con estos caracteres. foreach ($fila in $filas) { foreach ($p in $fila.PSObject.Properties) { if ($p.Value -is [string] -and $p.Value -match '^[=+\-@]') { $p.Value = "'" + $p.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 }