Cómo automatizar el inicio y la comprobación del estado de los servicios de Windows con PowerShell

Con Powershell, aprender a comprobar el estado de los servicios y mantener el correcto estado los servicios en los equipos Windows es crucial para garantizar un funcionamiento sin problemas en cualquier entorno de TI. Los servicios configurados para iniciarse automáticamente pero que no se ejecutan pueden provocar problemas de rendimiento, caídas imprevistas de la red e ineficiencias operativas.

Aquí es donde resultan útiles los scripts de PowerShell, que ofrecen una forma robusta y automatizada de gestionar y supervisar los servicios de Windows. En este post, exploraremos un script PowerShell diseñado para comprobar el estado de los servicios que no se estén ejecutando en ese momento, excluyendo aquellos configurados como ‘Inicio diferido’ o ‘Inicio activado’.

Contexto

Para los profesionales de TI y los proveedores de servicios gestionados (MSP), mantener el buen funcionamiento de los servicios es una prioridad absoluta. En ocasiones, los servicios de Windows pueden no iniciarse por diversos motivos, como problemas de configuración, conflictos o limitaciones de recursos del sistema.

Este script aborda la necesidad de un enfoque sistemático para identificar y reiniciar los servicios que deberían estar en ejecución pero no lo están. Al automatizar este proceso, los equipos de TI pueden ahorrar tiempo y reducir el riesgo de error humano.

El script para comprobar el estado de los servicios de Windows

#Requires -Version 5.1

<#
.SYNOPSIS
    Reports on or starts services for Automatic Services that are not currently running. Services set as 'Delayed Start' or 'Trigger Start' are ignored.
.DESCRIPTION
    Reports on or starts services for Automatic Services that are not currently running. Services set as 'Delayed Start' or 'Trigger Start' are ignored.
.EXAMPLE
    (No Parameters)
    
    Matching Services found!

    Name    Description                                         
    ----    -----------                                         
    SysMain Maintains and improves system performance over time.

PARAMETER: -IgnoreServices "ExampleServiceName"
    A comma separated list of service names to ignore.

PARAMETER: -StartFoundServices
    Attempts to start any services found matching the criteria.

.NOTES
    Minimum OS Architecture Supported: Windows 10, Windows Server 2016
    Release Notes: Initial Release
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#>

[CmdletBinding()]
param (
    [Parameter()]
    [String]$IgnoreServices,
    [Parameter()]
    [Switch]$StartFoundServices = [System.Convert]::ToBoolean($env:startFoundServices)
)

