Dilluns, juny 9, 2025
SCRIPTING

Script amb PowerShell per a reiniciar serveis remots en múltiples servidors

Hi ha coses que no haurien de ser complicades, com reiniciar un servei en diversos servidors. Però quan ho fas de manera regular, en entorns amb control de canvis, zones DMZ, servidors en diferents dominis i usuaris amb permisos limitats, comences a notar que amb un “Restart-Service” no n’hi ha prou. Tenir un script per a això sembla trivial fins que has de fer-ho fiable, reutilitzable i resistent a errors previsibles.

Evita confiar en WinRM per defecte

Molts scripts de PowerShell per a administració remota es basen en Invoke-Command amb sessions WinRM. Funciona… fins que deixa de fer-ho. En entorns on hi ha salts de domini, firewalls restrictius o servidors legacy que no tenen habilitat WinRM (o pitjor: el tenen mal configurat), et trobes amb errors genèrics o temps d’espera absurds.

Per a tasques com reiniciar serveis, sempre que sigui possible, prefereixo fer anar Get-Service i Restart-Service amb -ComputerName. Són comandaments que funcionen sobre RPC i no depenen de WinRM. Funcionen fins i tot amb servidors 2012 que ningú ha tocat des que van sortir del projecte.

Restart-Service -Name 'Spooler' -ComputerName 'SRV-PRINT01'

Sí, és més limitat, però és molt més fiable quan estàs operant en una xarxa heterogènia. El 90% dels errors que m’he trobat en producció amb reinicis automàtics estaven relacionats amb sessions remotes trencades o Kerberos fallant. Si l’objectiu és reiniciar un servei i no executar lògica complexa del costat remot, no hi ha necessitat de complicar-se amb sessions persistents.

Compte amb els noms dels serveis: no assumeixes res

Un altre error habitual és assumir que tots els serveis s’anomenen igual a tots els servidors. De vegades el nom del servei canvia segons la versió instal·lada, l’idioma del sistema o les polítiques locals. He vist serveis que en teoria eren “W3SVC” i acabaven sent “w3svc” o “WorldWideWebPublishing” per àlies configurats.

Solució: no asumeixis noms sense validar. Fes servir un script previ que et digui si el servei és present abans d’intentar reiniciar-lo. Alguna cosa com això:

$service = Get-Service -ComputerName $server -Name $serviceName -ErrorAction SilentlyContinue
if ($null -eq $service) {
    Write-Warning "$server: El servei '$serviceName' no es va trobar."
    return
}

A més, els scripts han de ser idempotents. No passa res si reinicies un servei que ja està corrent, però arrencar un servei que està detingut pot causar més problemes que els que resol. Alguns serveis tenen dependències que no reinicien bé si l’estat inicial no era l’esperat. Millor validar:

if ($service.Status -eq 'Running') {
Restart-Service -InputObject $service -Force -ErrorAction Stop
    Write-Output "$server: Servei '$serviceName' reiniciat."
} else {
    Write-Warning "$server: Servei '$serviceName' no estava corrent. No es reinicia."
}

Gestió d’ errors: no la deixis a l’ atzar

Quan treballes amb múltiples servidors, un o diversos fallaran. L’important no és evitar la decisió, sinó tractar-lo de manera que no arruïni tot el procés. Mai facis servir -ErrorAction Stop sense try/catch, i mai facis catch {} buit.

Això és el que a mi em funciona:

  1. Log per servidor, amb timestamps i resultats clars.
  2. Registre de fallades en un CSV per a revisió posterior.
  3. Que l’script no aturi l’execució completa si un servidor falla.

Un patró típic:

foreach ($server in $serverList) {
try {
        Restart-RemoteService -ComputerName $server -ServiceName $serviceName
    } catch {
        Write-Error "$server: Error en reiniciar el servei: $_"
        Add-Content -Path ".\errores.log" -Value "$($server),$($serviceName),$($_.Exception.Message)"
    }
}

I si fas servir funcions, no retornis errors amb throw llevat que realment vulguis avortar l’script complet. Retorna objectes amb propietats com . Success, . Message, . Server, . Timestamp. Això permet fer reporting després sense haver de parsejar logs de text.

Compte amb els timeouts: PowerShell no sempre espera prou

Una de les trampes clàssiques és pensar que Restart-Service espera a que el servei s’aturi i arrenqui. En realitat, no sempre espera el que hauria, i molt menys en remot. Si el servei triga a aturar-se per dependències, és freqüent que el restart es quedi a mitges.

El que faig és separar l’stop i l’start, amb validació entre mitges. Fins i tot amb petits sleeps. No és elegant, però és el que funciona:

