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:

No hay comentarios: