Cuando comencé a trabajar con Microsoft Office 365, mi principal herramienta era PowerShell. Con el tiempo, y hasta hoy, la gestión ha evolucionado hacia el uso de Microsoft Graph.
Actualmente, gestiono entornos donde varios clientes comparten la administración de Intune, lo que genera la necesidad de obtener información detallada de ciertos dispositivos. Tras una investigación, desarrollé el siguiente script.
Este script en PowerShell automatiza la generación de informes sobre configuraciones aplicadas en dispositivos gestionados con Intune. Parte de un archivo TXT que contiene los nombres de los equipos. A partir de esta lista, se conecta a Microsoft Graph para localizar los dispositivos en el entorno de Intune, registrando también aquellos que no se encuentran.
Para cada equipo identificado, el script lanza una consulta que genera un “export job” en Intune. Este proceso crea un archivo comprimido con el estado de las políticas aplicadas. El script espera a que finalice la tarea, descarga el archivo, lo descomprime y extrae la información en formato CSV.
Posteriormente, los datos se normalizan para mejorar su legibilidad. Por ejemplo, estados como “failed” se muestran como “Error”, y ciertos tipos técnicos se traducen a términos más claros como “Endpoint Security” o “Settings Catalog”.
Durante la ejecución, se incluye una barra de progreso que permite visualizar el avance, especialmente útil cuando se procesan múltiples equipos.
Finalmente, toda la información se consolida en una tabla y se exporta a un archivo CSV en la ruta especificada. El resultado es un informe claro que permite identificar rápidamente qué políticas están aplicadas a cada dispositivo y su estado.
Como ejecutar el SCRIPT:
Reporte-IntuneDeviceConfigV01.ps1 -ComputerListFile C:\Temp\IntuneReport\Equipos.txt -OutputFolder "C:\Temp\IntuneReport
Código: Reporte-IntuneDeviceConfigV01.ps1
param(
[string]$ComputerListFile = "C:\Temp\equipos.txt",
[string]$OutputFolder = "C:\Temp\IntuneReport"
)
# =========================
# Configuración general
# =========================
$ErrorActionPreference = "Stop"
function Ensure-Folder {
param([string]$Path)
if (-not (Test-Path -Path $Path)) {
New-Item -ItemType Directory -Path $Path -Force | Out-Null
}
}
function Wait-ExportJob {
param(
[Parameter(Mandatory = $true)]
[string]$JobId
)
do {
Start-Sleep -Seconds 3
$job = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs('$JobId')"
Write-Host "Estado del export job: $($job.status)" -ForegroundColor DarkCyan
}
while ($job.status -in @("notStarted","inProgress"))
return $job
}
function Get-DeviceConfigurationStatusesForDevice {
param(
[Parameter(Mandatory = $true)]
[string]$IntuneDeviceId
)
$body = @{
reportName = "DeviceConfigurationPolicyStatusesV3"
format = "csv"
filter = "(IntuneDeviceId eq '$IntuneDeviceId')"
} | ConvertTo-Json -Depth 5
$job = Invoke-MgGraphRequest `
-Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs" `
-Body $body `
-ContentType "application/json"
$completedJob = Wait-ExportJob -JobId $job.id
if (-not $completedJob.url) {
throw "El export job finalizó sin URL de descarga."
}
$tempZip = Join-Path $env:TEMP ("IntuneReport_" + [guid]::NewGuid().ToString() + ".zip")
$tempDir = Join-Path $env:TEMP ("IntuneReport_" + [guid]::NewGuid().ToString())
Invoke-WebRequest -Uri $completedJob.url -OutFile $tempZip
Expand-Archive -Path $tempZip -DestinationPath $tempDir -Force
$csvFile = Get-ChildItem -Path $tempDir -Filter *.csv | Select-Object -First 1
if (-not $csvFile) {
throw "No se encontró el CSV dentro del ZIP exportado."
}
$rows = Import-Csv -Path $csvFile.FullName
Remove-Item $tempZip -Force -ErrorAction SilentlyContinue
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
return $rows
}
function Normalize-State {
param([string]$State)
if ([string]::IsNullOrWhiteSpace($State)) {
return ""
}
switch -Regex ($State.ToLower()) {
"success|succeeded" { "Succeeded"; break }
"error|failed|fail" { "Error"; break }
"conflict" { "Conflict"; break }
"pending" { "Pending"; break }
"notapplicable" { "NotApplicable"; break }
default { $State }
}
}
function Normalize-PolicyType {
param([string]$Type)
if ([string]::IsNullOrWhiteSpace($Type)) {
return ""
}
switch -Regex ($Type.ToLower()) {
"endpoint" { "Endpoint Security"; break }
"settings" { "Settings Catalog"; break }
"catalog" { "Settings Catalog"; break }
"device" { "Device configuration"; break }
"admin" { "Administrative Templates"; break }
default { $Type }
}
}
# =========================
# Inicio
# =========================
Ensure-Folder -Path $OutputFolder
$csvPath = Join-Path $OutputFolder "Intune_DeviceConfigurationStatuses.csv"
if (-not (Test-Path -Path $ComputerListFile)) {
throw "No se encontró el archivo TXT: $ComputerListFile"
}
# Leer nombres de equipos desde TXT
$ComputerNameFilter = Get-Content -Path $ComputerListFile |
ForEach-Object { $_.Trim() } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Select-Object -Unique
if (-not $ComputerNameFilter -or $ComputerNameFilter.Count -eq 0) {
throw "El archivo TXT no contiene nombres de equipos válidos."
}
Write-Host ""
Write-Host "Conectando a Microsoft Graph..." -ForegroundColor Cyan
Connect-MgGraph -Scopes "DeviceManagementManagedDevices.Read.All","DeviceManagementConfiguration.Read.All" | Out-Null
Write-Host "Consultando dispositivos administrados..." -ForegroundColor Cyan
$devicesUri = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$select=id,deviceName,userDisplayName,userPrincipalName"
$devicesResponse = Invoke-MgGraphRequest -Method GET -Uri $devicesUri
$devices = @()
$devices += $devicesResponse.value
while ($devicesResponse.'@odata.nextLink') {
$devicesResponse = Invoke-MgGraphRequest -Method GET -Uri $devicesResponse.'@odata.nextLink'
$devices += $devicesResponse.value
}
# Filtrar solo los equipos del TXT
$devices = $devices | Where-Object { $_.deviceName -in $ComputerNameFilter }
if (-not $devices -or $devices.Count -eq 0) {
Write-Warning "No se encontraron dispositivos en Intune que coincidan con los nombres del TXT."
return
}
Write-Host ("Total de equipos a procesar: {0}" -f $devices.Count) -ForegroundColor Yellow
$results = New-Object System.Collections.Generic.List[object]
$index = 0
foreach ($device in $devices) {
$index++
$percent = [math]::Round(($index / $devices.Count) * 100, 0)
Write-Progress -Activity "Consultando configuraciones aplicadas" `
-Status "Procesando $($device.deviceName) ($index de $($devices.Count))" `
-PercentComplete $percent
try {
$reportRows = Get-DeviceConfigurationStatusesForDevice -IntuneDeviceId $device.id
if ($reportRows -and $reportRows.Count -gt 0) {
foreach ($row in $reportRows) {
$results.Add([PSCustomObject]@{
"Nombre del equipo" = $row.DeviceName
"Policy" = $row.PolicyName
"Logged in user" = $row.UPN
"Policy type" = Normalize-PolicyType -Type $row.UnifiedPolicyType
"State" = Normalize-State -State $row.PolicyStatus
})
}
}
else {
$results.Add([PSCustomObject]@{
"Nombre del equipo" = $device.deviceName
"Policy" = "(Sin configuraciones reportadas)"
"Logged in user" = $device.userPrincipalName
"Policy type" = ""
"State" = ""
})
}
}
catch {
$results.Add([PSCustomObject]@{
"Nombre del equipo" = $device.deviceName
"Policy" = "(Error consultando reporte)"
"Logged in user" = $device.userPrincipalName
"Policy type" = ""
"State" = $_.Exception.Message
})
}
}
# Agregar equipos del TXT que no existan en Intune
$foundDeviceNames = @($devices.deviceName | ForEach-Object { $_.Trim() })
$notFoundDevices = $ComputerNameFilter | Where-Object { $_.Trim() -notin $foundDeviceNames }
foreach ($missingDevice in $notFoundDevices) {
$results.Add([PSCustomObject]@{
"Nombre del equipo" = $missingDevice
"Policy" = "(Equipo no encontrado en Intune)"
"Logged in user" = ""
"Policy type" = ""
"State" = ""
})
}
Write-Progress -Activity "Consultando configuraciones aplicadas" -Completed
$results = $results | Sort-Object "Nombre del equipo","Policy"
Write-Host ""
Write-Host "Resultado:" -ForegroundColor Green
$results | Format-Table -AutoSize
$results | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
Write-Host ""
Write-Host "Reporte exportado en: $csvPath" -ForegroundColor Green
Si este artículo te ha resultado útil, puedes dejar tu comentario. Para cualquier duda, puedes contactarme a través del correo electrónico disponible en mi perfil.
