Para encontrar usuarios inactivos de Microsoft 365, una dos cosas que Microsoft guarda en sitios distintos: las licencias asignadas a cada cuenta y la fecha en que esa cuenta inició sesión correctamente por última vez. Microsoft Graph mantiene ambas en el objeto de usuario, así que una sola llamada a Get-MgUser que seleccione assignedLicenses y signInActivity le da una fila por cuenta con licencia con su fecha de último inicio de sesión. Compare la antigüedad de esas filas con un umbral que su equipo acuerde de antemano, normalmente 30, 60 o 90 días, y toda cuenta por encima de la línea es un asiento de pago sin ninguna evidencia de uso detrás. El script de más abajo hace exactamente eso y escribe un CSV que puede llevar a una revisión. Cópielo, ejecútelo, consérvelo.

Esta es la cara del inicio de sesión del desperdicio de licencias. La guía complementaria sobre cómo encontrar licencias de Microsoft 365 sin usar cubre la otra cara, la diferencia entre lo que ha comprado y lo que ha asignado. Esa diferencia es la fácil. Esta, el asiento asignado a una cuenta real que nadie usa, suele ser la cifra mayor y la conversación más difícil.

Empiece por la aritmética, porque decide si esto merece una tarde

Haga la cuenta primero, con sus propios números en lugar del punto de referencia de nadie. En un entorno de 300 usuarios, si el 4 por ciento de las cuentas con licencia no ha iniciado sesión en un trimestre, son 12 asientos. Doce asientos por lo que realmente paga por usuario y mes, multiplicado por doce meses, es la cifra anual. A £20 el asiento son £2,880 al año; a £30 son £4,320. Cambie cualquier dato de entrada y la respuesta se mueve, que es justo el sentido: puede comprobarlo mentalmente y su responsable financiero también.

Dos cosas hacen que merezca la pena perseguirlo en una organización de 50 a 600 usuarios. Se repite cada mes hasta que alguien actúa, así que no hacer nada se acumula. Y casi nunca es tarea de nadie. Finanzas ve una factura estable y supone que es correcta; TI ve un recuento de licencias y supone que Finanzas lo aprobó. Así es como los asientos latentes sobreviven durante años.

Por qué la página Licenses no puede responder a esto

La primera parada obvia es el centro de administración de Microsoft 365, en Billing y luego Licenses, que muestra cada producto con cuántos asientos ha comprado y cuántos están asignados. Es una comprobación útil de dos minutos y conviene hacerla. Tenga en cuenta que agrega por producto, mientras que Billing y luego Your products lista una fila por suscripción, así que las dos páginas no siempre coincidirán a primera vista.

Lo que ninguna le dirá es si un asiento asignado está haciendo algo. Esas páginas están construidas en torno al derecho de uso, no al comportamiento. Una cuenta creada para un contratista hace dos años, todavía habilitada, todavía con un asiento completo y sin haber iniciado sesión desde entonces, tiene ahí el mismo aspecto que su usuario más activo.

Reports y luego Usage le acerca más. El informe Active users muestra una fecha de última actividad por carga de trabajo para Exchange, OneDrive, SharePoint y Teams, junto con las licencias asignadas. Merece la pena conocerlo, y responde a una pregunta distinta: si alguien tocó una aplicación, no si la identidad llegó a autenticarse. Espere dos cosas. Los datos suelen estar disponibles para los últimos 7, 30, 90 y 180 días y pueden tardar un día o más en aparecer. Y los nombres de usuario están ocultos de forma predeterminada, así que si el informe muestra filas anonimizadas un administrador tiene que desactivar la opción "Conceal user, group, and site names in all reports" en Settings, Org Settings, Services, Reports.

Acuerde qué significa inactivo antes de ejecutar nada

Este es el paso que los equipos se saltan, y decide si su informe sobrevive al contacto con el resto del negocio. Treinta días es agresivo y arrastra bajas por maternidad o paternidad, bajas de larga duración, excedencias y personal de temporada. Noventa días dejan correr un trimestre entero de desperdicio antes de que se señale nada. Sesenta es una primera pasada razonable en un entorno mediano. No hay una respuesta correcta, y quien le diga que la hay, proveedores incluidos, le está vendiendo su valor predeterminado.

Lo que importa es que el umbral sea una política escrita que su equipo asuma, con excepciones nombradas, antes de que el primer informe llegue a nadie. Un informe sin umbral acordado produce una discusión. Un informe con uno produce acciones.

Los datos de inicio de sesión detrás de la respuesta

Microsoft Entra ID registra la actividad de inicio de sesión en cada objeto de usuario, y Graph la expone en el recurso de usuario v1.0 como la propiedad signInActivity. Lleva un último inicio de sesión interactivo, un último inicio de sesión no interactivo y un último inicio de sesión correcto, cada uno emparejado con un identificador de solicitud. Interactivo significa que una persona escribió credenciales o aprobó una solicitud. No interactivo cubre a un cliente que renueva un token en segundo plano, y por eso una cuenta puede parecer ocupada en un campo y muerta en otro.

