Windows PowerShellCreare script una riga alla volta

Don Jones

Nei precedenti articoli ho spesso insistito sul fatto che Windows PowerShell è una shell. È fatta per essere utilizzata in modo interattivo come la shell (o prompt dei comandi) cmd.exe che probabilmente tutti conoscono. Ma Windows PowerShell supporta anche un linguaggio di script più robusto del linguaggio batch di cmd.exe e potente

quanto i linguaggi come VBScript, se non di più. Tuttavia il fatto che Windows PowerShell™ sia una shell interattiva rende la creazione di script molto facile da imparare. In pratica è possibile sviluppare script in modo interattivo all'interno della shell, il che consente di creare gli script una riga alla volta e vedere immediatamente il risultato del proprio lavoro.

Questa tecnica di creazione degli script iterativa facilita anche il debug. Poiché si vedono subito i risultati dello script, è possibile apportare modifiche o correzioni rapidamente quando qualcosa non funziona come previsto.

In questo articolo presenterò un esempio di creazione interattiva di script in Windows PowerShell. Creerò uno script che legge nomi di servizi da un file di testo e imposta la modalità di avvio di ogni servizio su Disattivato.

Quello che mi interessa sia chiaro è il concetto di creare script in Windows PowerShell a pezzetti, invece di cercare di affrontare l'intero script in un solo colpo. È possibile suddividere qualsiasi attività amministrativa si debba eseguire nei suoi componenti e poi stabilire come far funzionare ogni pezzo separatamente. Come vedremo in seguito, Windows PowerShell dispone di ottimi modi per legare insieme tutti questi pezzi. Lavorando su piccole porzioni alla volta, sviluppare lo script finale risulterà più facile.

Lettura di nomi da un file

Scoprire come leggere i file di testo in Windows PowerShell può richiedere pazienza. Se eseguo Help *file*, ottengo semplicemente la guida per il cmdlet Out-File, che invia testo a un file invece di leggerlo dal file. E questo non mi aiuta. I nomi dei cmdlet di Windows PowerShell però hanno una loro logica che posso sfruttare a mio vantaggio. Quando Windows PowerShell recupera qualcosa, il nome del cmdlet normalmente comincia con Get. Eseguo quindi Help Get* per visualizzare questi cmdlet e poi scorro nell'elenco per cercare Get-Content. Sembra promettente! Allora eseguo Help Get-Content per sapere cosa succede (Figura 1) e sembra proprio quello che mi serve.

Figura 1 Esecuzione di Help Get-Content per ottenere ulteriori informazioni