Stop-Service -InputObject $service -Force -ErrorAction Stop
Start-Sleep -Seconds 5
$waited = 0
while ((Get-Service -ComputerName $server -Name $serviceName).Status -ne 'Stopped' -and $waited -lt 30) {
    Start-Sleep -Seconds 1
    $waited++
}
Start-Service -InputObject $service -ErrorAction Stop

Això evita que PowerShell llenci un error per intentar iniciar un servei que encara no ha acabat d’aturar-se.

Evita dependre de l’ usuari que executa: explícita les credencials

Una altra font constant de problemes és assumir que l’usuari que executa l’script té permisos en tots els servidors. Fins i tot en ambients on tens control total, hi ha entorns on els permisos varien segons l’OU, el grup o l’hora del dia (per polítiques rotatives).

PowerShell et permet passar credencials explícites. Sí, és incòmode. Però és millor això que descobrir que el restart no va funcionar perquè l’script el va llançar un operador sense privilegis de servei en un clúster específic.

$cred = Get-Credential
Get-Service -ComputerName $server -Credential $cred -Name $serviceName

Als meus scripts més reutilitzats, les credencials es demanen a l’inici i es mantenen en memòria com a objecte segur ([PSCredential]). No les guardo en disc tret que faci servir un vault (com SecretManagement o KeePass en entorns aïllats).

Logging: no escriguis només en consola

Un error comú és fer scripts que només escriuen en consola. Quan els executes en tasques programades o en sistemes d’automatització (Jenkins, Octopus, System Center), perds visibilitat si no hi ha un log estructurat.

El que faig:

  1. Sortida en consola per a monitoratge ràpid (Write-Output, Write-Warning, etc.).
  2. Log estructurat en CSV per reporting (Export-Csv -Append).
  3. Log en text pla per si cal enviar-lo a suport o compartir.

No recomano logs en format JSON tret que vagin directe a un SIEM o a Log Analytics. Per a administració, el CSV continua sent el més pràctic per a revisar i filtrar.

Proves controlades: sempre inclou un mode “dry-run”

Abans de llançar reinicis en 50 servidors, val la pena tenir una opció de dry-run que et digui què faria l’script sense tocar res. Així pots revisar quins serveis estan, quins falten, i detectar errors de permisos o noms.

Un patró habitual:

param (
[interruptor]$WhatIf
)
if ($WhatIf) {
    Write-Output "$server: Es reiniciaria el servei '$serviceName'"
} else {
    Restart-Service -InputObject $service -Force
}

No tots els cmdlets suporten -WhatIf, així que fer servir el teu propi switch et dona més control. En entorns crítics, aquesta opció és imprescindible. M’ha evitat diversos incidents en hores punta.

Fes servir noms explícits i evita dependències ocultes

Els scripts que més temps m’han fet perdre són els que depenen de variables globals sense declarar-les, funcions sense aïllament i paràmetres poc clars. Fes servir noms de paràmetre com -ServiceName, -ComputerName, -Credential, i documenta les dependències. No assumeixis que tots saben que $servers ja està definit perquè un altre script el va generar.

Quan un script s’executarà en un entorn automatitzat (una tasca de manteniment, un pipeline, una trucada des d’un orquestrador), cada dependència oculta es converteix en una trampa.

Quan no fer servir un script per reiniciar serveis

Hi ha moments en què reiniciar un servei de forma remota i automatitzada no és la solució correcta. Alguns exemples:

  1. Serveis que disparen failovers o reconfiguren un clúster.
  2. Serveis que depenen de dispositius físics (impressores, dongles, sensors).
  3. Serveis que no responen a Restart-Service perquè estan en estat penjat (“Stopping” etern).
  4. Serveis el reinici dels quals requereix intervenció manual posterior (per exemple, confirmació en GUI o reautenticació).

Per a aquests casos, és millor integrar l’script en un playbook més ampli o un sistema de tiquets que avisi l’operador.

Exemple de script a PowerShell per reinici remot de serveis

<#
.SYNOPSIS
    Reinicia un servei específic en una llista de servidors remots.
.DESCRIPTION
    Aquest script valida si el servei existeix i està en execució abans de reiniciar-lo. Es pot executar en mode de prova (dry-run).
    Registra els resultats en consola i en un arxiu CSV.
.PARAMETER ServiceName
    Nom del servei a reiniciar (ex. 'Spooler', 'W3SVC').
.PARAMETER ServerListPath
    Ruta a l'arxiu de text amb la llista de servidors (un per línia).
.PARAMETER Credential
    Credencial opcional per autenticar-se contra els servidors remots.
.PARAMETER WhatIf
    Si s' especifica, no s' executen accions, només es mostra el que es faria.
.EXAMPLE
    .\Reiniciar-Servicio.ps1 -ServiceName 'Spooler' -ServerListPath '.\servidors.txt' -WhatIf
