Windows PowerShellLa forza del filtraggio

Don Jones

Nell'articolo del mese scorso, ho illustrato le capacità e la flessibilità della pipeline di Windows PowerShell, che consente di passare un set di dati, o più precisamente un flusso di oggetti, da un cmdlet all'altro, perfezionandolo ulteriormente fino a ottenere il risultato desiderato. Ho inoltre detto che gli script e

non solo i cmdlet, possono sfruttare i vantaggi della pipeline di Windows PowerShell. Questo mese desidero trattare questo argomento più in dettaglio.

Una delle operazioni di cui mi occupo più spesso in Windows PowerShell™ è la creazione di script che agiscono su più computer remoti, solitamente tramite Strumentazione gestione Windows® (WMI). Analogamente a qualunque attività che si occupa dei computer remoti, c'è sempre la possibilità che uno o più computer non siano disponibili quando lo script viene eseguito. Quindi, ho bisogno che gli script siano in grado di affrontare questo problema.

Naturalmente, ci sono molti modi con cui è possibile fare in modo che uno script possa gestire un timeout di connessione WMI, ma questo approccio non mi piace molto poiché il periodo di timeout stesso è lungo, per impostazione predefinita circa 30 secondi. Questo potrebbe rallentare ulteriormente l'esecuzione dello script se deve attendere il verificarsi di più timeout. Invece, voglio che il mio script esegua un rapido controllo per vedere se un determinato computer è attualmente in linea prima di tentare di stabilire la connessione a esso.

Il paradigma di Windows PowerShell

Con altri linguaggi di script, ad esempio VBScript, mi occuperei di un computer alla volta. Ovvero, recupererei un nome di computer, forse da un elenco di nomi memorizzato in un file di testo ed eseguirei il ping del sistema per verificarne la disponibilità. Se disponibile, stabilirei la connessione WMI ed effettuerei tutte le altre operazioni necessarie. Questo è un normale approccio di script. Infatti, probabilmente inserirei tutto il codice in un ciclo che ripeterei per ogni computer con cui devo stabilire la connessione.

Windows PowerShell, tuttavia, è più adatto a operazioni batch grazie ai suoi oggetti di base e alla sua capacità di lavorare direttamente con gruppi o raccolte di oggetti. Il paradigma in Windows PowerShell non è lavorare con singoli oggetti o parti di dati, ma piuttosto lavorare con interi gruppi, perfezionando il gruppo bit per bit fino a ottenere il risultato desiderato. Ad esempio, anziché recuperare un nome di computer alla volta dal mio elenco, leggerei l'intera raccolta di nomi tutta insieme. E, anziché eseguire il ping di ciascun computer in un ciclo, creerei una singola routine in grado di accettare una raccolta di nomi, ne eseguirei il ping ed estrarrei i nomi che possono essere contattati. La fase successiva del mio processo sarebbe quindi stabilire la connessione WMI ai restanti nomi, quelli che possono essere raggiunti tramite il ping.

Windows PowerShell utilizza questo approccio per numerose attività. Ad esempio, per ottenere un elenco dei servizi in esecuzione, è possibile utilizzare qualcosa del genere:

Get-Service | Where-Object { $_.Status –eq "Running" }

La figura 1 mostra l'aspetto dell'output sul computer. Anziché controllare ciascun servizio uno alla volta, ho recuperato tutti i servizi tramite Get-Service, li ho reindirizzati come Where-Object e quindi ho escluso quelli che non venivano eseguiti. Questo è più o meno quello che desidero fare con il mio script: ottenere un elenco di nomi di computer, escludere quelli di cui non è possibile eseguire il ping e passare l'elenco dei computer di cui è possibile eseguire il ping alla fase successiva.

Figura 1 Come ottenere un elenco di computer di cui eseguire il ping