begin {
    # Replace script parameters with form variables
    if($env:servicesToExclude -and $env:servicesToExclude -notlike "null"){ $IgnoreServices = $env:servicesToExclude }

    # Get the last startup time of the operating system.
    $LastBootDateTime = Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -ExpandProperty LastBootUpTime
    if ($LastBootDateTime -gt $(Get-Date).AddMinutes(-15)) {
        $Uptime = New-TimeSpan $LastBootDateTime (Get-Date) | Select-Object -ExpandProperty TotalMinutes
        Write-Host "Current uptime is $([math]::Round($Uptime)) minutes."
        Write-Host "[Error] Please wait at least 15 minutes after startup before running this script."
        exit 1
    }

    # Define a function to test if the current user has elevated (administrator) privileges.
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    $ExitCode = 0
}
process {
    # Check if the script is running with Administrator privileges.
    if (!(Test-IsElevated)) {
        Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }

    # Define a string of characters that are invalid for service names.
    $InvalidServiceNameCharacters = "\\|/|:"
    # Create a list to hold the names of services to ignore.
    $ServicesToIgnore = New-Object System.Collections.Generic.List[string]

    # If there are services to ignore and they are separated by commas, split the string into individual service names.
    if ($IgnoreServices -and $IgnoreServices -match ",") {
        $IgnoreServices -split "," | ForEach-Object {
            # Check each service name for invalid characters or excessive length.
            if ($_.Trim() -match $InvalidServiceNameCharacters) {
                Write-Host "[Error] Service Name contains one of the invalid characters '\/:'. $_ is not a valid service to ignore."
                $ExitCode = 1
                return
            }

            if (($_.Trim()).Length -gt 256) {
                Write-Host "[Error] Service Name is greater than 256 characters. $_ is not a valid service to ignore. "
                $ExitCode = 1
                return
            }

            # Add valid services to the ignore list.
            $ServicesToIgnore.Add($_.Trim())
        }
    }
    elseif ($IgnoreServices) {
        # For a single service name, perform similar validation and add if valid.
        $ValidService = $True

        if ($IgnoreServices.Trim() -match $InvalidServiceNameCharacters) {
            Write-Host "[Error] Service Name contains one of the invalid characters '\/:'. '$IgnoreServices' is not a valid service to ignore. "
            $ExitCode = 1
            $ValidService = $False
        }

        if (($IgnoreServices.Trim()).Length -gt 256) {
            Write-Host "[Error] Service Name is greater than 256 characters. '$IgnoreServices' is not a valid service to ignore. "
            $ExitCode = 1
            $ValidService = $False
        }

        if ($ValidService) {
            $ServicesToIgnore.Add($IgnoreServices.Trim())
        }
    }

    # Create a list to hold non-running services that are set to start automatically.
    $NonRunningAutoServices = New-Object System.Collections.Generic.List[object]
    Get-Service | Where-Object { $_.StartType -like "Automatic" -and $_.Status -ne "Running" } | ForEach-Object {
        $NonRunningAutoServices.Add($_)
    }

    # Remove services from the list that have triggers or are set to delayed start,
    if ($NonRunningAutoServices.Count -gt 0) {
        $TriggerServices = Get-ChildItem -Path "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\*\*" -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "TriggerInfo" }
        $TriggerServices = $TriggerServices | Select-Object -ExpandProperty PSParentPath | Split-Path -Leaf
        foreach ($TriggerService in $TriggerServices) {
            $NonRunningAutoServices.Remove(($NonRunningAutoServices | Where-Object { $_.ServiceName -match $TriggerService })) | Out-Null
        }

        $DelayedStartServices = Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\*" | Where-Object { $_.DelayedAutoStart -eq 1 }
        $DelayedStartServices = $DelayedStartServices | Select-Object -ExpandProperty PSChildName
        foreach ($DelayedStartService in $DelayedStartServices) {
            $NonRunningAutoServices.Remove(($NonRunningAutoServices | Where-Object { $_.ServiceName -match $DelayedStartService })) | Out-Null
        }
    }

    # Remove explicitly ignored services from the list of non-running automatic services.
    if ($ServicesToIgnore.Count -gt 0 -and $NonRunningAutoServices.Count -gt 0) {
        foreach ($ServiceToIgnore in $ServicesToIgnore) {
            if ($NonRunningAutoServices.ServiceName -contains $ServiceToIgnore) {
                $NonRunningAutoServices.Remove(($NonRunningAutoServices | Where-Object { $_.ServiceName -match [Regex]::Escape($ServiceToIgnore) })) | Out-Null
            }
        }
    }

    # If there are still non-running automatic services left, display their names.
    # Otherwise, indicate no stopped automatic services were detected.
    if ($NonRunningAutoServices.Count -gt 0) {
        Write-Host "Matching Services found!"

        # Add Description to report.
        $ServicesReport = New-Object System.Collections.Generic.List[object]
        $NonRunningAutoServices | ForEach-Object {
            $Description = Get-CimInstance -ClassName Win32_Service -Filter "Name = '$($_.ServiceName)'" | Select-Object @{
                Name       = "Description"
                Expression = {
                    $Characters = $_.Description | Measure-Object -Character | Select-Object -ExpandProperty Characters
                    if ($Characters -gt 100) {
                        "$(($_.Description).SubString(0,100))..."
                    }
                    else {
                        $_.Description
                    }
                }
            }
            $ServicesReport.Add(
                [PSCustomObject]@{
                    Name = $_.ServiceName
                    Description = $Description | Select-Object -ExpandProperty Description
                }
            )
        }

        # Output report to activity log.
        $ServicesReport | Sort-Object Name | Format-Table -Property Name,Description -AutoSize | Out-String | Write-Host
    }
    else {
        Write-Host "No stopped automatic services detected!"
    }

    # Exit the script if there are no services to start or if starting services is not requested.
    if (!$StartFoundServices -or !($NonRunningAutoServices.Count -gt 0)) {
        exit $ExitCode
    }

    # Attempt to start each non-running automatic service up to three times.
    # Log success or error messages accordingly.
    $NonRunningAutoServices | ForEach-Object {
        Write-Host "`nAttempting to start $($_.ServiceName)."
        $Attempt = 1
        while ($Attempt -le 3) {
            Write-Host -Object "Attempt: $Attempt"
            try {
                $_ | Start-Service -ErrorAction Stop
                Write-Host -Object "Successfully started $($_.ServiceName)."
                $Attempt = 4
            }
            catch {
                Write-Host -Object "[Error] $($_.Exception.Message)"
                if ($Attempt -eq 3) { $ExitCode = 1 }
            }
            $Attempt++
        }
    }
    
    exit $ExitCode
}
end {
    
    
    
}

 

Accede a más de 300 scripts en el Dojo de NinjaOne

Obtén acceso

Análisis detallado

Profundicemos en el script para comprobar el estado de los servicios para entender cómo funciona paso a paso.

  1. Inicialización del script y parámetros: el script comienza definiendo su propósito y sus parámetros. Acepta dos parámetros opcionales: -IgnoreServices, una lista separada por comas de los servicios a ignorar, y -StartFoundServices, un interruptor para iniciar los servicios no en ejecución que cumplan los criterios.
  2. Comprobación del tiempo de actividad: antes de continuar, el script comprueba el tiempo de actividad del sistema para asegurarse de que lleva funcionando al menos 15 minutos. De este modo se evitan problemas relacionados con servicios que no hayan tenido tiempo suficiente para iniciarse correctamente tras un reinicio.
  3. Comprobación de privilegios: el script se asegura de que se está ejecutando con privilegios de administrador, ya que detener e iniciar servicios requiere permisos elevados.
  4. Validación del servicio: los servicios que deben ignorarse se validan para garantizar que no contienen caracteres no válidos y que sus nombres no son excesivamente largos.
  5. Identificación de servicios que no funcionan: el script recopila una lista de servicios configurados para iniciarse automáticamente pero que no se están ejecutando actualmente. A continuación, filtra los servicios que están configurados como “Inicio diferido” o que tienen condiciones de activación.
  6. Informes y acción: si se encuentran servicios no en ejecución, el script informa de ellos. Si el parámetro -StartFoundServices está activado, intenta iniciar cada uno de estos servicios, registrando los aciertos y los errores.

Posibles casos de uso

Estudio de caso

Imagina a un profesional de TI que gestiona una red de servidores Windows que alojan diversas aplicaciones críticas. Un día, los usuarios empiezan a informar de problemas para acceder a una aplicación clave. El profesional de TI ejecuta este script de PowerShell, que identifica que el servicio de la aplicación, configurado para iniciarse automáticamente, no se está ejecutando. El script intenta reiniciar el servicio, resolviendo el problema rápidamente y restableciendo el funcionamiento normal.

Comparaciones

Este script para comprobar el estado de los servicios proporciona un enfoque racionalizado y automatizado en comparación con la comprobación manual de cada servicio a través del complemento Services MMC o el uso de otros scripts menos específicos. Los métodos alternativos, como el uso del Programador de tareas para supervisar los servicios, pueden ser más complejos y difíciles de mantener.

FAQ

P: ¿Se puede utilizar este script para comprobar el estado de los servicios en versiones anteriores de Windows?

R: El script está diseñado para Windows 10 y Windows Server 2016 y versiones más recientes. La compatibilidad con versiones anteriores puede requerir ajustes.

P: ¿Qué ocurre si un servicio no se inicia?

R: El script intenta iniciar cada servicio hasta tres veces y registra los errores si falla.

P: ¿Puedo excluir varios servicios del control?

R: Sí, utiliza el parámetro -IgnoreServices con una lista de nombres de servicios separados por comas.

Implicaciones

La automatización de la supervisión y gestión de los servicios de Windows mejora la eficacia operativa y reduce la probabilidad de tiempos de inactividad. Garantizar que los servicios críticos estén siempre en funcionamiento puede mejorar la fiabilidad y la seguridad del sistema.

Recomendaciones

  • Supervisión periódica: programa el script para que se ejecute a intervalos regulares mediante el Programador de tareas para una supervisión continua.
  • Registro: implementa mecanismos de registro adicionales para mantener un historial de las acciones realizadas por el script.
  • Pruebas: prueba el script en un entorno controlado antes de desplegarlo en un entorno de producción.

Reflexiones finales

Al automatizar la gestión de los servicios de Windows, los profesionales de TI pueden garantizar un mayor tiempo de actividad y fiabilidad de sus sistemas. Para aquellos que buscan agilizar aún más sus operaciones de TI, herramientas como NinjaOne ofrecen soluciones integrales para supervisar, gestionar y automatizar diversos aspectos de la infraestructura de TI, complementando las capacidades proporcionadas por este script.

Categorías:

Quizá también te interese…

×

¡Vean a NinjaOne en acción!

Al enviar este formulario, acepto la política de privacidad de NinjaOne.

Términos y condiciones de NinjaOne

Al hacer clic en el botón “Acepto” que aparece a continuación, estás aceptando los siguientes términos legales, así como nuestras Condiciones de uso:

  • Derechos de propiedad: NinjaOne posee y seguirá poseyendo todos los derechos, títulos e intereses sobre el script (incluidos los derechos de autor). NinjaOne concede al usuario una licencia limitada para utilizar el script de acuerdo con estos términos legales.
  • Limitación de uso: solo podrás utilizar el script para tus legítimos fines personales o comerciales internos, y no podrás compartirlo con terceros.
  • Prohibición de republicación: bajo ninguna circunstancia está permitido volver a publicar el script en ninguna biblioteca de scripts que pertenezca o esté bajo el control de cualquier otro proveedor de software.
  • Exclusión de garantía: el script se proporciona “tal cual” y “según disponibilidad”, sin garantía de ningún tipo. NinjaOne no promete ni garantiza que el script esté libre de defectos o que satisfaga las necesidades o expectativas específicas del usuario.
  • Asunción de riesgos: el uso que el usuario haga del script corre por su cuenta y riesgo. El usuario reconoce que existen ciertos riesgos inherentes al uso del script, y entiende y asume cada uno de esos riesgos.
  • Renuncia y exención: el usuario no hará responsable a NinjaOne de cualquier consecuencia adversa o no deseada que resulte del uso del script y renuncia a cualquier derecho o recurso legal o equitativo que pueda tener contra NinjaOne en relación con su uso del script.
  • CLUF: si el usuario es cliente de NinjaOne, su uso del script está sujeto al Contrato de Licencia para el Usuario Final (CLUF).