martes, 12 de mayo de 2026

WINDOWS. GPO. Alertas Windows


 Propósito:

El objetivo de este post es avisar a los usuarios que tienen pendiente un reinicio.

Para mi una de las peores cosas que tiene Microsoft es como alertar o advertir a los usuarios de un evento, sinceramente no me gusta nada los pequeños mensajes que pasan desapercibidos en la parte izquierda de la pantalla.

Otra cosa que no da muchas opciones Microsoft, es forzar un reinicio con un numero importante de alertas a los usuarios. Sin utilizar comandos, solo lo permite el Wsus un reinicio forzoso y sus avisos no son los mejores. Con lo que he tenido que pensar en otra manera de alertar a mis usuarios, de las muchas que hay se me ha ocurrido la siguiente, solo avisando, que he querido compartir aquí.

Aunque lo parezca, es uno de los script que me ha llevado más tiempo crear, he pasado por muchas formas y versiones y con la ayuda de Copilot ha acabado como aquí voy a explicar.

Lo he preparado para que salte en el logon de inicio y en cada bloqueo y desbloqueo de la pantalla. Dejará de aparecer cuando ya no tenga ningún reinicio pendiente.

Pasos

La GPO.

Como veréis es una GPO de usuario, no de máquina, ya que es la mejor manera de que las alertas interactúen con el usuario conectado.

Tenemos que crear en la GPO una tarea programada, esta tarea la inyectaremos en los PCs.




Argumentos: -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command "& { $Source='\\Midominio.com\SYSVOL\midominio.com\scripts\WSUS\avisoreiniciopendiente.ps1'; $Target=$env:ProgramData+'\RebootAlert\avisoreiniciopendiente.ps1'; if (-not (Test-Path (Split-Path $Target))) { New-Item -ItemType Directory -Path (Split-Path $Target) -Force | Out-Null }; Copy-Item $Source $Target -Force; Start-Process powershell.exe -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File', $Target) -WindowStyle Hidden }"









Poner el script en un recurso compartido donde puedan leer todos los usuarios.

Código:

# ==================================================

# ALERTA DE REINICIO PENDIENTE - PRODUCCIÓN

# ==================================================

# - Muestra aviso WPF si hay reinicio pendiente

# - Sin control de tiempo (sale siempre que haya reboot)

# - Evita doble ejecución mediante Mutex (que no salga dos veces seguidas la alerta)

# - Pensado para ejecución vía GPO / Tarea programada

# ==================================================

 

Add-Type -AssemblyName PresentationFramework

 

# --------------------------------------------------

# EVITAR DOBLE EJECUCIÓN DEL SCRIPT

# (necesario porque se lanza PowerShell oculto)# ==================================================

# ALERTA DE REINICIO PENDIENTE - PRODUCCIÓN

# ==================================================

# - Muestra aviso WPF si hay reinicio pendiente

# - Sin control de tiempo (sale siempre que haya reboot)

# - Evita doble ejecución mediante Mutex

# - Pensado para ejecución vía GPO / Tarea programada

# ==================================================

 

Add-Type -AssemblyName PresentationFramework

 

# --------------------------------------------------

# EVITAR DOBLE EJECUCIÓN DEL SCRIPT

# (necesario porque se lanza PowerShell oculto)

# --------------------------------------------------

$mutexName = "Global\RebootPendingAlertMutex"

$createdNew = $false

$mutex = New-Object System.Threading.Mutex($true, $mutexName, [ref]$createdNew)

 

if (-not $createdNew) {

    # Ya hay otra instancia ejecutándose

    exit

}

 

# --------------------------------------------------

# FUNCIÓN: Detectar reinicio pendiente

# --------------------------------------------------

function Test-PendingReboot {

 

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") {

        return $true

    }

 

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") {

        return $true

    }

 

    return $false

}

 

# --------------------------------------------------

# FUNCIÓN: Motivo del reinicio pendiente

# --------------------------------------------------

function Get-PendingRebootReason {

 

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") {

        return "actualizaciones de Windows"

    }

 

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") {

        return "actualizaciones del sistema"

    }

 

    return "tareas de mantenimiento del sistema"

}

 

# --------------------------------------------------

# SI NO HAY REINICIO PENDIENTE, SALIR

# --------------------------------------------------

if (-not (Test-PendingReboot)) {

    exit

}

 

# --------------------------------------------------

# OBTENER MOTIVO DEL REINICIO

# --------------------------------------------------

$rebootReason = Get-PendingRebootReason

 

# --------------------------------------------------

# VENTANA WPF

# --------------------------------------------------

[xml]$xaml = @"

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        Title="Miempresa. Reinicio pendiente"

        Height="240"

        Width="540"

        WindowStartupLocation="CenterScreen"

        ResizeMode="NoResize"

        Topmost="True">

 

    <Grid Margin="10">

        <Grid.RowDefinitions>

            <RowDefinition Height="Auto"/>

            <RowDefinition Height="*"/>

            <RowDefinition Height="Auto"/>

        </Grid.RowDefinitions>

 

        <!-- BARRA SUPERIOR ROJA -->

        <Border Background="#B71C1C" Grid.Row="0" Padding="10">

            <TextBlock Text="Reinicio pendiente."

                       Foreground="White"

                       FontSize="16"

                       FontWeight="Bold"

                       HorizontalAlignment="Center"/>

        </Border>

 

        <!-- TEXTO PRINCIPAL -->

        <Border Grid.Row="1" Padding="20">

            <TextBlock

                Text="El equipo necesita reiniciarse para completar $rebootReason.

 

