<# .SYNOPSIS ¿Qué cuentas llevan N días o más sin un inicio de sesión correcto? .DESCRIPTION Revisa signInActivity de cada usuario y compara lastSuccessfulSignInDateTime contra el límite. Usa el último inicio correcto porque los intentos fallidos también actualizan las otras fechas. Omite cuentas creadas dentro del periodo, que todavía no han tenido oportunidad de entrar. Solo lectura: no cambia nada en el tenant. .PARAMETER RutaCsv Ruta del CSV de salida. Si se omite, solo se muestra en pantalla. .PARAMETER Dias Días sin iniciar sesión para considerar inactiva una cuenta. Por defecto, 90. .PARAMETER ExcluirInvitados Deja fuera a los usuarios invitados. .PARAMETER ZonaHoraria Zona horaria para mostrar las fechas. Por defecto, Central Standard Time (Mexico). .EXAMPLE .\get-cuentas-inactivas.ps1 -Dias 60 -ExcluirInvitados -RutaCsv .\inactivas.csv .NOTES Permisos: AuditLog.Read.All, User.Read.All Rol mínimo: Lector de informes (Reports 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, [ValidateRange(1, 36500)][int]$Dias = 90, [switch]$ExcluirInvitados, [string]$ZonaHoraria = 'Central Standard Time (Mexico)' ) $ErrorActionPreference = 'Stop' $permisos = @('AuditLog.Read.All', 'User.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' } $zona = [TimeZoneInfo]::FindSystemTimeZoneById($ZonaHoraria) Connect-MgGraph -Scopes $permisos -NoWelcome function ConvertTo-FechaUtc { param($Valor) # Graph puede dar texto o fecha; vacío o el año 0001 significan que no hay dato. if (-not $Valor) { return $null } $fecha = if ($Valor -is [datetimeoffset]) { $Valor.UtcDateTime } elseif ($Valor -is [datetime]) { if ($Valor.Kind -eq 'Unspecified') { [datetime]::SpecifyKind($Valor, 'Utc') } else { $Valor.ToUniversalTime() } } else { [datetimeoffset]::Parse("$Valor", [cultureinfo]::InvariantCulture).UtcDateTime } if ($fecha.Year -le 1) { return $null } $fecha } function Format-FechaLocal { param($FechaUtc) if ($FechaUtc) { [TimeZoneInfo]::ConvertTimeFromUtc($FechaUtc, $zona).ToString('yyyy-MM-dd HH:mm', [cultureinfo]::InvariantCulture) } } $ahora = [datetime]::UtcNow $limite = $ahora.AddDays(-$Dias) # Con signInActivity la página máxima es de 500. $uri = 'https://graph.microsoft.com/v1.0/users?$select=id,displayName,userPrincipalName,userType,accountEnabled,createdDateTime,signInActivity&$top=500' $usuarios = while ($uri) { $pagina = Invoke-MgGraphRequest -Method GET -Uri $uri $pagina.value $uri = $pagina.'@odata.nextLink' } $filas = foreach ($u in $usuarios) { if ($ExcluirInvitados -and $u.userType -eq 'Guest') { continue } $ultimo = ConvertTo-FechaUtc $u.signInActivity.lastSuccessfulSignInDateTime if ($ultimo -and $ultimo -ge $limite) { continue } $creada = ConvertTo-FechaUtc $u.createdDateTime if (-not $ultimo -and $creada -and $creada -ge $limite) { continue } $intentos = @((ConvertTo-FechaUtc $u.signInActivity.lastSignInDateTime), (ConvertTo-FechaUtc $u.signInActivity.lastNonInteractiveSignInDateTime)) | Where-Object { $_ } | Sort-Object -Descending [pscustomobject]@{ Nombre = $u.displayName Cuenta = $u.userPrincipalName Tipo = if ($u.userType -eq 'Guest') { 'Invitado' } else { 'Miembro' } Habilitada = if ($u.accountEnabled) { 'Sí' } else { 'No' } UltimoInicioOk = if ($ultimo) { Format-FechaLocal $ultimo } else { 'Sin inicio correcto registrado' } DiasSinIniciar = if ($ultimo) { [int][math]::Floor(($ahora - $ultimo).TotalDays) } else { $null } UltimoIntento = Format-FechaLocal @($intentos)[0] CreadaEl = Format-FechaLocal $creada } } Write-Host "Cuentas sin inicio de sesión correcto en $Dias días: $(@($filas).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 }