PowerShell - Windows

Come Riavviare un Servizio su un Server Remoto con PowerShell

La gestione remota dei servizi Windows è una delle attività quotidiane. Che si tratti di applicare una patch, risolvere un blocco applicativo o eseguire una manutenzione programmata, riavviare un servizio su un server remoto tramite PowerShell consente di intervenire rapidamente senza dover aprire una sessione RDP.

Prima di eseguire qualsiasi comando remoto, è necessario verificare che l’ambiente sia correttamente configurato. Windows Remote Management (WinRM) è il protocollo su cui si basa PowerShell Remoting. Per abilitarlo, eseguire il seguente comando sul server di destinazione con privilegi di amministratore

Enable-PSRemoting -Force

Assicurarsi inoltre che le porte necessarie siano aperte qualora sia presente un firewall

  • Porta 5985, PowerShell Remoting standard
  • Porta 5986, PowerShell Remoting sicuro

Inoltre, se i server non appartengono allo stesso dominio Active Directory, aggiungere il server remoto ai Trusted Hosts

Set-Item WSMan:\localhost\Client\TrustedHosts -Value "NomeServer" -Force

Metodo 1: Invoke-Command con Restart-Service

Il metodo più versatile per riavviare un servizio da remoto è combinare Invoke-Command con Restart-Service. Questo approccio sfrutta appieno PowerShell Remoting e funziona su tutte le versioni recenti di PowerShell

Invoke-Command -ComputerName "NomeServer" -ScriptBlock {
    Restart-Service -Name "NomeServizio" -Force
}

Il parametro -Force è fondamentale in quanto forza il riavvio anche quando il servizio ha dipendenze attive o processi figlio in esecuzione. Una buona pratica è includere nella stessa sessione remota una verifica dello stato finale del servizio

Invoke-Command -ComputerName "NomeServer" -ScriptBlock {
    Restart-Service -Name "NomeServizio" -Force
    Start-Sleep -Seconds 3
    $svc = Get-Service -Name "NomeServizio"
    Write-Output "Stato attuale: $($svc.Status)"
}

Metodo 2: Gestione con Credenziali Esplicite

In scenari in cui l’account con cui viene lanciato il comando non dispone dei privilegi necessari sul server remoto, è possibile specificare credenziali alternative. Questa è, ad esempio, la pratica consigliata per operazioni in ambienti con accesso privilegiato gestito (PAM)

$cred = Get-Credential

Invoke-Command -ComputerName "NomeServer" -Credential $cred -ScriptBlock {
    Restart-Service -Name "NomeServizio" -Force
    Get-Service -Name "NomeServizio" | Select-Object Name, Status, StartType
}

L’esecuzione di Get-Credential aprirà una finestra di dialogo sicura per l’inserimento delle credenziali, evitando di scrivere password in chiaro negli script.

Metodo 3: Riavvio su più server contemporaneamente

Una delle funzionalità più potenti di Invoke-Command è la possibilità di eseguire operazioni su più server in parallelo con un singolo comando. Questa capacità riduce drasticamente i tempi di intervento in caso di manutenzioni su infrastrutture distribuite

$servers = @("Server01", "Server02", "Server03")

Invoke-Command -ComputerName $servers -ScriptBlock {
    Restart-Service -Name "NomeServizio" -Force
    [PSCustomObject]@{
        Server  = $env:COMPUTERNAME
        Stato   = (Get-Service -Name "NomeServizio").Status
        Orario  = (Get-Date).ToString("HH:mm:ss")
    }
}

L’output viene restituito in forma tabellare con il nome del server, lo stato del servizio e il timestamp dell’operazione per tenere traccia dell’esito del comando sui server specificati.

Per rendere lo script ancora più pratico è possibile gestire l’elenco dei server tramite un file di testo, specificando i server uno per riga, evitando di modificare lo script ad ogni necessità. Creiamo quindi il file servers.txt come di seguito

Server01
Server02
Server03
Server04.domain.local
192.168.0.100