La razón para usar esto en lugar de rastrear los registros de inicio de sesión es la retención. Los registros de inicio de sesión y de auditoría se conservan 7 días en el nivel gratuito y 30 días con Entra ID P1 o P2, así que el registro por sí solo no puede hablarle de una ausencia de 90 días. La propiedad signInActivity es distinta: Entra la almacena mientras exista el objeto de usuario. Es la fuente correcta para esta pregunta.

Cuatro restricciones prácticas antes de ejecutar nada:

  • Solo se devuelve cuando la selecciona explícitamente. Un Get-MgUser normal no la incluye, y la propiedad simplemente no aparece en lugar de lanzar un error, que es la razón más común por la que se concluye que faltan los datos.
  • Leerla exige AuditLog.Read.All además de User.Read.All. Sin el ámbito de auditoría, los datos de licencia se devuelven y el campo de inicio de sesión vuelve vacío, lo que parece indicar que todas las cuentas están inactivas.
  • Requiere Microsoft Entra ID P1 o P2. En un tenant sin ninguno de los dos, cuente con que la propiedad no esté disponible, así que compruébelo antes de depurar una columna nula.
  • Un valor en blanco no es prueba de desuso. El último inicio de sesión correcto solo se registra desde diciembre de 2023 y no se rellenó de forma retroactiva, y el valor puede retrasarse hasta 24 horas. Trate el blanco como "investigar", nunca como "eliminar".

En el lado del directorio no necesita Global Administrator. Reports Reader es el rol con menos privilegios que puede leer los registros de auditoría y de inicio de sesión, y es el correcto: esta es una tarea de informes y no debería ser motivo para que nadie tenga un rol de escritura.

El script

Esto es todo. Se conecta, construye una tabla de búsqueda para que la salida se lea con nombres de plan en lugar de GUID, extrae cada cuenta con licencia junto con su actividad de inicio de sesión, la contrasta con su umbral y escribe un CSV.

# Inactive licensed users report
# Requires the Microsoft.Graph PowerShell SDK
Connect-MgGraph -Scopes "User.Read.All","AuditLog.Read.All","Organization.Read.All"
$InactiveDays = 60
$Cutoff = (Get-Date).AddDays(-$InactiveDays)
# Map SKU GUIDs to readable plan names
$SkuMap = @{}
Get-MgSubscribedSku | ForEach-Object { $SkuMap[$_.SkuId] = $_.SkuPartNumber }
# signInActivity is only returned when explicitly selected
$Users = Get-MgUser -All -Property "id,displayName,userPrincipalName,accountEnabled,userType,createdDateTime,assignedLicenses,signInActivity"
$Report = foreach ($u in $Users) {
    if (-not $u.AssignedLicenses -or $u.AssignedLicenses.Count -eq 0) { continue }
    if ($u.UserType -eq 'Guest') { continue }
    $last = $u.SignInActivity.LastSuccessfulSignInDateTime
    if (-not $last) { $last = $u.SignInActivity.LastSignInDateTime }
    if ($last -and $last -ge $Cutoff) { continue }
    [pscustomobject]@{
        DisplayName       = $u.DisplayName
        UserPrincipalName = $u.UserPrincipalName
        Enabled           = $u.AccountEnabled
        Created           = $u.CreatedDateTime
        LastSignIn        = $last
        DaysSinceSignIn   = if ($last) { [int](New-TimeSpan -Start $last -End (Get-Date)).TotalDays } else { 'Never' }
        Licences          = (($u.AssignedLicenses.SkuId | ForEach-Object { $SkuMap[$_] }) -join '; ')
    }
}
$Report | Sort-Object Created |
    Export-Csv -Path ".\inactive-licensed-users.csv" -NoTypeInformation -Encoding UTF8
"{0} licensed accounts with no sign-in in {1} days" -f @($Report).Count, $InactiveDays

Léalo una vez antes de ejecutarlo. Omite las cuentas sin licencia, porque una cuenta latente sin licencia no cuesta nada, y omite a los invitados, porque siguen reglas distintas y de otro modo dominarían la salida. Prefiere el último inicio de sesión correcto y recurre al último inicio de sesión interactivo, de modo que se comporta con sensatez en cuentas cuyo inicio de sesión correcto es anterior al arranque de ese campo en diciembre de 2023. Calcula la antigüedad de las filas en PowerShell y no en la consulta de Graph: signInActivity puede filtrarse en el servidor, pero no en combinación con ninguna otra propiedad filtrable, así que hacerlo en el cliente mantiene las condiciones de licencia e invitados en el mismo sitio. Una cuenta que nunca ha iniciado sesión aparece como Never en lugar de en blanco, porque ese es un hallazgo distinto del de una cuenta latente desde hace mucho.