#>
param (
    [Parameter(Mandatory)]
    [string]$ServiceName,
    [Parameter(Mandatory)]
    [string]$ServerListPath,
    [Parameter()]
    [System.Management.Automation.PSCredential]$Credential,
    [switch]$WhatIf
)
# Log de resultats estructurat
$LogPath = ".\log_reinici_servei.csv"
if (-not (Test-Path $LogPath)) {
    "Servidor,Servei,Estat,Missatge,Data" | Out-File -Encoding UTF8 -FilePath $LogPath
}
# Llegir llista de servidors
if (-not (Test-Path $ServerListPath)) {
    Write-Error "No s’ha trobat l’arxiu de servidores: $ServerListPath"
    exit 1
}
$Servers = Get-Content -Path $ServerListPath | Where-Object { $_.Trim() -ne "" }
foreach ($Server in $Servers) {
    $Server = $Server.Trim()
    $timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    try {
        # Obtenir el servei
        if ($Credential) {
            $service = Get-Service -ComputerName $Server -Name $ServiceName -Credential $Credential -ErrorAction Stop
        } else {
            $service = Get-Service -ComputerName $Server -Name $ServiceName -ErrorAction Stop
        }
        if ($service.Status -ne 'Running') {
            Write-Warning "$Server: El servei '$ServiceName' no està en execució. No es reinicia."
            "$Server,$ServiceName,No ejecutado,Servicio detenido,$timestamp" | Out-File -FilePath $LogPath -Append
            continue
        }
        if ($WhatIf) {
            Write-Host "$Server: Es reiniciaria el servei '$ServiceName'"
            "$Server,$ServiceName,Simulado,WhatIf activo,$timestamp" | Out-File -FilePath $LogPath -Append
        } else {
            # Parar i esperar que s'aturi
            if ($Credential) {
                Stop-Service -InputObject $service -Force -Credential $Credential -ErrorAction Stop
            } else {
                Stop-Service -InputObject $service -Force -ErrorAction Stop
            }
            Start-Sleep -Seconds 5
            $waited = 0
            while (
                (Get-Service -ComputerName $Server -Name $ServiceName -Credential:$Credential).Status -ne 'Stopped' -and
                $waited -lt 30
            ) {
                Start-Sleep -Seconds 1
                $waited++
            }
            # Iniciar novament
            if ($Credential) {
                Start-Service -InputObject $service -Credential $Credential -ErrorAction Stop
            } else {
                Start-Service -InputObject $service -ErrorAction Stop
            }
            Write-Host "$Server: Servei '$ServiceName' reiniciat correctament"
            "$Server,$ServiceName,Reiniciat,OK,$timestamp" | Out-File -FilePath $LogPath -Append
        }
    } catch {
        $msg = $_.Exception.Message.Replace("`n"," ").Replace("`r","")
        Write-Error "$Server: Error en procesar el servei '$ServiceName': $msg"
        "$Server,$ServiceName,Fallo,$msg,$timestamp" | Out-File -FilePath $LogPath -Append
    }
}

Exemple d’ execució interactiva

$cred = Get-Credential
.\Reiniciar-Servicio.ps1 -ServiceName 'Spooler' -ServerListPath '.\servidors.txt' -Credential $cred

Contingut de servidors.txt (exemple)

SRV-PRINT01
SRV-PRINT02
SRV-ARXIUS03

Notes finals sobre el disseny de l’ script

  1. No guarda credencials en cap moment. Si les necessita, les demana via Get-Credential i les passa com a objecte segur.
  2. El log és en CSV pera que es pugui filtrar fàcilment amb Excel, Import-Csv o anàlisi posterior.
  3. Gestiona errors de forma controlada: si un servidor falla, els altres segueixen.
  4. L’ús de -WhatIf és manual (no depèn dels cmdlets). Es controla dins del foreach per a màxima compatibilitat.
  5. Accepta ambients on alguns servidors tinguin configuracions diferents, serveis absents o estats anòmals.

Si necessites adaptar-lo a execució des de tasques programades, només cal assegurar-se de passar les credencials com a variable segura o executar sota un compte de servei amb permisos adequats.

Conclusió tècnica

Reiniciar serveis remots amb PowerShell no és complicat, però fer-ho bé i de forma segura porta temps. No es tracta d’escriure l’script més elegant, sinó d’evitar sorpreses en producció. La clau està en gestionar errors, no assumir configuracions homogènies i documentar cada decisió de l’script. Amb el temps, un aprèn a desconfiar del que “hauria de funcionar” i a escriure scripts que funcionen també quan no tot està com hauria.

Deixa un comentari

L'adreça electrònica no es publicarà. Els camps necessaris estan marcats amb *