Il file può contenere nomi NetBIOS, FQDN o indirizzi IP

# Percorso del file con l'elenco dei server
$filePath = "C:\Scripts\servers.txt"

# Lettura delle righe presenti nel file
$servers = Get-Content -Path $filePath |
           Where-Object { $_ -match '\S' } |
           ForEach-Object { $_.Trim() }

Invoke-Command -ComputerName $servers -ScriptBlock {
    Restart-Service -Name "NomeServizio" -Force
    [PSCustomObject]@{
        Server  = $env:COMPUTERNAME
        Stato   = (Get-Service -Name "NomeServizio").Status
        Orario  = (Get-Date).ToString("HH:mm:ss")
    }
}

Script Completo con Logging e Gestione degli Errori

Quando la gestione dei servizi remoti diventa un’attività ricorrente e strutturata occorre avere uno script che, oltre ad essere di semplice utilizzo, possa dare informazioni sulle operazioni fatte e che sia in grado di gestire eventuali errori. Questo permette di automatizzare le operazioni modificando solo il file di testo con l’elenco dei server sia manualmente che in automatico recuperando, ad esempio, le informazioni da un sistema di monitoraggio.

function Restart-RemoteService {
    param(
        [Parameter(Mandatory)]
        [string]$ServersFile,

        [Parameter(Mandatory)]
        [string]$ServiceName,

        [Parameter(Mandatory)]
        [PSCredential]$Credential = (Get-Credential),

        [string]$LogPath = "C:\Logs\ServiceRestart_$(Get-Date -Format 'yyyyMMdd').log"
    )

    if (-not (Test-Path -Path $ServersFile)) {
        Write-Error "Elenco server non trovato: $ServersFile"
        return
    }

    $computerNames = Get-Content -Path $ServersFile |
                     Where-Object { $_ -match '\S' } |
                     ForEach-Object { $_.Trim() }

    if ($computerNames.Count -eq 0) {
        Write-Error "Il file '$ServersFile' non contiene nomi server validi."
        return
    }

    Write-Host "Server trovati ($($computerNames.Count)): $($computerNames -join ', ')" -ForegroundColor Cyan

    $params = @{
        ComputerName = $computerNames
        ScriptBlock  = {
            param($svc)
            try {
                Restart-Service -Name $svc -Force -ErrorAction Stop
                Start-Sleep -Seconds 3
                $status = (Get-Service -Name $svc).Status
                [PSCustomObject]@{
                    Server    = $env:COMPUTERNAME
                    Servizio  = $svc
                    Stato     = $status
                    Successo  = $true
                    Errore    = $null
                    Timestamp = Get-Date
                }
            }
            catch {
                [PSCustomObject]@{
                    Server    = $env:COMPUTERNAME
                    Servizio  = $svc
                    Stato     = "Errore"
                    Successo  = $false
                    Errore    = $_.Exception.Message
                    Timestamp = Get-Date
                }
            }
        }
        ArgumentList = $ServiceName
    }

    $params.Credential = $Credential

    $risultati = Invoke-Command @params

    $risultati | Export-Csv -Path $LogPath -Append -NoTypeInformation -Encoding UTF8
    Write-Host "Log salvato in: $LogPath" -ForegroundColor Green

    $risultati | Format-Table -AutoSize
}

Questo script può essere salvato con il nome Restart-RemoteService.ps1 nella stessa directory dove è presente il file servers.txt

# Caricare la funzione nella sessione corrente
. "C:\Scripts\Restart-RemoteService.ps1"

# Eseguire il comando
Restart-RemoteService -ServersFile "C:\Scripts\servers.txt" -ServiceName "Spooler"

Lo script verifica l’esistenza del file prima di procedere, segnala quanti server ha trovato e gestisce il caso in cui il file sia vuoto.

Abbiamo visto come con PowerShell siamo in grado di gestire i servizi Windows da remoto, adattando i comandi a qualsiasi scenario: dalla semplice operazione su singolo server fino agli interventi automatizzati su centinaia di macchine in parallelo.