El bucle en cuatro movimientos. El script cubre los dos primeros. Los dos últimos son criterio, y ahí es donde el dinero se mueve de verdad.

Lo que el informe suele sacar a la luz, y la decisión que necesita cada caso

Casi toda primera ejecución produce las mismas cuatro categorías, y cada una necesita una decisión distinta de una persona distinta.

Personas que se fueron y a las que nunca se dio de baja del todo. Una cuenta todavía habilitada, todavía con licencia, a veces meses después de que la persona se marchara. Este es el hallazgo de mayor valor y menor riesgo, y es un fallo de proceso más que de licenciamiento. Retire la licencia, bloquee el inicio de sesión y ocúpese del buzón y de los archivos como corresponde. La lista de comprobación para dar de baja usuarios de Microsoft 365 tiene el orden completo de las operaciones, que importa porque hacer estos pasos en la secuencia equivocada pierde datos.

Cuentas de servicio y compartidas que retienen asientos completos. A menudo no muestran ningún inicio de sesión interactivo mientras una aplicación las usa de verdad. No las toque hasta haber encontrado al propietario. Pregunte qué necesita realmente la cuenta, porque un asiento de productividad completo suele ser mucho más que la respuesta.

Complementos duplicados o solapados. Cuentas que llevan un complemento que un plan base posterior ya incluye. La cuenta está activa, así que nunca aparece como latente, pero el complemento es peso muerto. Ordenar la salida por la columna de licencias los saca a la luz enseguida.

Personas reales genuinamente inactivas. Alguien de baja prolongada, alguien que cambió de puesto, alguien incorporado a un plan que nunca usó. Esta es la categoría que necesita a un responsable en la conversación más que a un script, y el resultado suele ser una degradación en lugar de una retirada. Nuestra guía sobre Business Premium, E3 y E5 es una referencia útil de lo que un plan más ligero le cuesta realmente al usuario.

Una advertencia antes de actuar sobre cualquiera de ellos. Compruebe de dónde viene cada licencia antes de retirarla del usuario. Las asignaciones basadas en grupos se gestionan en el grupo, y la página Licenses muestra el nombre del grupo en lugar de sus miembros, así que un asiento que retire de una cuenta puede volver en silencio a través de la pertenencia al grupo y el ahorro nunca llega a la factura.

Si las cuatro categorías anteriores ya le suenan, puede ver ese mismo informe ejecutándose de forma continua contra su propio tenant en una prueba de 14 días de EtherInsights, sin mantenimiento de scripts.

El límite honesto de ejecutar esto como script

El script es bueno. También es una instantánea, y ese es su techo. Alguien tiene que acordarse de ejecutarlo. Alguien tiene que interpretarlo, porque un CSV de 40 cuentas no es una decisión, son 40 decisiones. Alguien tiene que perseguir a los propietarios, aplicar los cambios y luego demostrar a Finanzas que el recuento de asientos bajó de verdad en la renovación, que es otro trabajo aparte. Sáltese dos meses y el entorno se le habrá movido debajo.

Eso no es un argumento contra escribir el script. Ejecútelo esta semana igualmente. Es un argumento para mirar con claridad al cuarto mes, cuando la persona que lo escribió está ocupada y el informe deja de ejecutarse en silencio. En una organización de 50 a 600 usuarios la limitación rara vez es la capacidad. Es que nadie asume la tarea recurrente.

Dónde encaja EtherInsights

EtherInsights es la versión continua de lo que este script hace una vez. Concilia las licencias con la actividad real de forma programada, saca a la luz los asientos latentes y nunca usados como hallazgos con un propietario con nombre y una acción siguiente en lugar de como una hoja de cálculo, y realiza el seguimiento del antes y el después para que una recuperación llegue a su revisión financiera como evidencia y no como una afirmación. Para el ciclo de vida completo, desde que se señala un asiento latente hasta que se da de baja a quien se va antes de que su licencia vuelva a facturar, la gestión de licencias y baja de usuarios de Microsoft 365 es la vía que cierra el ciclo.

Cuesta £0.79 por usuario activo con una prueba de 14 días, así que puede hacer la aritmética del principio de este artículo contra su propio tenant antes de comprometerse a nada. Si necesita llevarle a quien controla el presupuesto una cifra en lugar de un método, esa suele ser la vía más rápida para conseguirla. El desperdicio de licencias es una línea dentro de un panorama más amplio, y por eso se sitúa dentro de la optimización de costes en la nube, y por eso demostrar el ahorro importa tanto como encontrarlo.

Acuerde el umbral, ejecute el script, actúe de inmediato sobre quienes se han ido y ponga una fecha en el calendario para el mes que viene. Si esa fecha se sigue aplazando, ha encontrado el problema real, y no es técnico.

Explore la gestión de licencias y baja de usuarios de Microsoft 365 para ver los asientos latentes, la baja de quienes se van y el rastro de evidencia tratados como un proceso continuo en lugar de un recordatorio mensual.