Figura 1** Esecuzione di Help Get-Content per ottenere ulteriori informazioni **(Fare clic sull'immagine per ingrandirla)

Windows PowerShell considera quasi tutto come un oggetto e i file di testo non fanno eccezione. Tecnicamente un file di testo è un insieme di righe, ciascuna delle quali si comporta come un oggetto indipendente. Di conseguenza, se ho creato un file di testo denominato C:\services.txt e vi ho inserito nomi di servizi (ponendo ogni nome su una riga separata all'interno del file), Windows PowerShell può leggere i singoli nomi utilizzando il cmdlet Get-Content. Poiché lo scopo di questa presentazione è dimostrare la creazione interattiva di script, per cominciare mi limiterò ad eseguire Get-Content, assegnarvi il nome del file di testo e vedere cosa succede:

PS C:\> get-content c:\services.txt
messenger
alerter
PS C:\>

Come previsto, Windows PowerShell legge il file e ne visualizza il nome. Naturalmente non voglio solo visualizzare i nomi, ma ora so che Get-Content funziona come mi aspettavo.

Modifica di un servizio

La prossima cosa da fare è modificare la modalità di avvio di un servizio. Di nuovo, per prima cosa devo trovare il cmdlet giusto. Quindi eseguo Help *Service*. Viene visualizzato un elenco breve e il cmdlet Set-Service sembra l'unico adatto alle mie necessità. Voglio testare questo cmdlet per essere sicuro di capire come funziona prima di incorporarlo in uno script. Help Set-Service mostra il funzionamento previsto del cmdlet e basta fare un rapido test per conferma:

PS C:\> set-service messenger -startuptype
    disabled
PS C:\>

Combinazione delle parti

A questo punto devo combinare la possibilità di leggere i nomi dei servizi da un file con il cmdlet Set-Service e qui entrano in gioco le potenti funzionalità di pipeline di Windows PowerShell. Con la pipeline, l'output di un cmdlet può essere inoltrato come input a un secondo cmdlet. La pipeline inoltra oggetti interi. Se si inserisce nella pipeline un insieme di oggetti, la pipeline inoltra ogni oggetto separatamente. Questo significa che l'output di Get-Content, che come ricorderemo è un insieme di oggetti, può essere inviato a Set-Service attraverso la pipeline. Poiché Get-Content inoltra un insieme, ogni oggetto (o riga di testo) dell'insieme viene inoltrato separatamente a Set-Service. Il risultato è che Set-Service viene eseguito una volta per ogni riga del file di testo. Il comando è simile al seguente:

PS C:\> get-content c:\services.txt | 
 set-service -startuptype disabled
PS C:\>

Ed ecco cosa succede:

  1. Il cmdlet Get-Content viene eseguito e legge l'intero file. Ogni riga del file viene considerata come un oggetto separato che con gli altri forma un insieme di oggetti.
  2. L'insieme di oggetti viene inviato a Set-Service attraverso la pipeline.
  3. La pipeline esegue il cmdlet Set-Service una volta per ogni oggetto di input. Per ogni esecuzione, l'oggetto di input viene inoltrato a Set-Service come primo parametro del cmdlet, che è il nome del servizio.
  4. Set-Service viene eseguito utilizzando l'oggetto di input come primo parametro e gli eventuali altri parametri specificati, in questo caso il parametro -startuptype.

La cosa interessante è che a questo punto ho già ottenuto il mio scopo e non ho ancora creato uno script. Questa stessa azione sarebbe difficile da eseguire nella shell Cmd.exe e richiederebbe decine di righe di codice in VBScript. Windows PowerShell invece fa tutto con una riga sola. Però, non ho ancora finito. Come si vede, il comando non fornisce molte informazioni di stato o feedback. A parte l'assenza di errori, è difficile vedere se effettivamente è successo qualcosa. Ora che ho acquisito dimestichezza con la funzionalità necessaria per eseguire l'attività in questione, posso iniziare a migliorarla passando alla vera e propria creazione di uno script.

Windows PowerShell Prompt Here di Michael Murgolo (in inglese)

Uno dei più famosi Microsoft® PowerToys for Windows® e uno dei miei preferiti è lo strumento Open Command Window Here (Apri finestra di comando qui). Disponibile tra i Microsoft PowerToys for Windows XP o negli strumenti del Resource Kit di Windows Server® 2003, Open Command Window Here consente di fare clic con il pulsante destro del mouse su una cartella o un'unità disco in Esplora risorse per aprire una finestra di comando impostata sulla cartella selezionata.

Mentre stavo approfondendo la mia conoscenza su Windows PowerShell, mi sono trovato a desiderare che avesse la stessa funzionalità. Allora ho preso il file .inf di configurazione di Open Command Window Here, cmdhere.inf, dagli strumenti del Resource Kit di Windows Server 2003 e l'ho modificato per creare un menu di scelta rapida "Windows PowerShell Prompt Here". Questo file .inf si trova nel post di blog originale sul quale si basa questo articolo (disponibile all'indirizzo leeholmes.com/blog/PowerShellPromptHerePowerToy.aspx). Per installare lo strumento basta fare clic con il pulsante destro del mouse sul file .inf e selezionare Installa.

Mentre creavo la versione per Windows PowerShell di questo strumento, ho scoperto un bug nell'originale: se si disinstallava Open Command Window Here, rimaneva una voce del menu di scelta rapida attiva. Quindi ho creato una versione aggiornata di cmdhere.inf, anch'essa disponibile nel post di blog originale.

Entrambi questi PowerToys sfruttano il fatto che queste voci del menu di scelta rapida sono configurate nel Registro di sistema all'interno delle chiavi associate ai tipi di oggetto Directory e Drive, nello stesso modo in cui le azioni del menu di scelta rapida sono associate ai tipi di file. Ad esempio, quando si fa clic con il pulsante destro del mouse su un file .txt in Esplora risorse, all'inizio dell'elenco vengono visualizzate diverse azioni (ad esempio Apri, Stampa e Modifica). Per capire come sono state configurate queste voci, esaminiamo l'hive del Registro di sistema HKEY_CLASSES_ROOT.

Aprendo l'editor del Registro di sistema ed espandendo HKEY_CLASSES_ROOT, vengono visualizzate le chiavi con i nomi dei tipi di file come .doc, .txt e così via. Facendo clic sulla chiave .txt, si vede che il valore (Default) è txtfile (vedere la Figura A). Questo è il tipo di oggetto associato a un file .txt. Scorrendo verso il basso ed espandendo la chiave txtfile e poi la chiave shell al suo interno, vengono visualizzate le chiavi con i nomi di alcune delle voci del menu di scelta rapida per un file .txt. Non vengono visualizzate tutte le chiavi, perché vi sono altri modi per creare menu di scelta rapida. In ognuna di queste chiavi c'è una chiave di comando. Il valore (Default) nella la chiave di comando è la riga di comando che Windows esegue quando si seleziona tale voce dal menu di scelta rapida.

Gli strumenti per il prompt dei comandi e per il prompt di Windows PowerShell utilizzano questa tecnica per configurare le voci del menu di scelta rapida Open Command Window Here (Apri finestra di comando qui) e Windows PowerShell Prompt Here. Non vi sono tipi di file associati a unità e directory, ma vi sono chiavi Drive e Directory in HKEY_CLASSES_ROOT associate a tali oggetti.

Figura A Le voci del menu di scelta rapida sono configurate nel Registro di sistema

Figura A** Le voci del menu di scelta rapida sono configurate nel Registro di sistema **(Fare clic sull'immagine per ingrandirla)

Michael Murgolo è un consulente senior per le infrastrutture dei servizi di consulenza Microsoft. Lavora in particolare su sistemi operativi, distribuzione, servizi di rete, Active Directory, gestione dei sistemi, automazione e gestione delle patch.

Creazione interattiva di script

Una cosa da fare è eseguire più cmdlet per ogni servizio elencato in C:\services.txt. In questo modo potrò visualizzare il nome del servizio e qualsiasi altra informazione necessaria per poter seguire l'avanzamento dello script.

Prima ho utilizzato la pipeline per inoltrare oggetti da un cmdlet a un altro. Questa volta invece utilizzerò una tecnica più simile agli script definita "costrutto Foreach" che ho presentato nell'articolo del mese scorso. Il costrutto Foreach può accettare un insieme di oggetti ed eseguire più cmdlet per ogni oggetto dell'insieme. Indico una variabile che rappresenti l'oggetto corrente per l'intero ciclo. Ad esempio, il costrutto potrebbe iniziare nel modo seguente:

foreach ($service in get-content c:\services.txt)

Sto ancora eseguendo lo stesso cmdlet Get-Content per recuperare il contenuto del file di testo. Questa volta chiedo al costrutto Foreach di riprodurre a ciclo continuo l'insieme di oggetti restituiti da Get-Content. Il ciclo viene eseguito una volta per ogni oggetto e l'oggetto corrente viene inserito nella variabile $service. A questo punto devo solo specificare il codice che deve essere eseguito all'interno del ciclo. Per prima cosa cercherò di riprodurre il comando di una riga originale, per semplificare al massimo ed evitare di perdere funzionalità:

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> }
>>
PS C:\>

Qui ci sono alcune cose interessanti. Come si può notare, la prima riga del costrutto Foreach con una parentesi graffa aperta. Tutto ciò che si trova tra parentesi graffe è considerato interno al ciclo Foreach e verrà eseguito una volta per ogni oggetto nell'insieme di input. Dopo avere inserito la parentesi graffa aperta { e premuto Invio, il prompt di Windows PowerShell è diventato >> (Figura 2). Questo indica che PowerShell sa che ho iniziato un costrutto e sta aspettando che lo concluda. Poi ho digitato il cmdlet Set-Service. Questa volta ho utilizzato $service come primo parametro, dato che rappresenta il nome del servizio corrente letto dal file di testo. Nella riga seguente concludo il costrutto con una parentesi graffa chiusa. Dopo avere premuto due volte Invio, Windows PowerShell esegue immediatamente il codice.

Figura 2 Windows PowerShell sa che è stato iniziato un costrutto

Figura 2** Windows PowerShell sa che è stato iniziato un costrutto **(Fare clic sull'immagine per ingrandirla)

Funziona, immediatamente. Quello che ho scritto assomiglia a uno script, ma Windows PowerShell lo sta eseguendo in tempo reale e non viene memorizzato in nessun file di testo. Ora riscriverò tutto, aggiungendo una riga di codice per visualizzare il nome del servizio corrente:

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> "Disabling $service"
>> }
>>
Disabling messenger
Disabling alerter
PS C:\>

Si noti che sto chiedendo di visualizzare qualcosa e ho racchiuso questo qualcosa tra virgolette doppie. Le virgolette indicano a Windows PowerShell che questa è una stringa di testo e non un altro comando. Tuttavia, quando si usano virgolette doppie invece delle virgolette singole, Windows PowerShell cerca eventuali variabili nella stringa di testo. Se ne trova una, sostituisce al nome della variabile il suo valore effettivo. In questo modo, quando viene eseguito questo codice, si vedrà visualizzato il nome del servizio corrente.

Ma questo non è ancora uno script!

Finora ho utilizzato Windows PowerShell in modo interattivo, un ottimo sistema per vedere immediatamente i risultati di quello che si scrive. Ma prima o poi mi stancherò di riscrivere in continuazione queste righe di codice. Per questo Windows PowerShell può anche eseguire script. In realtà, gli script di Windows PowerShell seguono la definizione più letterale di script: essenzialmente Windows PowerShell legge semplicemente il file di testo dello script e "digita" ogni riga che trova, esattamente come se l'utente stesse digitando le righe manualmente. Questo significa che tutto ciò che ho fatto fino a questo punto può essere incollato in un file di script. Quindi ora utilizzerò il Blocco note per creare il file disableservices.ps1 nel quale incollerò quanto segue:

foreach ($service in get-content c:\services.txt) {
 set-service $service -startuptype disabled
 "Disabling $service"
 }

Ho collocato questo file nella cartella C:\test. Ora cercherò di eseguirlo da Windows PowerShell:

PS C:\test> disableservices
'disableservices' is not recognized as a cmdlet, function, operable program, or
<script file.
At line:1 char:15
+ disableservices <<<<
PS C:\test>

Ops... Dove ho sbagliato? Windows PowerShell è impostato sulla cartella C:\test, ma non ha trovato lo script. Perché? Per motivi di sicurezza, Windows PowerShell è progettato in modo da non eseguire mai script dalla cartella corrente, per impedire che uno script possa dirottare un comando del sistema operativo. Ad esempio, non posso creare uno script dir.ps1 e fare in modo che venga eseguito al posto del normale comando dir. Se devo eseguire uno script dalla cartella corrente, devo specificare un percorso relativo:

PS C:\test> ./disableservices
The file C:\test\disableservices.ps1 cannot be loaded. 
The execution of scripts is disabled on this system. 
Please see "get-help about_signing" for more details.
At line:1 char:17
+ ./disableservices <<<<
PS C:\test>

E adesso? Ancora non funziona. Il percorso è quello giusto, ma Windows PowerShell avvisa che non è possibile eseguire script. Questo accade perché, per impostazione predefinita, Windows PowerShell non è in grado di eseguire script. Ancora una volta si tratta di una precauzione di sicurezza per evitare i problemi che conosciamo tutti con VBScript. Per impostazione predefinita, gli script dannosi non possono essere eseguiti in Windows PowerShell perché nessuno script può essere eseguito per impostazione predefinita. Per eseguire lo script, devo modificare esplicitamente i criteri di esecuzione:

PS C:\test> set-executionpolicy remotesigned

Il criterio di esecuzione RemoteSigned consente l'esecuzione di script senza firma dal computer locale. Gli script scaricati devono sempre avere una firma per poter essere eseguiti. Un criterio migliore è AllSigned, che esegue solo script che hanno una firma digitale basata su un certificato emesso da un autore attendibile. Io però non ho sottomano un certificato e non posso firmare i miei script, quindi RemoteSigned è una buona scelta per questa situazione. Ora riproverò a eseguire lo script:

PS C:\test> ./disableservices
Disabling messenger
Disabling alerter
PS C:\test>

Voglio far notare che il criterio di esecuzione RemoteSigned che stiamo usando non è la scelta ottimale, ma solo una buona scelta. C'è infatti una soluzione di gran lunga migliore. Sarebbe molto più sicuro ottenere un certificato di firma codice, utilizzare il cmdlet Set-AuthenticodeSignature di Windows PowerShell per firmare i miei script e impostare il criterio di esecuzione AllSigned, che offre una protezione maggiore.

Esiste anche un altro criterio, Unrestricted, che è decisamente da evitare. Questo criterio consente l'esecuzione senza restrizioni di qualsiasi script, anche gli script dannosi provenienti da posizioni remote, il che può rappresentare un grosso rischio. Consiglio quindi di non utilizzare il criterio Unrestricted per alcun motivo.

Risultati immediati

Le funzionalità di script interattive di Windows PowerShell consentono di collaudare rapidamente gli script o anche solo piccole parti di script. Si ottengono risultati immediati e quindi si possono facilmente modificare gli script per ottenere esattamente i risultati desiderati. Alla fine è possibile spostare il codice in un file .ps1 per renderlo permanente e facilmente accessibile in futuro. E ricorda: se possibile, è necessario applicare una firma digitale a questi file .ps1 in modo da poter lasciare Windows PowerShell impostato sul criterio di esecuzione AllSigned, l'impostazione più sicura tra quelle che consentono l'esecuzione degli script.

Don Jones è il guru degli script di SAPIEN Technologies e coautore di Windows PowerShell: TFM (vedere www.SAPIENPress.com). Contatta Don all'indirizzo don@sapien.com.

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