<# .SYNOPSIS ¿Qué acciones de Microsoft Secure Score me faltan y cuáles dan más puntos? .DESCRIPTION Cruza los perfiles de control con la puntuación más reciente y lista las acciones con puntos pendientes, de mayor a menor. Omite controles retirados y los que marcaste como ignorados o resueltos por un tercero. 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-acciones-puntuacion.ps1 -RutaCsv .\acciones.csv .NOTES Permisos: SecurityEvents.Read.All Rol mínimo: Lector de seguridad Licencia: Sin plan específico: Secure Score muestra recomendaciones de los productos que tengas con licencia (Microsoft Entra ID, Exchange Online, Microsoft Defender, entre otros). Aplica a Microsoft 365 Business Premium 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 = @('SecurityEvents.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 $reciente = @((Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/security/secureScores?$top=1').value)[0] if (-not $reciente) { throw 'Graph no devolvió puntuaciones para este tenant.' } $puntosPorControl = @{} foreach ($c in $reciente.controlScores) { $puntosPorControl["$($c.controlName)"] = [double]$c.score } $uri = 'https://graph.microsoft.com/v1.0/security/secureScoreControlProfiles' $perfiles = while ($uri) { $pagina = Invoke-MgGraphRequest -Method GET -Uri $uri $pagina.value $uri = $pagina.'@odata.nextLink' } $filas = foreach ($p in $perfiles) { if ($p.deprecated) { continue } $ultimo = @($p.controlStateUpdates | Sort-Object { if ($_.updatedDateTime) { [datetimeoffset]"$($_.updatedDateTime)" } else { [datetimeoffset]::MinValue } })[-1] if ("$($ultimo.state)" -in @('Ignored', 'ThirdParty')) { continue } $maximo = [double]$p.maxScore $actuales = [double]$puntosPorControl["$($p.id)"] $pendientes = $maximo - $actuales if ($pendientes -le 0) { continue } [pscustomobject]@{ Accion = $p.title PuntosPendientes = $pendientes PuntosActuales = $actuales PuntosMaximos = $maximo Avance = if ($actuales -gt 0) { 'Parcial' } else { 'Sin iniciar' } Categoria = $p.controlCategory Servicio = $p.service ImpactoUsuario = $p.userImpact Costo = $p.implementationCost Rango = $p.rank Liga = $p.actionUrl } } $filas = $filas | Sort-Object @{ Expression = 'PuntosPendientes'; Descending = $true }, Rango Write-Host "Acciones pendientes: $(@($filas).Count), con $(($filas | Measure-Object PuntosPendientes -Sum).Sum) puntos por ganar" if ($RutaCsv) { # 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 }