Figura 1** Come ottenere un elenco di computer di cui eseguire il ping **(Fare clic sull'immagine per ingrandirla)

Funzioni di filtraggio

Sebbene potrei creare un apposito cmdlet per questo, non lo farò. La creazione del cmdlet richiede Visual Basic® o C# e una buona dose di competenza con Microsoft® .NET Framework. Cosa più importante, richiede più impegno di quanto desidero concedere per questa attività. Fortunatamente, Windows PowerShell consente di creare un tipo speciale di funzione, denominata filtro, che è assolutamente in grado di funzionare nella pipeline. Il profilo di base di una funzione di filtro è simile alla seguente:

function <name> {
  BEGIN {
    #<code>
  }
  PROCESS {
    #<code>
  }
  END {
    #<code>
  }
}

Come è possibile notare, questa funzione contiene tre blocchi di script indipendenti, denominati BEGIN, PROCESS ed END. Una funzione di filtraggio, ovvero una funzione specifica per la pipeline che consente di filtrare gli oggetti, può avere qualsiasi combinazione di questi tre blocchi di script, in base a quello che si desidera effettuare. Questi blocchi di script funzionano nel modo seguente:

  • Il blocco BEGIN viene eseguito quando la funzione viene richiamata per la prima volta. Se necessario, è possibile utilizzare questo blocco per eseguire operazioni di configurazione.
  • Il blocco PROCESS viene eseguito una volta per ogni oggetto pipeline passato alla funzione. La variabile $_ rappresenta l'oggetto di input della pipeline corrente. Il blocco PROCESS è necessario in una funzione di filtraggio.
  • Il blocco END viene eseguito dopo che tutti gli oggetti pipeline sono stati elaborati. Questo può essere utilizzato per qualunque lavoro di finalizzazione, se necessario.

Nel mio esempio, desidero creare una funzione di filtraggio che accetti una raccolta di nomi come oggetti di input e che quindi tenti di eseguire il ping su ciascuno di essi. Ogni oggetto di cui è possibile eseguire il ping, verrà emesso nella pipeline; i sistemi sui quali non è possibile eseguire il ping verranno semplicemente abbandonati. Poiché la funzionalità ping non richiede alcuna configurazione o finalizzazione speciale, utilizzerò semplicemente il blocco PROCESS. Il codice riportato nella figura 2 fornisce lo script completo.

Figure 2 Ping-Address e Restart-Computer

1  function Ping-Address {
2    PROCESS {
3      $ping = $false
4      $results = Get-WmiObject -query `
5      "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"
6      foreach ($result in $results) {
7        if ($results.StatusCode -eq 0) {
8          $ping = $true
9        }
10      }
11      if ($ping -eq $true) {
12        Write-Output $_
13      }
14    }
15  }
16
17  function Restart-Computer {
18    PROCESS {
19      $computer = Get-WmiObject Win32_OperatingSystem -computer $_
20      $computer.Reboot()
21    }
22  }
23
24  Get-Content c:\computers.txt | Ping-Address | Restart-Computer

Tenere presente che ho definito due funzioni: Ping-Address e Restart-Computer. In Windows PowerShell, le funzioni devono essere definite prima che possano essere richiamate. Di conseguenza, la prima riga eseguibile del mio script è la riga 24, che utilizza il cmdlet Get-Content per ottenere un elenco di nomi di computer da un file (un nome di computer per riga). Questo elenco, tecnicamente una raccolta di oggetti String, viene trasmesso alla funzione Ping-Address, che scarta ogni computer di cui non è possibile eseguire il ping. I risultati vengono inviati alla funzione Restart-Computer, che utilizza WMI per riavviare in remoto ogni computer di cui è possibile eseguire il ping.

La funzione Ping-Address implementa un blocco di script PROCESS, questo significa che la funzione si aspetta una raccolta di input di oggetti. Il blocco di script PROCESS si occupa automaticamente di tale input; non è necessario pertanto definire nessun argomento per contenere l'input. Inizio il processo alla riga 3 impostando la variabile $ping su $false, una variabile incorporata di Windows PowerShell che rappresenta il valore booleano False.

Quindi, utilizzo la classe WMI Win32_PingStatus per eseguire il ping del computer specificato. Notare che alla riga 5 la variabile $_ che rappresenta l'oggetto pipeline, è incorporata nella stringa di query WMI. Quando una stringa è racchiusa tra virgolette doppie, Windows PowerShell tenterà sempre di sostituire le variabili quale $_ con il relativo contenuto, in modo da non dover perdere tempo con la concatenazione di tipo stringa. Utilizzo questa funzionalità alla riga 5.

La riga 6 è un ciclo che verifica i risultati del ping. Per i risultati con esito positivo (cioè con un valore di StatusCode pari a zero), imposto $ping con lo stesso valore di $true. Alla riga 11, verifico che $ping sia stato impostato con il valore di $true. In tal caso, imposto l'oggetto di input originale sul flusso di output predefinito. Windows PowerShell gestisce automaticamente il flusso di output predefinito. Se questa funzione si trova alla fine della pipeline, il flusso di output viene convertito in una rappresentazione di testo. Se nella pipeline sono presenti più comandi, gli oggetti del flusso di output, in questo caso gli oggetti stringa che contengono i nomi dei computer, vengono passati al comando della pipeline successivo.

La funzione Restart-Computer è un po' più semplice, ma utilizza anche un blocco PROCESS per partecipare facilmente alla pipeline. Alla riga 19, la funzione stabilisce la connessione al computer e recupera la relativa classe WMI Win32_OperatingSystem. La riga 20 esegue il metodo Reboot per il riavvio del computer remoto.

La 24 è la riga dove vengono eseguiti tutti i processi. Ovviamente bisogna prestare molta attenzione quando si esegue questo script, poiché riavvia tutti i computer presenti in C:\computers.txt, cosa che potrebbe avere effetti disastrosi se i nomi presenti nel file non sono corretti.

Passaggi successivi

Questo script non è del tutto invulnerabile. Dovrei considerare la possibilità di un errore WMI non correlato alla connettività di base, ad esempio un blocco firewall sul computer remoto o un errore di protezione WMI. Posso gestire questi problemi implementando un gestore di trap, che tra l'altro sembra essere l'argomento giusto per un articolo futuro.

Inoltre, questo script esegue un'azione piuttosto importante, il riavvio di un computer remoto. Qualunque script che tenti tale azione potenzialmente pericolosa, dovrebbe implementare due parametri di Windows PowerShell comuni, –Confirm e –WhatIf. Questo argomento richiederebbe più spazio di quello ora disponibile, ma anche questo è un ottimo argomento per un altro articolo. Nel frattempo, è possibile contattare il blog del team di Windows PowerShell; l'architetto Jeffrey Snover si occupa di questo argomento.

Anche senza entrare nello specifico di queste funzionalità, è possibile farsi un'idea del loro funzionamento. Perfezionerò questa tecnica negli articoli successivi, mostrando in che modo queste funzioni possono modulare il codice riutilizzabile e migliorare la funzionalità degli script in generale.

Don Jones è il guru degli script di SAPIEN Technologies e coautore di Windows PowerShell: TFM (SAPIEN Press, 2007). È possibile contattare Don all'indirizzo www.ScriptingAnswers.com.

© 2008 Microsoft Corporation e CMP Media, LLC. Tutti i diritti riservati. È vietata la riproduzione completa o parziale senza autorizzazione.