Por favor, guarda tu trabajo y reinicia cuando te sea posible."

                TextWrapping="Wrap"

                TextAlignment="Center"

                VerticalAlignment="Center"

                FontSize="13"/>

        </Border>

 

        <!-- BOTÓN -->

        <Button Name="AceptarBtn"

                Grid.Row="2"

                Width="120"

                Height="35"

                Margin="10"

                HorizontalAlignment="Right"

                Background="#D32F2F"

                Foreground="White"

                FontWeight="Bold"

                Content="Aceptar"/>

    </Grid>

</Window>

"@

 

$reader = New-Object System.Xml.XmlNodeReader $xaml

$window = [Windows.Markup.XamlReader]::Load($reader)

 

$button = $window.FindName("AceptarBtn")

$button.Add_Click({ $window.Close() })

 

$window.ShowDialog() | Out-Null

 

# --------------------------------------------------

$mutexName = "Global\RebootPendingAlertMutex"

$createdNew = $false

$mutex = New-Object System.Threading.Mutex($true, $mutexName, [ref]$createdNew)

 

if (-not $createdNew) {

    # Ya hay otra instancia ejecutándose

    exit

}

 

# --------------------------------------------------

# FUNCIÓN: Detectar reinicio pendiente

# --------------------------------------------------

function Test-PendingReboot {

 

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") {

        return $true

    }

 

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") {

        return $true

    }

 

    return $false

}

 

# --------------------------------------------------

# FUNCIÓN: Motivo del reinicio pendiente

# --------------------------------------------------

function Get-PendingRebootReason {

 

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") {

        return "actualizaciones de Windows"

    }

 

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") {

        return "actualizaciones del sistema"

    }

 

    return "tareas de mantenimiento del sistema"

}

 

# --------------------------------------------------

# SI NO HAY REINICIO PENDIENTE, SALIR

# --------------------------------------------------

if (-not (Test-PendingReboot)) {

    exit

}

 

# --------------------------------------------------

# OBTENER MOTIVO DEL REINICIO

# --------------------------------------------------

$rebootReason = Get-PendingRebootReason

 

# --------------------------------------------------

# VENTANA WPF

# --------------------------------------------------

[xml]$xaml = @"

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        Title="Midominio. Reinicio pendiente"

        Height="240"

        Width="540"

        WindowStartupLocation="CenterScreen"

        ResizeMode="NoResize"

        Topmost="True">

 

    <Grid Margin="10">

        <Grid.RowDefinitions>

            <RowDefinition Height="Auto"/>

            <RowDefinition Height="*"/>

            <RowDefinition Height="Auto"/>

        </Grid.RowDefinitions>

 

        <!-- BARRA SUPERIOR ROJA -->

        <Border Background="#B71C1C" Grid.Row="0" Padding="10">

            <TextBlock Text="Reinicio pendiente."

                       Foreground="White"

                       FontSize="16"

                       FontWeight="Bold"

                       HorizontalAlignment="Center"/>

        </Border>

 

        <!-- TEXTO PRINCIPAL -->

        <Border Grid.Row="1" Padding="20">

            <TextBlock

                Text="El equipo necesita reiniciarse para completar $rebootReason.

 

Por favor, guarda tu trabajo y reinicia cuando te sea posible."

                TextWrapping="Wrap"

                TextAlignment="Center"

                VerticalAlignment="Center"

                FontSize="13"/>

        </Border>

 

        <!-- BOTÓN -->

        <Button Name="AceptarBtn"

                Grid.Row="2"

                Width="120"

                Height="35"

                Margin="10"

                HorizontalAlignment="Right"

                Background="#D32F2F"

                Foreground="White"

                FontWeight="Bold"

                Content="Aceptar"/>

    </Grid>

</Window>

"@

 

$reader = New-Object System.Xml.XmlNodeReader $xaml

$window = [Windows.Markup.XamlReader]::Load($reader)

 

$button = $window.FindName("AceptarBtn")

$button.Add_Click({ $window.Close() })

 

$window.ShowDialog() | Out-Null


Se me ocurre muchos puntos de mejora, pero los dejo para más adelante.

Sinceramente, se puede usar para lanzar cualquier tipo de alertas.

by GoN | Published: May 2026 | Last Updated:

lunes, 27 de abril de 2026

WINDOWS. AD. Special group Protected Users

 

Propósito:

El objetivo de este post es añadir un nuevo nivel de seguridad a cuentas privilegiadas, para ello haremos uso del grupo del AD:  Protected Users

Pasos

Los beneficios son una reducción drástica de exposición de credenciales al añadir una cuenta a Protected Users tales como:

