Guías · Nivel 3

Microsoft Graph con PowerShell: las primeras consultas y un informe que no existe en el portal

El SDK, los ámbitos de permiso y por qué pedir los mínimos, Graph Explorer para encontrar la consulta, paginación, y el informe de dispositivos sin check-in con su usuario y su manager en un CSV.

Qué vas a conseguir

Hacer con un script lo que el portal te obliga a hacer a clics: cruzar dispositivos de Intune con usuarios y managers de Entra, sacar un CSV cada lunes y, cuando lo tengas claro, automatizarlo sin que nadie inicie sesión. Es la base de los repos de mi GitHub que leen el tenant.

Antes de empezar

  • PowerShell 7 (pwsh), en Windows, macOS o Linux.
  • Una cuenta con los roles de lectura necesarios: Global Reader basta para todo lo de esta guía.
  • Para la automatización final, permiso para crear un registro de aplicación (o que lo cree el administrador de identidad con los permisos que le pidas).

1. El SDK y la primera conexión

pwsh
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Users, Microsoft.Graph.DeviceManagement -Scope CurrentUser -ForceConnect-MgGraph -Scopes "User.Read.All","Device.Read.All","DeviceManagementManagedDevices.Read.All" -NoWelcomeGet-MgContext | Select-Object Account, TenantId, @{n="Scopes";e={$_.Scopes -join ", "}}Account                  TenantId                             Scopes-------                  --------                             ------inigo.admin@contoso.com  7f1c…-…-…                            Device.Read.All, DeviceManagementManagedDevices.Read.All, User.Read.All, ...Get-MgUser -UserId laura.prado@contoso.com -Property displayName,department,jobTitle,accountEnabled | Format-List displayName,department,jobTitle,accountEnableddisplayName    : Laura Pradodepartment     : FinanzasjobTitle       : AnalistaaccountEnabled : True

Dos cosas que importan desde el primer día. Instala solo los módulos que necesitas (Microsoft.Graph completo son 40 módulos y tarda diez minutos). Y pide solo los ámbitos que la tarea necesita: -Scopes es lo que un administrador te concede y lo que un auditor verá; Directory.ReadWrite.All para un informe de lectura es lo que no quieres explicar.

2. Encontrar la consulta: Graph Explorer

Graph Explorer · https://developer.microsoft.com/graph/graph-explorer
GETv1.0https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$filter=lastSyncDateTime le 2026-09-22T00:00:00Z&$select=deviceName,userPrincipalName,operatingSystem,lastSyncDateTime,complianceState&$top=5
Permisos modificadosDeviceManagementManagedDevices.Read.All ✓ consentido
Respuesta 200 OK
{ "value": [
{ "deviceName": "LAPTOP-3K8MQ2", "userPrincipalName": "j.navarro@contoso.com", "operatingSystem": "Windows", "lastSyncDateTime": "2026-08-30T06:12:44Z", "complianceState": "noncompliant" },
{ "deviceName": "Laura's iPhone", "userPrincipalName": "laura.prado@contoso.com", "operatingSystem": "iOS", "lastSyncDateTime": "2026-09-12T19:40:02Z", "complianceState": "compliant" } ] }
Ejecutar consulta
Fragmentos de código

Graph Explorer es donde pruebas la URL antes de escribir una línea de PowerShell: la pestaña Permisos modificados te dice exactamente qué ámbito necesita y Fragmentos de código te da el cmdlet equivalente. La regla de oro: $select siempre (menos datos, más rápido) y $filter en el servidor, no Where-Object en tu máquina con 5.000 dispositivos descargados.

3. Paginación: lo que rompe los scripts

pwsh
$r = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users?`$select=id,userPrincipalName&`$top=999"$r.value.Count; $r.'@odata.nextLink' -ne $null999True# con el cmdlet, -All recorre todas las páginas por ti:$users = Get-MgUser -All -Property id,userPrincipalName,accountEnabled -PageSize 999$users.Count3412

Graph devuelve como mucho 999 objetos por llamada y un @odata.nextLink para la siguiente página. Un script que "funcionaba" con 800 usuarios y devuelve 999 de 3.412 cuando la empresa crece no da error: da datos incompletos. -All en los cmdlets o un bucle sobre nextLink con Invoke-MgGraphRequest es obligatorio.

4. El informe: dispositivos sin check-in, con usuario y manager

Get-StaleDevices.ps1
#Requires -Modules Microsoft.Graph.Authentication, Microsoft.Graph.Users, Microsoft.Graph.DeviceManagementparam([int]$Days = 30, [string]$Out = "stale-devices-$(Get-Date -Format yyyy-MM-dd).csv") Connect-MgGraph -Scopes "User.Read.All","DeviceManagementManagedDevices.Read.All" -NoWelcome$since = (Get-Date).AddDays(-$Days).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") $devices = Get-MgDeviceManagementManagedDevice -All -Filter "lastSyncDateTime le $since" `  -Property deviceName,userPrincipalName,operatingSystem,osVersion,lastSyncDateTime,complianceState,managedDeviceOwnerType $cache = @{}$rows = foreach ($d in $devices) {  $mgr = $null  if ($d.UserPrincipalName) {    if (-not $cache.ContainsKey($d.UserPrincipalName)) {      $u = Get-MgUser -UserId $d.UserPrincipalName -Property accountEnabled,department -ExpandProperty manager -ErrorAction SilentlyContinue      $cache[$d.UserPrincipalName] = $u    }    $u = $cache[$d.UserPrincipalName]    $mgr = $u.Manager.AdditionalProperties.userPrincipalName  }  [pscustomobject]@{    Device = $d.DeviceName; OS = "$($d.OperatingSystem) $($d.OsVersion)"; Owner = $d.ManagedDeviceOwnerType    LastSync = $d.LastSyncDateTime; DaysSilent = [int]((Get-Date) - $d.LastSyncDateTime).TotalDays    Compliance = $d.ComplianceState; User = $d.UserPrincipalName; UserEnabled = $u.AccountEnabled    Department = $u.Department; Manager = $mgr  }}$rows | Sort-Object DaysSilent -Descending | Export-Csv $Out -NoTypeInformation -Encoding utf8"$($rows.Count) dispositivos sin check-in en $Days días -> $Out"
pwsh
./Get-StaleDevices.ps1 -Days 3047 dispositivos sin check-in en 30 días -> stale-devices-2026-10-22.csvImport-Csv .\stale-devices-2026-10-22.csv | Where-Object UserEnabled -eq False | Select-Object Device, User, DaysSilent, Manager | Format-TableDevice         User                     DaysSilent Manager------         ----                     ---------- -------LAPTOP-3K8MQ2  j.navarro@contoso.com            53 m.ortega@contoso.comDESKTOP-Q71A   p.ruiz@contoso.com               41 m.ortega@contoso.com

Esto es lo que el portal no te da en una pantalla: el equipo que lleva 53 días sin sincronizar pertenece a un usuario deshabilitado (una baja), y quien tiene que devolverlo es su manager. La caché de usuarios evita 47 llamadas repetidas al mismo manager. El CSV va al ticket de bajas y, con la guía de Jenkins, el script se prueba en cada cambio.

5. Automatizar sin usuario: registro de aplicación

Entra admin center · Registros de aplicaciones
IdentidadAplicacionesRegistros de aplicacionesNuevo registro
Nombreit-reports-graph
Tipos de cuenta admitidosSolo esta organización
it-reports-graphPermisos de APIAgregar un permisoMicrosoft GraphPermisos de aplicación
User.Read.All
DeviceManagementManagedDevices.Read.All
Conceder consentimiento de administrador para Contoso
it-reports-graphCertificados y secretosCertificadosCargar certificado
Huella digitalA1B2…F0 · Expira: 22/10/2027
pwsh · servidor de tareas
$cert = New-SelfSignedCertificate -Subject "CN=it-reports-graph" -CertStoreLocation Cert:\CurrentUser\My -KeyExportPolicy NonExportable -NotAfter (Get-Date).AddYears(1)Export-Certificate -Cert $cert -FilePath .\it-reports-graph.cer     # este .cer es el que se sube al registroConnect-MgGraph -ClientId "0a1b…-…" -TenantId "7f1c…-…" -CertificateThumbprint $cert.Thumbprint -NoWelcome(Get-MgContext).AuthTypeAppOnly

Para la tarea programada, el script usa certificado, no secreto de cliente: la clave privada no sale del servidor y no hay nada que copiar en un fichero. Los permisos de aplicación (no delegados) son los que importan aquí, y el consentimiento del administrador queda en el registro de auditoría. El repo 20 de mi GitHub revisa cada mes estos registros: quién tiene qué permiso y cuándo caduca el certificado.

6. Lo que revisas cada mes

  • Registros de aplicaciones de IT: permisos concedidos frente a los usados (Registros de aplicaciones → la app → Permisos de API), certificados que caducan en 90 días.
  • Que ningún script del equipo usa Directory.ReadWrite.All o Mail.ReadWrite "por si acaso".
  • Los -Scopes interactivos que has consentido tú mismo: Aplicaciones empresariales → Microsoft Graph Command Line Tools → Permisos; revoca lo que no uses.

Siguientes pasos

  • Escritura con cuidado: Update-MgUser, Invoke-MgSyncDeviceManagementManagedDevice (forzar check-in a los 47 del informe) siempre con -WhatIf primero y en un grupo pequeño.
  • Azure Automation o una tarea en el servidor de herramientas para que el informe llegue cada lunes al canal de Teams.
  • Las mismas consultas sirven desde Python (msgraph-sdk) o curl con un token: Graph es una API REST, PowerShell es solo la puerta más cómoda para IT.

¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.

Tech Career Lab · newsletter

¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.

Apúntate y llévate las 33 ideas →