* NTLM deshabilitado → elimina: Pass-the-Hash clásico, credenciales reutilizable y ataques en redes legacy

* Kerberos endurecido: Solo cifrado fuerte (AES), Tickets con vida corta y nada de RC4

*No hay cacheo de credenciales:  No quedan hash reutilizables en workstations, ,enos valor post-compromiso

* Delegación Kerberos prohibida, evita ataques “relay” y abuso de servicios. Protege movimientos laterales silenciosos

Desde una perspectiva más técnica.

Al añadir un Domain Admin a Protected Users, el impacto vendria a ser

  • Autenticación: NTLM deja de funcionar
  • Legacy systems: Pueden dejar de aceptar login
  • Aplicaciones antiguas: Fallos silenciosos
  • Scripts antiguos: Dejan de autenticar
  • Delegación: Bloqueada completamente
  • Se rompen cosas, si existen dependencias legacy.

Problemas habituales 

Problema 1: entornos híbridos/legacy.Muy común que en  Appliances, NAS antiguos, Impresoras, Software industrial,  aplicaciones que solo hablan NTLM, esos sistemas no saben Kerberos moderno, el admin no puede autenticarse.

Problema 2: cuentas de servicio mal diseñadas, Protected Users está pensado para humanos, no para automatización.

Microsoft NO lo activa por defecto por retrocompatibilidad con Active Directory que vive en mundos donde hay Windows 2008 aún, hay NTLM o hay software que no pueden romper

Microsoft asume correctamente que Protected Users es para organizaciones que saben exactamente lo que están haciendo.



Cuando lo probé el uso de este grupo en mi entorno del AD, lo hice con unos cuantos usuarios de test tier0 y tier1. Al rato me di cuenta que los tier0 (administradores de dominio) no podían sincronizar el AD, supongo porque no lo tengo todo lo actualizado que se necesita, con lo que los tuve que sacar y dejar solo los Tier1.


C:\> repadmin /syncall /AdeP

Sincronizando todos los NC guardados en dc01.

Sincronizando partición: DC=ForestDnsZones,DC=MiDominio,DC=com

MENSAJE DE DEVOLUCIÓN DE LLAMADA: Error al establecer contacto con el servidor CN=NTDS Settings,CN=DC1,CN=Servers,CN=Nombre-predeterminado-primer-sitio,CN=Sites,CN=Configuration,DC=MiDominio,DC=com (error de red): 5 (0x5):

    Acceso denegado.

MENSAJE DE DEVOLUCIÓN DE LLAMADA: Error al establecer contacto con el servidor CN=NTDS Settings,CN=SE-DC3,CN=Servers,CN=Azure-DC,CN=Sites,CN=Configuration,DC=MiDominio,DC=com (error de red): 5 (0x5):

    Acceso denegado.

MENSAJE DE DEVOLUCIÓN DE LLAMADA: Error al establecer contacto con el servidor CN=NTDS Settings,CN=dC3,CN=Servers,CN=CLOUDC,CN=Sites,CN=Configuration,DC=MiDominio,DC=com (error de red): 5 (0x5):

    Acceso denegado.

MENSAJE DE DEVOLUCIÓN DE LLAMADA: Error al establecer contacto con el servidor CN=NTDS Settings,CN=dC4,CN=Servers,CN=Nombre-predeterminado-primer-sitio,CN=Sites,CN=Configuration,DC=MiDominio,DC=com (error de red): 5 (0x5):

    Acceso denegado.

SyncAll se finalizó con error grave de Win32: 8440 (0x20f8):

    El contexto del nombre especificado en la operación de replicación no es válido.

by GoN | Published: April 2026 | Last Updated:

viernes, 17 de abril de 2026

WINDOWS. Admincount. Analisis

 

Propósito:

El objetivo es garantizar que no existan cuentas de administrador no autorizadas en Active Directory.

Se realiza una comprobación en las cuentas que no son de administrador para identificar si tienen el atributo `admincount` activado. Si lo tienen, significa que esta cuenta, que no debería ser de administrador, ha recibido derechos de administrador en el pasado. Esto suele ocurrir cuando un administrador otorga derechos temporales a una cuenta normal fuera de proceso.

Se deben revisar estas cuentas, especialmente en lo que respecta a su actividad anterior, y eliminar el atributo `admincount`. Para identificar las cuentas detectadas por esta regla, recomendamos ejecutar el siguiente comando de PowerShell: `get-adobject -ldapfilter "(admincount=1)"`.

Pasos

 PASO 0: Listar quien cumple esta condición

COMMANDO:  get-adobject -ldapfilter "(admincount=1)"


 PASO 1 — Confirmar que el usuario YA NO es admin

COMMANDO:  Get-ADUser usuarioX -Properties MemberOf | Select -ExpandProperty MemberOf


 Verifica que NO esté en:
  • Domain Admins
  • Enterprise Admins
  • Schema Admins
  • Account Operators
  • Backup Operators
  • Administrators (builtin)
  • Print Operators
  • Server Operators
PASO 2 — Quitar el atributo adminCount

COMMANDO: Set-ADUser usuarioX -Clear adminCount



by GoN | Published: April 2026 | Last Updated: