Sei sulla pagina 1di 464

Windows

PowerShell 2.0
Guida completa

Efran Cobisi
Windows PowerShell 2.0 - Guida completa

Autore: Efran Cobisi

Collana:

Publisher: Fabrizio Comolli


Progetto grafico: escom - Milano
ISBN: 978-88-6604-042-2

Copyright © 2010 Edizioni FAG Milano

Via G. Garibaldi 5 – 20090 Assago (MI) - www.fag.it

Nessuna parte del presente libro può essere riprodotta, memorizzata in un sistema che ne
permetta l’elaborazione, né trasmessa in qualsivoglia forma e con qualsivoglia mezzo
elettronico o meccanico, né può essere fotocopiata, riprodotta o registrata altrimenti, senza
previo consenso scritto dell’editore, tranne nel caso di brevi citazioni contenute in articoli di
critica o recensioni.

La presente pubblicazione contiene le opinioni dell’autore e ha lo scopo di fornire


informazioni precise e accurate. L’elaborazione dei testi, anche se curata con scrupolosa
attenzione, non può comportare specifiche responsabilità in capo all’autore e/o all’editore
per eventuali errori o inesattezze.
Nomi e marchi citati nel testo sono generalmente depositati o registrati dalle rispettive
aziende. L’autore detiene i diritti per tutte le fotografie, i testi e le illustrazioni che
compongono questo libro.
Prefazione

Windows PowerShell 2.0 è un innovativo strumento di automazione,


creato da Microsoft per gestire i sistemi operativi Windows tramite
un’eccellente shell a riga di comando e un linguaggio di scripting
basato sul framework Microsoft .NET.
Rispetto alle shell realizzate in precedenza da Microsoft, come CMD
e COMMAND.COM, PowerShell è un prodotto completamente
nuovo, progettato fin dall’inizio con l’obiettivo di rispondere alle
moderne necessità degli amministratori di sistema, professionisti IT
e sviluppatori. Nonostante la presenza di un’interfaccia a riga di
comando testuale, che potrebbe trarre in inganno, PowerShell è una
piattaforma orientata agli oggetti ed è completamente integrata con il
framework Microsoft .NET, che consiste in un esteso insieme di
librerie in grado di rispondere a qualsiasi esigenza di sviluppo,
compresa la manipolazione di dati e testi, l’interazione con database
e file XML, la gestione del registry e del log degli eventi di Windows,
il supporto per le comunicazioni di rete, la creazione di interfacce
grafiche e molto altro ancora.
A differenza dei predecessori e delle shell supportate dai sistemi
operativi *nix, come bash, ksh o csh, PowerShell offre il vantaggio di
poter compiere qualsiasi operazione senza essere dipendente da
tool esterni. Per ogni comando delle shell menzionate esiste un
equivalente in PowerShell, che spesso offre un livello di funzionalità
più elevato grazie alla struttura a oggetti assunta dalle informazioni e
dai comandi stessi. La maggior parte dei tool supportati dalle shell
*nix, inoltre, è già presente in PowerShell sotto forma di comando
nativo e, qualora non lo fosse, la completa integrazione con il
framework Microsoft .NET darebbe all’utente l’autonomia per
raggiungere lo stesso risultato.
L’obiettivo di questo libro consiste nel fornire al lettore tutto il
materiale necessario per conoscere Windows PowerShell e produrre
script di automazione con questo prodotto, partendo dalla riga di
comando. Tuttavia, poiché la shell è un punto di congiunzione tra il
mondo degli amministratori di sistema e quello degli sviluppatori, nel
libro è dedicato ampio spazio anche alle principali tecnologie di
gestione di Windows e alle tematiche che riguardano da vicino il
framework Microsoft .NET.
Struttura del libro
Il libro è organizzato in cinque diverse parti, in modo tale da facilitare
l’apprendimento e la consultazione sia per il lettore che si accinge
per la prima volta a utilizzare una shell a riga di comando sia per chi
ha già precedenti esperienze in merito oppure ha esigenze
specifiche da soddisfare.

Parte I – Introduzione
Contiene le informazioni necessarie per il primo avvio di PowerShell,
per eseguire i primi comandi e ottenere indicazioni dalla guida in
linea.
Parte II – Sintassi di base
Raggruppa i capitoli che trattano gli elementi sintattici fondamentali e
la struttura del linguaggio di scripting della shell: una volta terminata
la lettura di questa parte si possono già realizzare i primi script.
Parte III – Elaborazione dei dati
I capitoli di questa parte contengono un’analisi dettagliata delle
principali tipologie di dato supportate da PowerShell e delle
funzionalità messe a disposizione dal framework Microsoft .NET per
gestirle.

Parte IV – Amministrazione del sistema


Illustra le principali tecniche messe a disposizione da PowerShell per
amministrare Windows. Include la gestione dei processi e dei servizi,
del file system, del registro eventi di Windows e l’integrazione con
COM e WMI.
Parte V – Funzionalità avanzate
Questa parte comprende i capitoli che analizzano le funzionalità più
avanzate della shell ed è destinata al lettore che abbia maturato
esperienza con gli argomenti trattati nelle parti precedenti del libro;
sono esposti concetti come i job in background, l’esecuzione di task
in remoto, la customizzazione dell’host e la gestione degli errori.
Requisiti tecnici
Per usare PowerShell 2.0 è necessario disporre di uno tra i seguenti
sistemi operativi:
• Windows XP (con SP3);
• Windows Vista (con SP1);
• Windows 7;
• Windows Server 2003 (con SP2) o 2003 R2 (con SP2);
• Windows Server 2008 (con SP1) o 2008 R2.
Gli esempi illustrati nel corso del libro sono stati realizzati utilizzando
alternativamente Windows 7 e Windows Server 2008. Quanto
presentato, tuttavia, è perfettamente compatibile con tutti i sistemi
operativi menzionati.
Convenzioni
Rispetto al materiale didattico presente nella guida di riferimento
ufficiale di PowerShell, in questo volume si è cercato di ridurre la
complessità delle informazioni presentate, tralasciando talvolta
alcuni dettagli o alcune opzioni dei comandi. Nel corso del libro,
tuttavia, sono analizzate le tecniche che permettono di recuperare
queste informazioni in autonomia qualora lo si desiderasse,
all’interno della shell stessa. Rispetto alla guida ufficiale, inoltre, la
sintassi dei comandi è stata semplificata in modo tale da presentare
tre semplici casistiche distinte:
• testo racchiuso tra parentesi quadre: indica un elemento
opzionale;
• testo racchiuso tra parentesi angolari: indica un elemento
obbligatorio, da specificare;
• testo normale: indica il nome del comando o di un suo
parametro e va digitato così com’è.
Nello schema sintattico che segue, per esempio, i testi Get-Help e -
Examples vanno riportati così come sono indicati, mentre <Nome comando> va

sostituito con il valore desiderato; le parentesi quadre di -Examples


indicano, poi, che questo elemento è opzionale:
Get-Help <Nome comando> [-Examples]

Ciascun esempio proposto all’interno del libro, infine, è frutto della


diretta trascrizione di uno o più script realizzati per l’occasione e può
presentare riferimenti a uno o più utenti reali. Diversamente da altre
opere, infatti, si è scelto di non sopprimere questi dettagli per
consentire al lettore di familiarizzare più velocemente con i concetti
esposti.
Informazioni sull’autore
Efran Cobisi si occupa di architettura e sviluppo di soluzioni
software con la piattaforma Microsoft .NET e Microsoft SQL Server.
Dal 2001, Efran è legato in modo indissolubile con il
framework ASP.NET, di cui ha testato le prime
versioni beta fornendo feedback tecnico agli
sviluppatori Microsoft. Studiando da vicino questa
tecnologia, acquisisce nel 2004 il titolo di Microsoft
Certified Professional, per poi qualificarsi come
Microsoft Certified Application Developer.
Nel 2009, infine, completa il percorso di
certificazione da sviluppatore diventando Microsoft Certified Solution
Developer for Microsoft .NET
È specializzato, inoltre, nello sviluppo di soluzioni legate a SQL
Server ed al suo motore di esecuzione: è legato a questo RDBMS
sin dalla versione 7.0 e continua a seguirne l’evoluzione, anno dopo
anno. È Microsoft Certified Technology Specialist su SQL Server
2005.
Efran si occupa attivamente di training per sviluppatori professionisti
e nel 2007 ha conseguito il titolo di Microsoft Certified Trainer. È
stato, inoltre, mentore del gruppo DOTNET-WEB di DevelopMentor
e collabora con diversi portali ed editori (tra cui Edizioni Master ed
ASPItalia.com) in qualità di autore di articoli tecnici di informatica per
sviluppatori.
Dal 2007 è community lead di powershell.it, la community italiana di
Windows PowerShell. Efran è nato a Padova nel 1981 e vive in
questa città con Laura, la sua compagna.
Informazioni su powershell.it
Powershell.it è la community italiana di Windows PowerShell,
fondata nel 2007 con lo scopo di diffondere e condividere la
conoscenza di questa shell a riga di comando tra i professionisti IT e
gli sviluppatori interessati all’automazione di Windows attraverso il
framework Microsoft .NET
È gestita e alimentata da diversi professionisti dell’IT, tra cui trainer,
sistemisti e sviluppatori certificati.
La community, tramite il sito www.powershell.it, offre gratuitamente
una serie di servizi e contenuti, tra i quali:
• articoli tecnici di diverso livello per sistemisti, professionisti IT,
amministratori di rete e di database e sviluppatori;
• un forum moderato, dove sono trattati temi nell’ambito
dell’automazione e dell’amministrazione di sistema;
• la guida di riferimento – costantemente aggiornata – di tutti i
comandi disponibili in PowerShell. Contiene sia i comandi creati
da Microsoft sia quelli aggiunti alla shell da altri produttori;
• una sezione dedicata agli strumenti di sviluppo per PowerShell.

La registrazione a powershell.it è completamente gratuita e


consente di ricevere le newsletter periodiche della community, con
aggiornamenti e novità su PowerShell.
Errata
Durante la stesura e la revisione di questo volume è stata posta la
massima attenzione per individuare e rimuovere eventuali refusi ma,
tuttavia, è ancora possibile che siano presenti delle imperfezioni; il
documento con gli eventuali errata è pubblicato sul sito powershell.it
ed è consultabile a questo indirizzo:
http://www.powershell.it/Libri/Errata.aspx
Ringraziamenti
Questo libro è frutto di diversi mesi di studio, preparazione, stesura,
revisione e impaginazione: non sarebbe stato possibile realizzarlo
senza il contributo di diverse persone. Ringrazio innanzitutto la mia
compagna Laura, per tutto l’amore e la comprensione che mi ha
donato in questi mesi di duro lavoro e sacrificio: il suo continuo
supporto ed incoraggiamento sono stati per me davvero
fondamentali.
Desidero poi ringraziare FAG Edizioni, nella persona di Fabrizio
Comolli, per aver creduto con così tanta passione in questo progetto;
grazie a Marco Aleotti e Roberta Venturieri di Escom, per il design
creativo, il supporto e l’ottimo lavoro di impaginazione. Un
ringraziamento speciale va a Jeffrey Snover, di Microsoft Corp., per i
preziosi consigli e la disponibilità dimostrata nel rispondere a ogni
mio quesito tecnico su PowerShell, e a tutto il team di progettisti e
sviluppatori che ha lavorato a questo fantastico prodotto.
Ringrazio Microsoft Italia, per il ruolo attivo che gli evangelist e tutto
il team TechNet hanno assunto rispetto a powershell.it e al progetto
di questo volume; ringrazio in particolare Giorgio Malusardi, per il
suo continuo supporto e interessamento. Ringrazio, poi, gli amici di
QBGROUP, per aver compreso l’importanza di questa attività e
avermi facilitato nel portarla a termine.
Grazie, infine, a tutta la mia famiglia, per avermi dato la possibilità di
raggiungere questo obiettivo: ringrazio, in particolare, mio padre
Franco, che mi ha avvicinato per la prima volta al mondo
dell’informatica nell’era di MS-DOS e GW-Basic e che, purtroppo,
non ha fatto in tempo a vedere questo volume realizzato.
Parte I
Introduzione
Installazione e primo avvio

Un’analisi dei requisiti minimi per poter installare


PowerShell sul proprio sistema e una panoramica
delle interfacce disponibili per utilizzare la shell.

A partire dalla versione 2.0, PowerShell è parte integrante di


Windows Management Framework (WMF), un pacchetto creato da
Microsoft con l’obiettivo di fornire un’infrastruttura di gestione
comune a tutte le versioni di Windows a partire da Windows XP.
L’utilizzo della shell, pertanto, è subordinato all’installazione di
questo pacchetto: il seguito del capitolo illustra i passaggi necessari
per portare a termine questa attività preliminare e avviare
PowerShell.

Windows Management Framework


Oltre a PowerShell, WMF comprende altri due componenti creati con
l’obiettivo di aumentare la capacità di automazione del sistema
operativo: Windows Remote Management (WinRM) e Background
Intelligent Transfer Service (BITS). WinRM è un’implementazione
del protocollo standard WS-Management, ideata per consentire
l’accesso e lo scambio di informazioni all’interno di un’infrastruttura
IT tramite protocollo SOAP (Simple Object Access Protocol).
Poiché SOAP si basa sul formato XML e può operare attraverso
HTTP, WinRM è in grado di scambiare dati anche attraverso quei
firewall che ammettono solo il traffico web.
NO La tecnologia WinRM è alla base dei sistemi di esecuzione dei job in background
TA e dei task remoti di PowerShell, trattati, rispettivamente, nei Capitoli 26 e 27.

BITS è un servizio di Windows che consente di trasferire file in rete


impiegando la banda non sfruttata da altri software, utilizzando i
protocolli HTTP e HTTPS. Nonostante alcuni comandi di PowerShell
consentano di interagire con questo componente, non c’è una diretta
dipendenza tra i due e BITS è utilizzato dal sistema operativo più
che altro per scaricare gli aggiornamenti del software.

Installazione
A partire da Windows 7 e Windows Server 2008 R2, Windows
Management Framework è compreso nel sistema operativo e non è
necessaria alcuna installazione da parte dell’utente; qualora
PowerShell non fosse disponibile tra i programmi installati, tuttavia,
potrebbe essere necessario abilitare manualmente questa
funzionalità, seguendo le istruzioni riportate nella sezione
“Abilitazione di PowerShell su Windows Server 2008” più avanti in
questo capitolo. Le sezioni che seguono, invece, sono dedicate
all’installazione di WMF nelle versioni meno recenti di Windows.

Requisiti minimi per l’installazione


Nei sistemi precedenti a Windows 7 e Windows Server 2008 R2,
Windows Management Framework è disponibile in due pacchetti
separati:
• WMF Core, che contiene PowerShell e WinRM;
• WMF BITS, che contiene BITS.
Il supporto di WMF varia a seconda del componente considerato.
WMF Core si può installare su queste versioni di Windows:
• Windows Server 2008 Service Pack 1;
• Windows Server 2008 Service Pack 2;
• Windows Server 2003 Service Pack 2;
• Windows Vista Service Pack 2;
• Windows Vista Service Pack 1;
• Windows XP Service Pack 3;
• Windows Embedded POSReady 2009;
• Windows Embedded for Point of Service 1.1.
WMF BITS, d’altro canto, offre una compatibilità meno estesa del
precedente ed è limitato a questi sistemi operativi:
• Windows Server 2008 Service Pack 1
• Windows Server 2008 Service Pack 2
• Windows Vista Service Pack 2
• Windows Vista Service Pack 1

Installazione del framework Microsoft .NET


Poiché il funzionamento di PowerShell dipende strettamente dalla
presenza del framework Microsoft .NET, è necessario che il sistema
su cui si desidera installare WMF disponga almeno della versione
2.0 Service Pack 1 di questo prodotto. A partire da Windows Vista e
Windows Server 2008, il sistema operativo include nativamente una
versione più aggiornata di quella menzionata, in grado di soddisfare
pienamente i requisiti di PowerShell. Si noti che alcune funzionalità
avanzate della shell (come l’interfaccia a finestre, illustrata più avanti
in questo capitolo) non sono disponibili se nel sistema non è
installata almeno la versione 3.0 del framework Microsoft .NET.
Per ulteriori informazioni relative all’installazione del framework Microsoft.NET si
NO
rimanda al sito Microsoft dedicato all’argomento, disponibile all’indirizzo
TA
http://www.microsoft.com/netframework.

Installazione di Windows Management Framework


Una volta verificato che il sistema soddisfi il requisito del framework
Microsoft .NET, è possibile procedere con l’installazione di WMF. Se
sulla macchina è già installata la versione 1.0 di PowerShell, d’altra
parte, prima di procedere è necessario rimuoverla, come indicato
nella prossima sezione.
Per installare WMF è possibile utilizzare i link contenuti nella
knowledge base Microsoft dedicata all’argomento, disponibile
all’indirizzo:
http://support.microsoft.com/kb/968929

Disinstallazione di PowerShell 1.0


Come anticipato, dovendo installare Windows Management
Framework è necessario prima rimuovere l’eventuale versione 1.0 di
PowerShell installata nel sistema operativo. Per determinare quale
versione di PowerShell sia installata è possibile verificare nel registry
il valore della chiave PowerShellVersion, presente in:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine.

Il nome della cartella di installazione di PowerShell (di default


NO %SystemRoot%\System32\WindowsPowerShell\vl.0) riporta il testo 1.0 anche per la
TA versione 2.0, quindi non si deve fare affidamento su questo dato per individuare la
versione installata.

Per disinstallare PowerShell 1.0 da Windows XP e Windows Server


2003 è necessario seguire questi passaggi:
1. aprire Installazione applicazioni dal Pannello di controllo di
Windows;
2. fare clic su Installazione componenti di Windows;
3. Rimuovere questi aggiornamenti, se disponibili:
• KB926139 – Windows PowerShell v1.0;
• KB926140 – Windows PowerShell v1.0 (localizzazione);
• KB926141 – Windows PowerShell v1.0 (interfaccia MUI).
Per disinstallare PowerShell 1.0 da Windows Vista è sufficiente
seguire questi passaggi:
1. aprire Installazione applicazioni dal Pannello di controllo di
Windows;
2. Fare clic su Visualizza aggiornamenti installati;
3. Rimuovere questo aggiornamento, se disponibile:
• KB928439 – Windows PowerShell v1.0.
Per disinstallare PowerShell 1.0 da Windows Server 2008, infine,
seguire questi passaggi:
1. aprire Server Manager dal menu Strumenti di
amministrazione;

Figura 1.1 - Come aprire il Server Manager.

2. nella sezione Riepilogo funzionalità fare clic su Rimuovi


funzionalità;
3. deselezionare la casella Windows PowerShell dall’elenco;
4. continuare con la procedura guidata fino al termine.
Figura 1.2 - Per disinstallare Windows PowerShell deselezionare la relativa casella.

Abilitazione di PowerShell su Windows Server 2008


Si noti che, come anticipato, a partire da Windows 7 e Windows
Server 2008 R2, Windows Management Framework è già incluso nel
sistema operativo e non è necessaria alcuna installazione; con le
edizioni server di Windows, tuttavia, per utilizzare PowerShell è
necessario abilitare esplicitamente la funzionalità attraverso Server
Manager, seguendo questi passaggi:
1. aprire Server Manager dal menu Strumenti di
amministrazione;
2. nella sezione Riepilogo funzionalità fare clic su Aggiungi
funzionalità;
3. selezionare la casella Windows PowerShell dall’elenco;
4. continuare con la procedura guidata fino al termine.
Figura 1.3 - Per abilitare Windows PowerShell in Windows Server 2008 fare clic su
Aggiungi funzionalità, poi selezionare Windows PowerShell dall’elenco.

Avviare PowerShell
All’interno del menu Start di Windows, l’installazione (o l’abilitazione)
di PowerShell 2.0 porta alla creazione di una nuova cartella
chiamata Windows PowerShell e di due nuovi collegamenti, sotto la
voce Programmi > Accessori.

Figura 1.4 - Il collegamento a PowerShell nel menu Start di Windows.

L’interfaccia a console
Il primo collegamento, chiamato semplicemente Windows
PowerShell, avvia una classica interfaccia a console che permette di
impartire comandi alla shell e ricevere informazioni utilizzando del
semplice testo.
Figura 1.5 - L’interfaccia a console testuale di PowerShell 2.0.

L’interfaccia è volutamente limitata (come nella maggior parte delle


shell a riga di comando), così da rendere il più possibile immediato
lo scambio di dati tra l’utente e il sistema.
Le operazioni consentite dall’interfaccia a console sono quelle
standard, esposte da Windows per le finestre di questo tipo.
Premendo e rilasciando il pulsante sinistro del mouse all’interno
della console, è possibile selezionare del testo, che può essere poi
copiato negli appunti di Windows premendo il tasto Invio. Viceversa,
cliccando con il pulsante destro del mouse all’interno della console, il
sistema incolla l’eventuale testo presente negli appunti di Windows.
Scegliendo la voce Proprietà dal menu contestuale, attivabile
facendo clic con il pulsante destro sulla barra del titolo della finestra,
si accede a una serie di opzioni che consentono, tra l’altro, di
modificare il layout e i colori utilizzati dall’interfaccia, così come il font
e le dimensioni dei caratteri.
Per avviare rapidamente l’interfaccia a console testuale di PowerShell è possibile
NO
digitare direttamente powershell nella finestra Esegui di Windows. Il relativo
TA
eseguibile, infatti, si chiama proprio powershell.exe.
L’interfaccia a finestre
Il secondo collegamento è chiamato Windows PowerShell ISE
(acronimo di Integrated Scripting Environment) e consente di
lanciare un’interfaccia grafica alternativa a quella a console: anche in
questo caso l’esecuzione di comandi e lo scambio di informazioni
avvengono utilizzando del testo, ma PowerShell ISE espone una
serie di funzionalità che rendono più agevoli queste operazioni.

Figura 1.6 - PowerShell ISE, l’interfaccia grafica di PowerShell 2.0.

PowerShell ISE permette di gestire con facilità i propri comandi


grazie a un editor di testo avanzato in linea con gli strumenti di
questo tipo, che offre un sistema di colorazione del codice in base
alla sintassi, la piena integrazione con gli appunti di Windows e la
capacità di eseguire le porzioni di codice selezionate (caratteristica
che potrebbe ricordare SQL Server Management Studio al lettore
con precedenti esperienze con questo software).
Questa interfaccia a finestre, inoltre, è dotata di un help contestuale
che permette di accedere rapidamente alla guida relativa a un
comando facendo clic sul nome e premendo il tasto F1. La guida
dedica ampio spazio anche alle numerose funzionalità esposte
dall’interfaccia stessa.
PowerShell ISE, infine, rende molto agevoli le operazioni di test e
debug del codice e contiene un’intera voce di menu dedicata a
queste attività.

Interfacce alternative
Sia l’interfaccia a console testuale sia PowerShell ISE sono solo un
mezzo per dialogare con la shell, il cui funzionamento è determinato
dal codice presente in alcune librerie di sistema installate con
Windows Management Framework. Entrambe le interfacce, infatti, in
gergo sono definite host di PowerShell, ovvero applicativi che
ospitano il “motore” di questa piattaforma.
Grazie a questa separazione tra logica di funzionamento e
interfaccia, Microsoft ha reso possibile lo sviluppo di interfacce
alternative per PowerShell; al momento in cui questo libro viene
scritto, su Internet sono disponibili ben quattro interfacce alternative
alle due fornite da Microsoft, la maggior parte delle quali è gratuita. Il
seguito di questo paragrafo le illustra brevemente.
PowerGUI è un’interfaccia grafica gratuita sviluppata da Quest
Software (http://www.quest.com), che ricalca l’aspetto di PowerShell
ISE e include un ottimo editor di comandi, dotato anch’esso di un
sistema di colorazione del codice in base alla sintassi. Tra le
caratteristiche di PowerGUI spiccano la possibilità di utilizzare con
facilità le librerie di comandi create dagli utenti della community del
prodotto e l’eccellente supporto per il test del codice, che potrebbe
ricordare quello offerto dai classici strumenti di sviluppo software.
Grazie al contributo di Stefano Del Furia, powershell.it collabora con
Quest Software affinché PowerGUI sia disponibile anche in lingua
italiana.
Figura 1.7 - Una schermata di PowerGUI.

Per ulteriori informazioni su PowerGUI si rimanda alla sezione


dedicata a questo prodotto all’interno di powershell.it:
http://www.powershell.it/PowerGUI.aspx
PowerShell Plus è un’interfaccia grafica a pagamento sviluppata da
Idera (http://www.idera.com), che presenta caratteristiche molto
simili a PowerGUI. L’editor di comandi di PowerShell Plus, tuttavia,
consente di interagire in maniera più organica con le librerie di
comandi installate con il prodotto. Gli strumenti per il testing del
codice, inoltre, sono più completi rispetto al software analizzato in
precedenza e un’intera sezione dell’interfaccia è dedicata al signing,
una pratica avanzata necessaria in alcuni scenari dove la sicurezza
del codice eseguito all’interno della shell deve essere garantita
tramite un hash.
Questo prodotto include un’ottima guida in linea che contiene anche
un tutorial per apprendere PowerShell; il sito del prodotto, inoltre,
contiene centinaia di comandi già pronti all’uso, creati dalla
community di utenti.
Nel momento in cui questo libro viene scritto, PowerShell Plus non è
disponibile in lingua italiana.
Figura 1.8 - Una schermata di PowerShell Plus.

BGShell è un prodotto gratuito e open source ospitato da CodePlex


all’indirizzo http://bgshell.codeplex.com; si tratta di un’interfaccia
minimalista per PowerShell basata su Windows Forms, che offre la
colorazione del codice basata sulla sintassi e la visualizzazione dei
messaggi di errore tramite i balloon tips di Windows. Nel momento in
cui questo libro viene scritto, BGShell sembra non essere più
attivamente sviluppato. Il prodotto non è disponibile in lingua italiana.
Figura 1.9 - Una schermata di BGShell.

PoshConsole è un’interfaccia che promette di rivoluzionare la


presentazione delle informazioni provenienti dal “motore” di
PowerShell; è anche questo un progetto gratuito e open source,
ospitato da CodePlex all’indirizzo http://poshconsole.code-plex.com.
La caratteristica unica di questo prodotto – sviluppato utilizzando
Windows Presentation Foundation – consiste nella capacità di
visualizzare gli eventuali elementi grafici ritornati dalla shell
direttamente all’interno dell’interfaccia, capovolgendo il concetto di
console testuale. Nel momento in cui questo libro viene scritto,
PoshConsole è arrivato alla versione 2.0 e presenta un’attività di
sviluppo costante. Non è disponibile in lingua italiana.
Figura 1.10 - Una schermata di PoshConsole.
Concetti di base

Un’introduzione alprompt dei comandi di PowerShell e


all’ esecuzione dei cmdlet, con alcune nozioni
fondamentali sul recupero dei comandi disponibili
e la guida in linea. Una panoramica sulla
compatibilità con gli script degli altri interpreti dei
comandi Microsoft.

Come è stato evidenziato nel capitolo precedente, il pacchetto di


installazione di Windows PowerShell 2.0 comprende, oltre alla
classica interfaccia a console testuale, anche un’interfaccia grafica a
finestre (PowerShell ISE); esistono, inoltre, diverse interfacce
sviluppate da terze parti, scaricabili da Internet.
Poiché i concetti esposti in questo libro sono per la maggior parte
indipendenti dall’interfaccia impiegata, si è scelto di presentare quasi
tutti gli esempi di utilizzo della shell tramite l’interfaccia a console
testuale, sia perché è la più classica, sia perché è più leggera della
controparte grafica, sia perché in generale soffre meno limiti di
quest’ultima, soprattutto rispetto all’interazione con altri eseguibili a
console.
La riga di comando
Windows PowerShell è una shell interattiva, che consente all’utente
di digitare dei comandi da eseguire e di visualizzare in seguito il
risultato ottenuto. Quando la shell è disponibile a ricevere comandi
informa l’utente visualizzando un breve testo, seguito da un cursore
lampeggiante: si tratta del prompt dei comandi.

Il prompt
La riga del prompt contiene, di default, alcune informazioni relative
alla sessione di lavoro corrente: una tipica riga comincia con il testo
PS – acronimo di PowerShell – seguito dalla directory di lavoro
corrente, un’indicazione molto utile quando, per esempio, si stanno
effettuando delle manutenzioni su file system e sono coinvolte più
cartelle differenti; all’avvio della shell la directory di lavoro
corrisponde alla cartella dell’utente che ha avviato l’applicazione.
Quando se ne presenta la necessità, il prompt fornisce
automaticamente informazioni aggiuntive all’operatore. Se si sta
lavorando all’interno di una sessione remota (tema trattato nel
Capitolo 27), PowerShell aggiunge in testa alla riga di prompt il
nome della macchina su cui si sta operando, per consentire
all’utente di verificare in modo rapido dove sta eseguendo le proprie
istruzioni ed evitare così potenziali problemi; in fase di debug, inoltre,
la shell aggiunge al testo del prompt la sigla [DBG]:.

Figura 2.1 - Un tipico prompt di PowerShell.

Per consentire alla shell di elaborare un comando è sufficiente


digitarne le istruzioni al prompt e farle seguire dal tasto Invio.
Digitando il testo che segue, PowerShell ritorna, per esempio, la
data e ora correnti:
PS C:\Users\ikmju> Get-Date

domenica 10 gennaio 2010 15:57:36

La shell dispone di un nutrito arsenale di comandi come questo,


progettati sia per portare a termine dei compiti in autonomia sia per
costituire le basi di funzionalità più complesse, realizzabili proprio
grazie alle interazioni tra un comando e gli altri.
Nel gergo di PowerShell questo tipo di comandi è chiamato cmdlet –
pronunciato “commandlet” – un’abbreviazione di “command applet”,
termine che identifica letteralmente un comando in grado di
effettuare un’operazione specifica.
I cmdlet sono strumenti indispensabili per chi utilizza la shell e
ricoprono, pertanto, un ruolo di rilievo nel seguito del libro.
Il prompt dispone, inoltre, di un meccanismo di completamento
automatico dei comandi immessi, in grado di agevolare la
digitazione delle istruzioni al prompt: premendo il tasto Tab , infatti,
PowerShell esamina il testo digitato nella riga corrente e propone
una lista di possibili elementi il cui nome inizia (o corrisponde) con
quanto immesso. Digitando Get-D e premendo il tasto Tab , per
esempio, la shell completa la riga con il comando Get-Date.
Laddove esista più di un risultato possibile, la shell consente di
scorrere gli elementi trovati – ordinati alfabeticamente – con
successive pressioni del tasto Ta b (oppure Maiusc+Tab per
scorrere all’indietro).
Oltre al completamento automatico dei comandi, infine, la shell
mette a disposizione dell’utente uno storico delle ultime righe di
comando processate (di default fino a un massimo di 64). Per
scorrere gli elementi è sufficiente utilizzare i tasti Freccia su e
Freccia giù, mentre premendo il tasto F7 gli elementi dello storico
sono visualizzati in una lista, così che sia più semplice recuperare la
riga desiderata.

I cmdlet
L’architettura di PowerShell garantisce che tutti i cmdlet siano
usufruibili secondo regole precise e ben documentate, a cominciare
dal nome: ogni cmdlet, infatti, ha un nome del tipo Verbo-
Sostantivo (come Remove-Item, Export-Csv, Disable-ComputerRestore ecc.),
così che sia semplice determinare lo scopo di un blocco di istruzioni
e più immediato ricordare quale comando utilizzare in una
determinata situazione.
La shell non fa distinzione tra minuscole e maiuscole, quando si
tratta di nomi di cmdlet; Get-Date è equivalente a get-date e a gEt-DaTe, ma
per questioni di stile e leggibilità è consigliabile utilizzare sempre la
prima forma.

I parametri
Ogni cmdlet espone diversi parametri, tramite i quali è possibile
variarne il funzionamento e la logica delle istruzioni. Insieme
all’organizzazione e alla nomenclatura dei cmdlet, PowerShell rende
finalmente universale e chiaramente documentato anche il sistema
di valorizzazione (in gergo parsing) e di passaggio dei parametri
verso ciascun comando, poiché queste attività non sono delegate ai
singoli tool (come avviene per altre shell) ma al motore stesso della
piattaforma.
Per specificare il valore di un parametro è sufficiente accodare al
nome del cmdlet, separato da uno spazio, un trattino (-) e il nome del
parametro di interesse, seguito nuovamente da uno spazio e dal
valore che questo deve assumere. La logica si ripete nel caso di
impostazione di più parametri, dove per separare l’indicazione di un
parametro dall’altro si usa nuovamente uno spazio.
Il cmdlet Get-process, per esempio, recupera la lista dei processi in
esecuzione in un computer (di default il computer locale) e dispone
di un parametro, -Name, tramite il quale filtrare i processi ritornati per
nome.
Eseguendo le istruzioni che seguono, PowerShell ritorna la lista dei
processi attivi nella macchina locale il cui nome è “iexplore” (ovvero
le istanze di Internet Explorer):

PS C:\Users\ikmju> Get-Process -Name iexplore


Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ----- -- -----------
497 19 17160 36148 160 1.31 2932 iexplore
416 20 9340 25576 143 1.65 3352 iexplore
[...]

Per semplificare l’uso dei comandi e snellire le istruzioni – magari


ripetitive – PowerShell consente di specificare il valore di alcuni
parametri, per i cmdlet che lo prevedono, non solo attraverso la
modalità appena vista ma anche indicandone il solo valore e
omettendone il nome: una funzionalità tipicamente impiegata per i
parametri utilizzati più di frequente.
Nonostante ogni parametro rimanga sempre e comunque utilizzabile
tramite il proprio nome, alcuni sono anche posizionali. In fase di
parsing la shell analizza tutti i parametri denominati dalla riga di
comando, poi recupera i valori che restano e desume dalla posizione
di ciascuno a quali parametri appartengano. Per entrare nella logica
di questo meccanismo è utile tornare brevemente all’esempio
precedente: dalla versione 2.0 di PowerShell, Get-Process è in
grado di recuperare informazioni anche da un computer remoto,
funzionalità attivabile specificando, tramite il parametro -
computerName, il nome della macchina di interesse (o il suo
indirizzo IP).
Per rilanciare il comando dell’esempio precedente, ma ritornare
questa volta le istanze di Internet Explorer di un PC remoto, è
possibile eseguire istruzioni simili alle seguenti:

PS C:\Users\ikmju> Get-Process -Name iexplore -ComputerName 10.0.1.9

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ----- -- -----------
875 27 37808 53660 241 8.80 2660 iexplore

A differenza di -ComputerName, però, il parametro -Name è anche


posizionale; si tratta dell’unico parametro di questo tipo all’interno del
cmdlet Get-Process e possiamo sfruttarne la natura riscrivendo le ultime
istruzioni in due modi alternativi, equivalenti a quello di partenza ma
più snelli:

PS C:\Users\ikmju> Get-Process iexplore -ComputerName 10.0.1.9


Oppure:

PS C:\Users\ikmju> Get-Process -ComputerName 10.0.1.9 iexplore

PowerShell è inoltre in grado di riconoscere anche i nomi abbreviati


dei parametri, così che sia possibile snellire ulteriormente le proprie
istruzioni, a patto che il nome parziale immesso riesca a identificare
univocamente un parametro del cmdlet di interesse; in caso
contrario, la shell genera un errore. Il parametro -ComputerName di
Get-Process, per esempio, può essere indicato utilizzando anche
solo una lettera:

PS C:\Users\ikmju> Get-Process iexplore -C 10.0.1.9

Conviene utilizzare con moderazione la funzionalità di riconoscimento dei nomi


NO
abbreviati dei parametri: anche se consente di ottenere codice più snello, il rischio
TA
è che ne aumenti anche la difficoltà di lettura e interpretazione.

Un tipo particolare di parametro, chiamato switch, abilita (o


disabilita) una particolare funzionalità del cmdlet cui è correlato; lo
switch rappresenta quindi una sorta di interruttore e può pertanto
assumere solo due stati: attivo e disattivo. Questa peculiarità ha
spinto gli sviluppatori di PowerShell a trattare gli switch in maniera
differente dagli altri parametri dei cmdlet: rispetto a questi ultimi,
infatti, la sintassi d’uso degli switch differisce nel passaggio dei
valori. Se uno switch non è presente nella riga di comando allora è
automaticamente disattivo; in caso contrario è attivo. Il cmdlet Stop-
Process, per esempio, il cui obiettivo è arrestare i processi in

esecuzione, utilizza lo switch -Confirm per mitigare i possibili danni


derivanti dall’utilizzo improprio del comando. Se Stop-Process è
richiamato specificando lo switch -Confirm allora arresta i processi solo
dopo aver richiesto conferma all’utente; in caso contrario questo
passaggio è evitato e l’arresto è immediato:

PS C:\Users\ikmju> Stop-Process -Name calc -Confirm

Conferma
Eseguire l’operazione?
Esecuzione dell’operazione "Stop-Process" sulla destinazione "calc (1644)".
[S] Sì [T] Sì a tutti [N] No [U] No a tutti [O] Sospendi [?] Guida (il
valore predefinito è "S"):

Figura 2.2 - La conferma della chiusura di un processo tramite PowerShell ISE.

Anche nei nomi dei parametri (e, di riflesso, nelle loro abbreviazioni)
PowerShell non fa distinzione tra minuscole e maiuscole.

Ottenere informazioni sui comandi


A differenza di altre shell, dove l’unico modo per conoscere i
comandi disponibili consiste nel consultare la guida in linea,
PowerShell è un ambiente completamente auto-descrittivo, dove è
possibile recuperare per la sessione corrente la lista dei comandi
utilizzabili e qualsiasi altra informazione a essi associata. Questa
funzionalità risulta probabilmente familiare al lettore che ha avuto
esperienze di sviluppo con il framework Microsoft .NET, poiché
entrambi i meccanismi si basano sul sistema della reflection del
framework, ovvero la capacità dei componenti software di questa
infrastruttura di rendere pubbliche e fruibili le informazioni sulle
proprie strutture.
Recupero dei comandi disponibili
Il cmdlet Get-Command consente di recuperare la lista dei comandi
disponibili nella sessione corrente. Come indicato nel seguito del
capitolo, oltre ai cmdlet la shell riconosce e gestisce diverse tipologie
di comando, compresi gli eseguibili, i file batch dell’interprete dei
comandi CMD e gli script di WSH (Windows Script Host): Get-Command,
quindi, restituisce di riflesso comandi che appartengono a una
qualsiasi di queste tipologie. L’esempio più semplice consiste nel
mero richiamo del cmdlet, che ritorna tutti i comandi disponibili:

PS C:\Users\ikmju> Get-Command

CommandType Name Definition


----------- ---- ----------
Alias % ForEach-Object
Alias ? Where-Object
Function A: Set-Location A:
Alias ac Add-Content
Cmdlet Add-Computer Add-Computer [-DomainName] <String> [-
Credential...
Cmdlet Add-Content Add-Content [-Path] <String[]> [-Value]
<Object[...
Cmdlet Add-History Add-History [[-InputObject] <PSObject[]>] [-
Pass...
[...]

È possibile filtrare la tipologia di comando ritornata mediante il


parametro -CommandType, al quale, passando il valore Cmdlet, per
esempio, si indica di escludere tutti i comandi che non siano cmdlet:

PS C:\Users\ikmju> Get-Command -CommandType Cmdlet

CommandType Name Definition


----------- ---- ----------
Cmdlet Add-Computer Add-Computer [-DomainName] <String> [-
Credential...
Cmdlet Add-Content Add-Content [-Path] <String[]> [-Value]
<Object[...
Cmdlet Add-History Add-History [[-InputObject] <PSObject[]>] [-
Pass...
[...]

Così come avviene per Get-Process, anche Get-Command è in grado di


filtrare gli elementi recuperati per nome: è disponibile per l’occasione
un parametro -Name, al primo posto tra i posizionali di questo cmdlet.
È possibile sfruttare le regole di nomenclatura dei cmdlet per
recuperare solo quelli che utilizzano un determinato verbo o un
determinato sostantivo all’interno del proprio nome: Get-Command
dispone, infatti, del parametro -Verb per il primo caso e del parametro
-Noun per il secondo.

È semplice, quindi, risalire a un insieme di cmdlet che si occupano di


gestire la stessa tipologia di informazioni: sapendo che il cmdlet Get-
Service, per esempio, permette di recuperare i servizi Windows di un

computer, è possibile utilizzarne il sostantivo (Service) in una ricerca


tramite Get-Command e trovare cmdlet affini:

PS C:\Users\ikmju> Get-Command -Noun Service

CommandType Name Definition


----------- ---- ----------
Cmdlet Get-Service Get-Service [[-Name] <String[]>] [-ComputerName
...
Cmdlet New-Service New-Service [-Name] <String> [-BinaryPathName]
<...
Cmdlet Restart-Service Restart-Service [-Name] lt;String[]> [-Force] [-
Pa...
[...]

Tabella 2.1 - I principali verbi dei cmdlet.


Verbo Ricorrenze (nei cmdlet di sistema)
Get 46
Set 19
New 17
Remove 14
Export 8
Write 8
Import 7
Out 7
Add 6
Start 6

Tabella 2.2 - I principali sostantivi dei cmdlet.


Sostantivo Ricorrenze (nei cmdlet di sistema)
Object 9
Item 9
ItemProperty 8
Service 8
PSSession 7
EventLog 7
PSSessionConfiguration 6
Job 6
Computer 6
Process 5

Ottenere informazioni dalla guida


I dati restituiti da Get-Command sono indispensabili ma, senza descrizioni
dei comandi né esempi di utilizzo, si rischierebbe di rendere il
processo di apprendimento della shell estremamente impegnativo.
PowerShell dispone di un’ottima guida in linea, alimentata
parzialmente in modo automatico, grazie alla reflection (lo stesso
meccanismo, quindi, su cui si basa Get-Command), e per una quota parte
dai contenuti testuali creati dai produttori di ciascun cmdlet e
installati assieme ai cmdlet stessi.
La guida è attivabile utilizzando il comando Get-Help, specificando il
cmdlet su cui si desiderano informazioni.

PS C:\Users\ikmju> Get-Help Get-Command

NOME
Get-Command

RIEPILOGO
Ottiene informazioni di base sui cmdlet e altri elementi dei comandi di Windows
PowerShell.

SINTASSI
Get-Command [[-Name] <string[]>] [-CommandType {Alias | Function |
Filter | Cmdlet | ExternalScript | Application |
Script | All}] [[-ArgumentList] <Object[]>] [-Module <string[]>] [-
Syntax] [-TotalCount <int>] [<CommonParameters>
]
[...]

DESCRIZIONE
Il cmdlet Get-Command ottiene informazioni di base relative a cmdlet e
ad altri elementi dei comandi di Windows PowerShell nella sessione, quali
alias, funzioni, filtri, script e applicazioni.
[...]

COLLEGAMENTI CORRELATI
Online version: http://go.microsoft.com/fwlink/?LinkID=113309
about_Command_Precedence
Get-Help
Get-PSDrive
[...]

COMMENTI
Per visualizzare gli esempi, digitare: "get-help Get-Command -examples".
[...]

Il risultato dell’istruzione è un documento strutturato in sezioni,


ciascuna con informazioni specifiche del cmdlet richiesto.
La sezione Nome (Name, nei sistemi in lingua inglese) riporta
semplicemente il nome del cmdlet, per esteso.
La sezione Riepilogo (Synopsis) contiene un’utile descrizione degli
obiettivi del comando.
La sezione Sintassi (Syntax) riporta le modalità di chiamata
consentite dal cmdlet e comprende uno schema dei parametri
ammessi.
La sezione Descrizione (Description) contiene il testo vero e proprio
di guida del comando, con indicazioni sul suo funzionamento e sulle
modalità di impiego.
La sezione Collegamenti correlati (Related links) consiste in un
elenco di contenuti inerenti a quello corrente, per un eventuale
approfondimento: vi si possono trovare riferimenti ad altri cmdlet e
url verso siti web (tipicamente verso TechNet, il sito di risorse
Microsoft per professionisti IT).
Infine la sezione Note (Remarks) contiene eventuali avvertimenti e
note testuali relative al cmdlet.
L’output della guida in linea comprende altre quattro sezioni, che
nella visualizzazione predefinita di Get-Help non sono visibili;
l’utilissima sezione Esempi (Examples) fa parte di questo gruppo e
contiene generalmente diversi script che mostrano come utilizzare
ciascun cmdlet. Per visualizzarla è sufficiente utilizzare lo switch

-Examples di Get-Help:

PS C:\Users\ikmju> Get-Help Get-Command -Examples


[...]

---------------------------ESEMPIO 1 -------------------------

C:\PS>get-command
Descrizione
-----------
Con questo comando si ottengono informazioni su tutti i cmdlet e le
funzioni di Windows PowerShell.

Nella visualizzazione predefinita vengono elencati il tipo di comando


("Cmdlet", "Function" o "Filter"), il nome del cmdlet o della funzione e la
sintassi o la definizione della funzione.
[...]

Utilizzando lo switch –Detailed, invece, Get-Help aggiunge alle


informazioni della visualizzazione di base sia la sezione Esempi sia
la sezione Parametri (Parameters), che contiene importanti
indicazioni sullo scopo di ciascun parametro e sul tipo di dato a cui
questo è associato.

PS C:\Users\ikmju> Get-Help Get-Command -Detailed


[...]

PARAMETRI
[...]

-CommandType <CommandTypes>
Ottiene solo i tipi di comandi specificati. Utilizzare "CommandType"
o il relativo alias, "Type". Per impostazione predefinita, Get-Command
consente di ottenere cmdlet e funzioni.

I valori validi sono:


-- Alias: tutti gli alias Windows PowerShell della sessione corrente.
-- All: tutti i tipi di comando. Equivale a "get-command *".

-- Application: tutti i file non di Windows PowerShell nei percorsi


elencati nella variabile di ambiente Path ($env:path), inclusi i file con
estensione txt, exe e dll.

-- Cmdlet: cmdlet della sessione corrente. Il valore predefinito è


"Cmdlet".
[...]

Lo switch -Detailed rappresenta un giusto compromesso tra le varie opzioni di


NO
visualizzazione offerte da Get-Help: permette di accedere alle informazioni di base
TA
e agli esempi dei cmdlet, tralasciando quanto potrebbe non essere sempre utile.

Infine, lo switch -Full aggiunge alla visualizzazione ritornata da -


Detailed diverse informazioni tecniche sul cmdlet, come la presenza di
parametri posizionali e obbligatori – aggiunti alla sezione Parametri
– piuttosto che indicazioni sul tipo di dato ritornato (o accettato,
come spiegato nel Capitolo 8) dal comando, nelle sezioni Input
(Inputs) e Output (Outputs).
PS C:\Users\ikmju> Get-Help Get-Command -Full
[...]

PARAMETRI
[...]

-CommandType <CommandTypes>
[...]

Obbligatorio? false
Posizione? named
Valore predefinito
Accettare input da pipeline? true (ByPropertyName)
Accettare caratteri jolly? False
[...]

INPUT
System.String
È possibile reindirizzare una proprietà "Name", "Command" e "Verb"
specificata o un oggetto stringa a Get-Comma
nd.
[...]

OUTPUT
Object
Il tipo di oggetto restituito dipende dal tipo di elemento del comando
recuperato. Get-Command in un cmdlet, per esempio, recupera un oggetto
CmdletInfo, mentre Get-Command in una DLL recupera un oggetto ApplicationInfo.
[...]

Tabella 2.3 - Correlazione tra gli switch di Get-Help e le sezioni


della guida in linea.
Sezione (default) -examples -detailed -full
Nome X X X X
Riepilogo X X X X
Sintassi X X X
Descrizione X X X
Parametri X X
Parametri (extra) X
Input e output X
Note X
Esempi X X X
Collegamenti correlati X X
Commenti X X

A differenza dell’interfaccia a console testuale, PowerShell ISE


contiene anche una guida in linea in formato HTML, richiamabile
premendo il tasto F1 dopo aver posizionato il cursore all’interno del
nome del cmdlet desiderato; il testo della guida è esattamente lo
stesso che si recupera attraverso lo switch -Full di Get-Help ed è
disponibile per i soli cmdlet di sistema.

Figura 2.3 - La guida in linea attivabile da PowerShell ISE.

I comandi esterni alla shell


In linea con la filosofia di sviluppo Microsoft, anche PowerShell
mantiene una forte retro-compatibilità con le applicazioni della
stessa famiglia prodotte in precedenza dalla casa madre, anche se
le tecnologie interessate sono completamente differenti. Dall’interno
della shell è possibile lanciare qualsiasi tipo di eseguibile
riconosciuto da Windows, digitandone il nome al prompt dei comandi
così come avviene per i cmdlet; l’estensione del file (.exe o .com) può
essere omessa, alla stregua dell’interprete dei comandi CMD (e, più
indietro nel tempo, di COMMAND.COM).
Digitando notepad, per esempio, la shell recupera e lancia l’eseguibile
notepad.exe, il Blocco note di Windows. Il Blocco note è
un’applicazione con un’interfaccia grafica a finestre che non può
interagire con PowerShell, pertanto la shell non ne attende la
chiusura ma torna immediatamente operativa, visualizzando un
nuovo prompt.

Figura 2.4 - L’apertura del Blocco note di Windows da PowerShell.

Gli eseguibili dotati di interfaccia a console testuale, invece,


condividono con la shell lo stesso meccanismo di rappresentazione
delle informazioni: PowerShell, in questo caso, non solo attende il
termine del relativo processo ma è anche in grado di interagire
(anche se in forma ridotta) con i canali di input e di output a
quest’ultimo collegati per elaborare, successivamente, quanto
prodotto.
L’eseguibile tasklist.exe, per esempio, è un tool a riga di comando che
visualizza la lista di processi e servizi attivi, incluso a partire da
Windows XP: l’output generato da questo eseguibile viene
visualizzato direttamente nella finestra di PowerShell e può
eventualmente essere catturato. Nonostante i tool a riga di comando
come tasklist siano stati ampiamente superati dalla potenza dei
cmdlet, PowerShell consente, grazie a questo meccanismo, di
continuare a utilizzare eseguibili a console non progettati per questa
shell e migrare così alla nuova piattaforma un po’ alla volta.
Figura 2.5 - L’esecuzione di tasklist.exe da PowerShell.

Oltre agli eseguibili, la shell consente di aprire anche i singoli file,


determinando quale applicazione utilizzare tra quelle disponibili in
base all’estensione di questi. Il meccanismo impiegato, che il lettore
con precedenti esperienze di sviluppo all’interno di sistemi Windows
potrebbe ricondurre all’API ShellExecute, è il medesimo adoperato da
Explorer all’interno di Windows.
A patto che il file c:\test.txt esista, per esempio, è possibile utilizzare
la riga di codice che segue per aprire l’applicazione associata
all’estensione .txt, di default il Blocco note di Windows:

PS C:\Users\ikmju> C:\test.txt

PowerShell, infine, supporta gli script creati per gli interpreti dei
comandi CMD e la piattaforma Windows Script Host (WSH), che
comprende cscript e wscript.
In modo automatico, infatti, è possibile lanciare dalla shell sia i file
batch gestiti dal primo sistema (con estensione .bat o .cmd) sia gli
script eseguibili dal secondo (con estensione .vbs o .js).
Nonostante l’output generato da questi strumenti sia integrato con
quello della shell, dietro le quinte la piattaforma esegue in maniera
trasparente i diversi interpreti e ne dirotta l’output (e l’input) verso la
finestra principale.
Nel caso di CMD è possibile, per esempio, creare un file batch con
nome Hello.bat, inserirvi il contenuto che segue e memorizzarlo
all’interno della cartella C:\ del sistema:

@echo Un saluto da CMD…

Successivamente è possibile eseguire il batch all’interno di


PowerShell, specificando il percorso completo del file:

PS C:\Users\ikmju> C:\hello.bat
Un saluto da CMD...

Lo stesso approccio è utilizzato anche per gli script della piattaforma


Windows Script Host, con una sola differenza: poiché questa
piattaforma può visualizzare i messaggi sia tramite console testuale
sia tramite finestre grafiche, se si desidera dirottare l’output generato
verso la console di PowerShell è necessario forzare la prima
modalità di presentazione. In realtà, ciò che dev’essere modificato è
l’interprete dei comandi utilizzato di default da Windows Script Host,
l’eseguibile da impiegare quando si lancia un file di script con una tra
le estensioni riconosciute dalla piattaforma.
La configurazione di questo aspetto è semplice e si conclude con
l’esecuzione di una singola riga di codice; per impostare come
interprete di default cscript.exe, e quindi abilitare i messaggi a
console, è sufficiente eseguire:

WScript //H:CScript

Effettuato questo passaggio, è possibile creare – a immagine e


somiglianza dell’esempio precedente – uno script con nome Hello.vbs,
inserirvi il contenuto che segue, memorizzarlo all’interno della
cartella C >:\ del sistema:

WScript.Echo "Un saluto da WSH..."

ed eseguirlo all’interno di PowerShell, anche in questo caso


specificando il percorso completo del file:
PS C:\Users\ikmju> c:\hello.vbs
Microsoft (R) Windows Script Host Versione 5.7
Copyright (C) Microsoft Corporation 1996-2001. Tutti i diritti riservati.

Un saluto da WSH...

Per tornare alla modalità predefinita di Windows, dove l’interprete di


default è wscript.exe e i messaggi sono visualizzati tramite una finestra
grafica, la riga di comando da eseguire è:

WScript //H:WScript

Il lancio di comandi esterni dalla shell è vincolato da diverse impostazioni di


NO sicurezza e governato da alcune regole che consentono la corretta convivenza tra
TA l’ambiente di sviluppo e la shell stessa: entrambi gli aspetti sono trattati
approfonditamente nel seguito del libro.
Parte II
Sintassi di base
Oggetti e tipi di oggetto

Una panoramica sui tipi di oggetto supportati dalla shell e


un’analisi delle tecniche di manipolazione delle variabili e
istanze di oggetto,
delle attraverso proprietà e
metodi.

Nelle shell e nei tool a riga di comando tradizionali, le informazioni


sono rappresentate da semplici testi e la necessità di fornire il
risultato di un comando al successivo obbliga l’utente a ricomporre
parzialmente il dato di partenza. La rappresentazione di informazioni
complesse o strutturate, tra l’altro, porta inevitabilmente alla
creazione di testi di difficile lettura: da un lato si vorrebbe soddisfare
l’utente, generando rappresentazioni testuali leggibili, dall’altro si
vorrebbe aprire le porte al più vasto scenario di automazione
possibile, includendo anche i dettagli meno significativi. La
ricostruzione del dato di partenza (come, per esempio, la
dimensione dei file dall’output del comando dir, in
CMD/COMMAND.COM o ls, nei sistemi *nix), tuttavia, può essere
notevolmente complessa e richiedere l’impiego di tool esterni in
grado di manipolare il testo (come grep, sed e awk) mentre altre
volte non è semplicemente attuabile.
Nel seguito di questo capitolo si esamina la soluzione introdotta da
PowerShell per risolvere questo problema e si gettano le basi per
l’impiego della piattaforma di scripting del sistema.

Le modalità di parsing
Per consentire la convivenza della piattaforma di scripting e
dell’ambiente di esecuzione dei comandi, quando interpreta una riga
la shell può impiegare due diverse modalità di parsing, a seconda
della necessità.
La modalità di parsing delle espressioni permette di recuperare il
risultato di un’operazione che coinvolge uno o più valori e che vede
coinvolto il motore di scripting della shell, come per esempio una
somma di numeri.
La modalità di parsing dei comandi, invece, consente di eseguire
un cmdlet o un eseguibile esterno.
L’attivazione dell’una o dell’altra modalità avviene in base ai primi
caratteri individuati nella riga, tralasciando gli eventuali spazi iniziali:
se si tratta di una keyword o di un simbolo previsto dal linguaggio di
scripting (elementi approfonditi nel seguito del libro) oppure di una
cifra allora il parsing avviene per le espressioni. In tutti gli altri casi la
shell effettua il parsing per un comando.
È possibile combinare le due diverse modalità di parsing all’interno
di una stessa riga, racchiudendo la porzione di testo di interesse
all’interno di una coppia di parentesi tonde (). Così facendo, si
obbliga la shell a rivalutare la modalità di parsing da adottare per il
codice desiderato.

In alcuni casi la shell permette una convivenza pacifica tra le due modalità di
parsing all’interno della stessa riga anche omettendo le parentesi tonde di
NO
raggruppamento descritte poc’anzi: tuttavia, per una maggiore chiarezza
TA
espositiva, in questo libro si è scelto di utilizzare in ogni occasione questa struttura
sintattica, anche laddove non fosse strettamente necessaria.

Gli oggetti
L’approccio impiegato da PowerShell per risolvere il problema della
ricostruzione dei dati scambiati da un comando all’altro è davvero
innovativo e si basa sull’assunto che ogni informazione gestita da
questa shell è sempre rappresentata da un oggetto del framework
Microsoft .NET, dove il termine oggetto è utilizzato per indicare un
pacchetto strutturato di informazioni e funzionalità correlate a
queste. Non solo, dunque, è possibile usare degli oggetti all’interno
dell’ambiente di scripting di PowerShell, ma gli stessi cmdlet
ritornano e accettano come argomenti degli oggetti! Le funzionalità e
la tipologia di informazioni supportate da ciascun oggetto sono
definite dal suo tipo (chiamato anche classe, per analogia con i
linguaggi di derivazione C), che consiste in uno schema definito
all’interno di una libreria o di un modulo eseguibile. Il sistema dei tipi
di Microsoft .NET è aperto e chiunque ne può creare e aggiungere:
gli elementi utilizzati con maggiore frequenza, tuttavia, sono quelli
compresi con il framework Microsoft .NET che, per tale ragione,
sono chiamati nativi.
Quando un oggetto è definito da una particolare classe si dice che è
un’istanza di quest’ultima, dove oggetto e istanza sono sinonimi ma
il secondo termine rivela con più precisione la natura del dato.
Nonostante la shell visualizzi a video un testo, il comando Get-Date,
per esempio, ritorna un oggetto strutturato che incorpora diverse
informazioni sulla data e l’ora correnti: diversamente da quanto
potrebbe sembrare, infatti, l’informazione comprende anche il
numero di millisecondi e l’eventuale indicazione sull’ora legale,
anche se queste informazioni non sono direttamente visualizzate.

NO Il meccanismo in base a cui la shell determina quale dato visualizzare a video per
TA ciascun tipo di oggetto è analizzato nel Capitolo 9.

Le proprietà d’istanza
Quasi tutte le informazioni contenute all’interno di un oggetto sono
tipicamente disponibili attraverso le proprietà esposte da
quest’ultimo. Ogni proprietà consiste in un ulteriore oggetto
secondario, a cui è stato attribuito un nome: per recuperare o
elaborare il valore associato a una proprietà di un oggetto è
sufficiente utilizzare il simbolo punto (.) tra l’oggetto di riferimento e il
nome della proprietà desiderata, secondo questo semplice schema:
<Oggetto>.<Proprietà>

Nello script che segue, per esempio, si recupera il valore della


proprietà DayOfYear dell’oggetto ritornato da Get-Date, che
corrisponde al giorno dell’anno (da 1 a 365-366) per la data:

PS C:\Users\ikmju> (Get-Date).DayOfYear
90

Si noti che, poiché Get-Date è un cmdlet e il recupero del valore di una


qualsiasi proprietà interagisce con la piattaforma di scripting, è
necessario utilizzare una coppia di parentesi tonde per forzare la
shell a cambiare modalità di parsing.
In maniera simile alla precedente, in quest’altro script si utilizza il
cmdlet Get-ChildItem (trattato approfonditamente nel Capitolo 17)
per recuperare la data e l’ora di ultima modifica di un file:

PS C:\Users\ikmju> (Get-Childltem C:\config.sys).LastWriteTime

sabato 19 gennaio 2008 9.38.29

Come si può notare, l’output di questo script è simile a quello del


cmdlet Get-Date. Sia l’oggetto ritornato da quest’ultimo sia l’oggetto
rappresentato dalla proprietà LastWriteTime, infatti, sono dello stesso
tipo.
NOT I tipi di oggetto sono analizzati più avanti in questo
A capitolo.

Usufruendo più volte della tecnica di recupero delle proprietà, è


possibile recuperare un’informazione nidificata all’interno di un’unica
riga; risalire al giorno dell’anno dell’ultima modifica di un file, per
esempio, presuppone che lo script precedente sia modificato così:

PS C:\Users\ikmju> (Get-ChildItem C:\config.sys).LastWriteTime.DayOfYear 32

I metodi d’istanza
I metodi d’istanza sono blocchi di funzionalità che interagiscono con
l’oggetto di cui fanno parte, talvolta modificandolo oppure
semplicemente ritornando un risultato. A ogni metodo è assegnato
un nome e, in modo simile alle proprietà, per invocare un metodo di
un oggetto è sufficiente utilizzare il simbolo punto (.) tra l’oggetto di
riferimento e il nome del metodo desiderato, cui è necessario far
seguire una coppia di parentesi tonde().
Ciascun metodo può accettare un numero di parametri prestabilito
dalla definizione del metodo stesso (fornita nella classe dell’oggetto
di appartenenza), tramite i quali è generalmente possibile variare il
funzionamento del codice: quando un metodo accetta dei parametri
è necessario specificare il valore di ognuno all’interno della coppia di
parentesi tonde di chiamata, rispettando l’ordine della definizione e
separando ogni parametro con il simbolo virgola (,).
La sintassi di chiamata dei metodi può essere schematizzata così:

<Oggetto>.<Metodo>(<Parametro1>, <Parametro2> [, <Parametro3>, ...])

Nello script che segue, per esempio, si invoca il metodo


IsDaylightSavingTime() che, per gli oggetti dello stesso tipo ritornato da
Get-Date, è in grado di indicare se l’istanza associata è relativa a un
orario legale o solare:

PS C:\Users\ikmju> (Get-Date).IsDaylightSavingTime()
True

Rimanendo in tema di date, impiegando il metodo AddDays() è


possibile, per esempio, aggiungere un determinato numero di giorni
a una data, ottenendo un nuovo oggetto di questo tipo su cui è
possibile effettuare nuove elaborazioni:

PS C:\Users\ikmju> (Get-Date) .AddDays(907)

domenica 23 settembre 2012 15.18.39

PS C:\Users\ikmju> (Get-Date).AddDays(907).AddDays(50).IsDaylightSavingTime()
False

Il metodo ToString()
Ogni oggetto del framework Microsoft.NET è tenuto a esporre alcuni
metodi di base per via di un meccanismo chiamato ereditarietà, che
permette di derivare la definizione di un tipo dall’altro e formare una
gerarchia di classi. Senza scendere nel dettaglio di questo
argomento, destinato a un pubblico di soli sviluppatori, vale la pena
di osservare che, tra i metodi menzionati, ve n’è uno chiamato
ToString() che permette di ottenere sempre una rappresentazione
testuale dell’istanza a cui è associato.
Nello script che segue, per esempio, si utilizza il metodo Tostring()
per forzare la shell a produrre una rappresentazione testuale di
alcuni oggetti:

PS C:\Users\ikmju> (Get-Date).ToString()
31/03/2010 16.57.17
PS C:\Users\ikmju> (Get-Date).DayOfYear.ToString()
90
PS C:\Users\ikmju> (Get-Date).DayOfYear.ToString().ToString()
90

La generazione delle rappresentazioni testuali degli oggetti all’interno di


NO PowerShell si avvale di un meccanismo articolato, descritto approfonditamente nel
TA Capitolo 9. Il metodo ToString() svolge comunque un compito importante rispetto
a questa attività.

Recuperare i membri di un oggetto


Per ottenere la lista delle proprietà e dei metodi supportati da un
oggetto è possibile utilizzare il cmdlet Get-Member, fornendo l’oggetto da
ispezionare al parametro -In-putObject. Così come avviene in molte
altre occasioni, PowerShell utilizza il potente sistema della reflection
del framework Microsoft .NET per recuperare questo tipo di
informazioni.
La sintassi di base per richiamare Get-Member è rappresentata da
questo schema:

Get-Member -InputObject <Oggetto>

Per ottenere informazioni sui membri dell’oggetto ritornato da Get-


Date, per esempio, è possibile eseguire questo script:

PS C:\Users\ikmju> Get-Member -InputObject (Get-Date)


TypeName: System.DateTime

Name MemberType Definition


---- ----------- ----------
Add Method System.DateTime Add(System.TimeSpan
value)
AddDays Method System.DateTime AddDays(double value)
AddHours Method System.DateTime AddHours(double value)
[...]
ToString Method string ToString(), string
ToString(string format), string ToString(System.IForma...
[...]
Day Property System.Int32 Day {get;}
DayOfWeek Property System.DayOfWeek DayOfWeek {get;}
DayOfYear Property System.Int32 DayOfYear {get;}
[...]

Nel seguito del libro sono analizzati i principali membri per le classi più utilizzate
NO
del framework Microsoft .NET. La documentazione ufficiale per ciascun tipo e
TA
ciascun membro è disponibile sul sito Microsoft MSDN.

Le variabili
All’interno di PowerShell, una variabile consiste in un riferimento
temporaneo a un’istanza di un oggetto, dotato di nome. È possibile
utilizzare variabili all’interno di una sessione della shell per
contenere valori quali numeri, testi o qualsiasi altro oggetto del
framework Microsoft .NET.
Una variabile è identificata dal proprio nome, che in base alla
sintassi deve iniziare con il simbolo del dollaro ($) e può contenere
un numero qualsiasi di lettere e cifre, in modo simile a questi
esempi:
• $x
• $test
• $fileCountl23
• $9783

Nonostante non sia una buona prassi utilizzare simboli (o soli


numeri, come nell’ultimo esempio) all’interno dei nomi delle variabili,
in tal caso è necessario racchiudere il nome della variabile tra
parentesi graffe, come illustrato da questi esempi:
• ${test con spazi}

• ${xy#z}

Quando si tratta di nomi di variabili, la shell non fa distinzione tra


maiuscole e minuscole, quindi questi nomi fanno tutti capo alla
stessa variabile:
• $test
• $TeSt
• $TEST

L’assegnazione
L’impostazione del valore di una variabile avviene mediante
l’operatore di assegnazione, individuato dal simbolo di uguale (=)
frapposto tra la variabile e l’oggetto di partenza. La sintassi per
questo tipo di operazione è schematizzata come segue:

<Variabile> = <Oggetto>

Utilizzando il codice che segue, per esempio, si assegna a una


variabile l’oggetto ritornato dal cmdlet Get-Date:

PS C:\Users\ikmju> $test = (Get-Date)

Digitando il nome della variabile al prompt, la shell si occupa di


produrne una rappresentazione testuale a video:

PS C:\Users\ikmju> $test

giovedì 1 aprile 2010 12.34.37

Il recupero delle proprietà e l’invocazione dei metodi dell’oggetto


originale possono avvenire direttamente nella variabile, che tra l’altro
offre il vantaggio di supportare il completamento automatico
tramite il tasto Tab:

PS C:\Users\ikmju> Get-Member -InputObject $test

TypeName: System.DateTime

Name MemberType Definition


---- ---------- ----------
Add Method System.DateTime
Add(System.TimeSpan value)
AddDays Method System.DateTime AddDays(double
value)
AddHours Method System.DateTime AddHours(double
value)
[...]

PS C:\Users\ikmju> $test.DayOfYear
90
PS C:\Users\ikmju> $test.IsDaylightSavingTime()
True

Diversamente da altri linguaggi di sviluppo, la piattaforma di scripting


della shell non vincola le tipologie di oggetti ammessi da una
variabile, una volta che questa è stata utilizzata. È perfettamente
lecito, pertanto, reimpostarne il valore utilizzando un oggetto
completamente differente dal precedente, come dimostrato
dall’esempio che segue:

PS C:\Users\ikmju> $test = (Get-ChildItem C:\config.sys)

PS C:\Users\ikmju> Get-Member -InputObject $test

TypeName: System.IO.FileInfo

Name MemberType Definition


---- ---------- ----------
Mode CodeProperty System.String Mode{get=Mode;}
AppendText Method System.IO.StreamWriter
AppendText()
[...]

Le variabili automatiche
Le variabili automatiche sono un particolare insieme di variabili il cui
valore è impostato da PowerShell durante la sessione di lavoro. La
variabile $PID, per esempio, contiene un numero che corrisponde
all’identificativo del processo che ospita la sessione di PowerShell,
mentre $LastExitCode contiene il codice di uscita dell’ultimo eseguibile
nativo Windows lanciato dalla shell.
Le variabili automatiche sono fondamentali per il funzionamento
della shell e le più importanti sono illustrate assieme ai concetti cui
sono legate, nel seguito del libro; la Tabella 3.1, inoltre, elenca
alcune voci di utilità generale.

Tabella 3.1 - Una selezione di variabili automatiche.


Variabile Obiettivo
$? Ritorna lo stato di esecuzione dell’ultima operazione
$Error Ritorna la sequenza degli errori generati più di recente
$Home Ritorna il percorso della directory home dell’utente che
ha eseguito il processo di PowerShell
$LastExitCode Ritorna il codice di uscita dell’ultimo eseguibile nativo
Windows lanciato dalla shell
$PID Ritorna l’identificativo del processo che ospita la
sessione
Ritorna il percorso della directory di installazione di
$PsHome
Windows PowerShell
$Pwd Ritorna il percorso della directory corrente
Ritorna il percorso della directory home dell’utente che
$Home
ha eseguito il processo di PowerShell

Recupero dei valori da console


Il cmdlet Read-Host è in grado di richiedere all’utente l’immissione
interattiva di un testo e restituirne il valore, che può essere
immagazzinato in una variabile per una successiva elaborazione. Se
si fornisce un testo al parametro -Prompt, eventualmente racchiuso tra
doppi apici nel caso contenga degli spazi, il comando lo visualizza
come messaggio di richiesta verso l’utente.
La sintassi di base di Read-Host è proposta in questo schema:

<Variabile> = (Read-Host [-Prompt <Messaggio>])

Nello script che segue, per esempio, si richiede all’utente di inserire


il proprio nome e si assegna tale valore a una variabile;
successivamente si utilizza la proprietà Length delle variabili di tipo
testuale per visualizzare a video il numero di caratteri immessi:

PS C:\Users\ikmju> $nome = (Read-Host -Prompt "Inserisci il tuo nome")


Inserisci il tuo nome: Efran
PS C:\Users\ikmju> $nome.Length
5

NO Le variabili di tipo testuale – chiamate, in gergo, stringhe – sono trattate


TA approfonditamente nel Capitolo 12.

I tipi di oggetto
Come anticipato nella prima parte di questo capitolo, ogni tipo (o
classe) di oggetto descrive le funzionalità e la tipologia di
informazioni supportate dalle istanze che gli fanno capo. Ogni tipo è
contenuto in una libreria (o in un eseguibile), gestita dal framework
Microsoft .NET che, quando è caricata all’interno della sessione di
PowerShell, consente di utilizzare i tipi che espone e di individuarli in
base al loro nome; ogni libreria (o eseguibile) di questo tipo prende il
nome di assembly. All’avvio di ogni sessione, PowerShell carica di
default i principali assembly del sistema, garantendo all’utente la
disponibilità immediata delle classi utilizzate più di frequente.
Così come avviene in molte altre piattaforme di sviluppo, per cercare
di renderne più ordinato e gestibile l’insieme, all’interno del
framework Microsoft .NET le classi disponibili sono dichiarate
all’interno di contenitori dotati di un nome identificativo, chiamati
namespace (o spazi dei nomi, in italiano). I namespace possono
essere nidificati l’uno nell’altro, per raggruppare tra loro delle classi
che condividono lo stesso obiettivo o appartengono allo stesso
ambito; il nome completo di un tipo è dato dalla combinazione della
gerarchia dei namespace cui appartiene e del nome del tipo stesso,
dove ciascun elemento è separato dal successivo per mezzo del
simbolo punto (.).
Per visualizzare il nome completo del tipo di un oggetto è possibile
utilizzare il cmdlet Get-Member, prestando attenzione alla prima riga
visualizzata a video:

PS C:\Users\ikmju> $test = (Get-ChildItem C:\config.sys)

PS C:\Users\ikmju> Get-Member -InputObject $test

TypeName: System.IO.FileInfo
[...]

L’oggetto ritornato dal cmdlet Get-ChildIditem nell’esempio precedente è


di tipo System.IO.Filelnfo, dove System e IO sono due namespace – di
cui il secondo è contenuto nel primo – e Filelnfo è il nome con cui la
classe è stata dichiarata all’interno dell’assembly che la espone.

NO Il namespace System. IO contiene i tipi utilizzati per gestire file e


TA cartelle.

L’output dell’esempio che segue, invece, è di tipo System.DateTime; in


questo caso System è il namespace e DateTime è il nome della classe:
PS C:\Users\ikmju> Get-Member -InputObject (Get-Date)

TypeName: System.DateTime
[...]

NOT Il namespace System contiene i tipi di base del


A sistema.

All’interno di PowerShell è possibile avvalersi di quasi tutte le


funzionalità esposte dal framework Microsoft .NET, incluso il
meccanismo della reflection, che permette di ispezionare i tipi offerti
dal sistema, i membri che ospitano e molti altri dettagli. Per
consentire l’impiego della reflection, ogni oggetto dispone di un
metodo chiamato GetType() in grado di ritornare un’istanza di un nuovo
oggetto di tipo System.Type, che rappresenta la classe del primo. La
classe System.Type è davvero ricca di funzionalità e permette di
accedere a qualsiasi dettaglio sulla definizione del tipo e dei suo
membri: la proprietà FullName, per esempio, ritorna il nome completo
del tipo (così come viene recuperato da Get-Member), mentre la
proprietà Assembly consente di recuperare un oggetto che rappresenta
l’assembly in cui la classe è dichiarata.
Nello script che segue, per esempio, si utilizza il metodo GetType()
per recuperare informazioni da alcuni oggetti diversi:

PS C:\Users\ikmju> $date = (Get-Date)


PS C:\Users\ikmju> $date.GetType().FullName
System.DateTime
PS C:\Users\ikmju> $date.GetType().Assembly

GAC Version Location


--- ------- --------
True v2.0.50727
C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll

PS C:\Users\ikmju> (Get-ChildItem C:\config.sys).GetType().FullName


System.IO.FileInfo

La shell dispone, infine, di un costrutto sintattico che consente di


recuperare un particolare oggetto di tipo System.Type, proprio come se
si fosse utilizzato il metodo GetType() a fronte di una sua istanza.
Racchiudendone il nome tra parentesi quadrate ( [] ), infatti, la shell
ne converte la rappresentazione nell’oggetto desiderato, come
dimostra l’esempio che segue:
PS C:\Users\ikmju> [System.DateTime].FullName
System.DateTime
PS C:\Users\ikmju> [System.String].Assembly

GAC Version Location


--- ------- --------
True v2.0.50727 C:\Windows\Microsoft.NET\Framework\v2.0.50727\
mscorlib.dll

I tipi primitivi
Nonostante il framework Microsoft .NET comprenda nativamente
alcune decine di migliaia di classi, alcune tra queste costituiscono le
basi su cui ogni altra classe è costruita. Questi elementi sono
chiamati tipi primitivi e svolgono un ruolo essenziale nella creazione
di qualsiasi script all’interno di PowerShell. Il seguito del libro dedica
ampio spazio a ciascuno mentre questa sezione ne riepiloga
brevemente gli obiettivi e le potenzialità, in base all’ambito.
Ambito logico:
• System.Boolean esprime un valore logico, vero o falso, ed è alla base del
controllo di qualsiasi flusso di esecuzione del codice.

Ambito numerico:
• System.Byte corrisponde a un valore numerico intero ad 8 bit ed è spesso
utilizzato per elaborare il contenuto di file binari;
• rappresentano numeri interi a 16 bit e sono
System.Int16 e System.uinti6
talvolta impiegati per memorizzare piccoli valori numerici interi;
• System.Int32 e System.UInt32rappresentano numeri interi a 32 bit e sono
generalmente utilizzati per memorizzare qualsiasi valore numerico intero;
• rappresentano numeri interi a 64 bit e sono
System.Int64 e System.UInt64
impiegati solo quando la capienza dei tipi precedenti non è sufficiente a
immagazzinare un valore intero molto grande;
• Valore numerico decimale a 128 bit, impiegato per
System.Decimai.
memorizzare valori decimali con la massima precisione possibile;
• System.single.Numero in virgola mobile a 32 bit, usato per memorizzare
valori decimali molto grandi o molto piccoli, con una precisione limitata;
• System.Double. Numero in virgola mobile a 64 bit, usato con gli stessi
obiettivi del precedente ma garantendo una precisione doppia rispetto a
esso.

Ambito testuale:
• System.char rappresenta un singolo carattere Unicode ed è talvolta
utilizzato per ottenere un carattere in base al rispettivo codice numerico;
System.string rappresenta un testo organizzato in base a una sequenza
ordinata di caratteri, chiamata stringa: i metodi e le proprietà di questa
classe permettono di manipolare qualsiasi testo.

Quando si digita un valore costante (come un numero o una


stringa) all’interno di un’espressione, la shell adotta un meccanismo
di decodifica interno che lo converte automaticamente nel tipo
primitivo che meglio si adatta a immagazzinarne il valore. Senza
scendere nel dettaglio di questa tecnologia, oggetto di analisi nel
seguito del libro, per poter utilizzare dei valori costanti è necessario
tenere presenti le regole presentate nel resto di questa sezione.
I valori numerici interi sono convertiti automaticamente da
PowerShell nel tipo System.Int32 oppure nel tipo System.Int64, a seconda
della grandezza del numero.

PS C:\Users\ikmju> $x = 123
PS C:\Users\ikmju> $x.GetType().FullName
System.Int32
PS C:\Users\ikmju> (123456).GetType().FullName
System.Int32
PS C:\Users\ikmju> (1234567890).GetType().FullName
System.Int32
PS C:\Users\ikmju> (12345678901234).GetType().FullName
System.Int64
PS C:\Users\ikmju> (1234567890123456789).GetType().FullName
System.Int64

I valori numerici decimali utilizzano il simbolo punto (.) per separare


la parte intera da quella decimale, a prescindere dalle impostazioni
internazionali presenti nella macchina. I valori di questo tipo sono
convertiti automaticamente dalla shell nel tipo System.Double.

PS C:\Users\ikmju> (1.23).GetType().FullName
System.Double
PS C:\Users\ikmju> (2348123213213321.23498723422).GetType().FullName
System.Double

Per forzare l’impiego della classe System.Decimal, che mantiene la


precisione del dato immesso, è sufficiente aggiungere il suffisso d al
numero desiderato:

PS C:\Users\ikmju> (1.23d).GetType().FullName
System.Decimal

NOT I tipi numerici sono trattati approfonditamente nel Capitolo


A 14.

I caratteri che formano le stringhe devono essere racchiusi tra apici;


PowerShell supporta sia gli apici singoli (') sia quelli doppi (") e in
base a questi varia la modalità con cui interpreta la stringa. La
tipologia di apice utilizzata deve essere uniforme, la stessa per
l’elemento di apertura e per quello di chiusura.

PS C:\Users\ikmju> $x = "Questa è una stringa!"


PS C:\Users\ikmju> $x.GetType().FullName
System.String
PS C:\Users\ikmju> ('Questa usa degli apici singoli').GetType().FullName
System.String

NOT Le stringhe e le diverse tipologie di apici sono trattate nel Capitolo


A 12.

Una volta che l’apice iniziale (singolo o doppio) è stato aperto,


PowerShell rimane in attesa di dati da parte dell’utente; la pressione
del tasto Invio porta la shell ad aggiungere la relativa coppia di
caratteri che denotano una nuova linea all’interno della stringa in
fase di definizione. In tal caso il cursore della shell va a capo ma
questa presenta un prompt differente, composto da due parentesi
angolari (>>). Per terminare l’attesa e ritornare al prompt consueto è
necessario digitare l’apice di chiusura (in accordo con la tipologia
dell’iniziale) e premere nuovamente il tasto Invio.

PS C:\Users\ikmju> $testo = "Nel mezzo del cammin di nostra vita


>> mi ritrovai per una selva oscura
>> ché la diritta via era smarrita."
>>
PS C:\Users\ikmju> $testo.GetType().FullName
System.String

NO La shell mostra il prompt di attesa in tutte le occasioni in cui attende l’immissione


TA di un ulteriore dato o comando da parte dell’utente. Non è infrequente nel resto del
libro osservare alcuni script di esempio che, a causa della dimensione, hanno
richiesto l’impiego di più righe di comando, causandone la comparsa.

New-Object
La shell mette a disposizione il cmdlet New-Object per creare un oggetto
partendo dal nome del tipo desiderato, da fornire al parametro
posizionale -TypeName secondo questa sintassi:

New-Object <NomeTipo>

La creazione di nuovi oggetti, quindi, non è limitata ai soli tipi primitivi


per mezzo dell’interpretazione dei valori costanti, ma può avvenire
per qualsiasi tipo disponibile. In questo script, per esempio, si crea
un nuovo oggetto di tipo System.Version, impiegato per
memorizzare le informazioni sulla versione di un componente
software, e lo si assegna a una variabile:

PS C:\Users\ikmju> $version = New-Object System.Version


PS C:\Users\ikmju> $version

Major Minor Build Revision


----- ----- ----- --------
0 0 -1 -1

PS C:\Users\ikmju> Get-Member -InputObject $version

TypeName: System.Version

Name MemberType Definition


----- ---------- ----------
[...]
Build Property System.Int32 Build {get;}
Major Property System.Int32 Major {get;}
[...]

Poiché il namespace System è quello più utilizzato nel framework


Microsoft .NET, quando ci si riferisce a una classe compresa in
questo namespace all’interno della shell è possibile utilizzarne il
nome omettendo la porzione che vi fa riferimento. Queste due righe
di codice, infatti, portano allo stesso risultato:

PS C:\Users\ikmju> (New-Object System.Int32).GetType().FullName


System.Int32
PS C:\Users\ikmju> (New-Object Int32).GetType().FullName
System.Int32
La stessa opzione è disponibile quando si utilizza l’elemento
sintattico illustrato in precedenza:

PS C:\Users\ikmju> [System.IO.FileInfo].FullName
System.IO.FileInfo
PS C:\Users\ikmju> [IO.FileInfo].FullName
System.IO.FileInfo

La creazione degli oggetti che fanno capo ad alcune classi del


framework Microsoft .NET prevede il passaggio iniziale di alcuni
valori a un metodo speciale, chiamato costruttore della classe, che
viene richiamato solo durante questo primo istante di vita delle
istanze. Nonostante i temi trattati in questo libro non richiedano
l’impiego esplicito di un costruttore, è utile sapere che, qualora si
presentasse questa necessità, è possibile fornire i relativi valori
iniziali al cmdlet New-Object mediante il parametro -ArgumentList.

NO Il sito Microsoft MSDN contiene le informazioni sui costruttori di tutte le classi del
TA framework Microsoft .NET e sui valori che questi si attendono.

Proprietà e metodi statici


Il framework Microsoft .NET prevede che, all’interno di qualsiasi
classe, sia possibile definire proprietà e metodi statici, che
consistono in funzionalità e informazioni non associate ad alcuna
istanza del tipo e che, in generale, possano essere erogate a
prescindere dall’esistenza di altri oggetti.
I membri statici di una classe possono essere recuperati grazie al
cmdlet Get-Member ma, a causa di un limite di questo comando, è
comunque necessario fornire un’istanza (qualsiasi) del tipo di
interesse; la visualizzazione dei membri statici si attiva specificando
lo switch -Static.
Fornendo a Get-Member un’istanza del tipo system. mt32, per
esempio, è possibile notare come questo esponga le due proprietà
statiche MinValue e MaxValue, corrispon-denti al valore minimo e
massimo immagazzinabile dalle istanze del tipo stesso:

PS C:\Users\ikmju> Get-Member -InputObject 123 -Static

TypeName: System.Int32
Name MemberType Definition
---- ---------- ----------
[...]
MaxValue Property static System.Int32 MaxValue {get;}
MinValue Property static System.Int32 MinValue {get;}

Allo stesso modo, analizzando i membri statici di un oggetto


System.DateTime si può notare come questo disponga del metodo
statico DaysInMonth(), che permette di recuperare il numero di giorni
di un mese in un particolare anno:

PS C:\Users\ikmju> Get-Member -InputObject (Get-Date) -Static

TypeName: System.DateTime

Name MemberType Definition


---- ---------- ----------
[...]
DaysInMonth Method static int DaysInMonth(int year, int month)
[...]

Per richiamare proprietà e metodi statici è necessario ottenere un


riferimento al tipo desiderato utilizzando la sintassi delle parentesi
quadre illustrata in precedenza, cui far seguire due volte il simbolo
dei due punti (:) e il nome della proprietà o del metodo, seguendo
per il resto la stessa sintassi prevista per i corrispettivi d’istanza. La
sintassi è schematizzabile così:

[<NomeTipo>]::<Proprietà>]
[<NomeTipo>]::<Metodo>(...)

Per ottenere il valore della proprietà statica MaxValue di


System.Int32, dunque, è sufficiente eseguire questo codice:

PS C:\Users\ikmju> [Int32]::MaxValue
2147483647

Mentre per risalire al numero di giorni del mese di febbraio dell’anno


2100 si può utilizzare questa istruzione:

PS C:\Users\ikmju> [DateTime]::DaysInMonth(2100, 2)
28

Riferimenti null
Quando una variabile non fa riferimento ad alcun oggetto si dice che
è nulla e il suo valore è pari a quello della variabile automatica $null.
Alcuni metodi degli oggetti esposti dal framework Microsoft .NET
ritornano un valore nullo nei casi in cui non vi sia alcuna
informazione da ritornare al chiamante.
Le variabili pari a $null non fanno capo ad alcun tipo e il tentativo di
richiamarne un qualsiasi metodo d’istanza (compresi quelli di base,
come astringo) genera un errore, come evidenziato dal codice che
segue:

PS C:\Users\ikmju> $test = 123


PS C:\Users\ikmju> $test.ToString()
123
PS C:\Users\ikmju> $test = $null
PS C:\Users\ikmju> $test.ToString()
Impossibile chiamare un metodo su un'espressione con valore null.
In riga:l car:15
+ $test.ToString <<<< ()
+ CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

Il cast
Il cast (o casting) è un’operazione che consente di convertire un
oggetto in un altro, in base a un tipo specifico. PowerShell amplifica
il concetto di cast presente in altri linguaggi di sviluppo e attiva, per
ogni operazione di questo tipo, un potente motore di conversione, in
grado di raggiungere il più delle volte il risultato sperato. Per
effettuare il cast di un oggetto in un altro tipo è sufficiente far
precedere al primo il nome del tipo del secondo, avendo cura di
racchiudere quest’ultimo tra parentesi quadre ([]). Lo schema della
sintassi è il seguente:

[<NomeTipo>] <OggettoOriginale>

Nello script che segue, per esempio, si assegna a una variabile un


oggetto System.Int32 frutto del cast di una stringa:

PS C:\Users\ikmju> $stringa = "123"


PS C:\Users\ikmju> $intero = [Int32]$stringa

PS C:\Users\ikmju> $stringa.GetType().FullName System.String


PS C:\Users\ikmju> $intero. GetType().-FullName System.Int32
Nei casi in cui la conversione di un tipo in un altro non è possibile, il
cast genera un errore appropriato:

PS C:\Users\ikmju> $intero = [Int32] "hello, world"


Impossibile convertire il valore "hello, world" nel tipo "System.Int32".
Errore: "Formato della stringa di input non corretto."
In riga:l car:l8
+ $intero = [Int32] <<<< "hello, world"
+ Categorylnfo : NotSpecifìed: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

I type accelerator
Tutta la semantica del linguaggio di scripting di PowerShell è nata
per consentire all’utente di impegnare la minore quantità di tempo
possibile per raggiungere i propri obiettivi. I type accelerator sono tra
le caratteristiche che facilitano questo proposito e consistono nella
possibilità di utilizzare stringhe molto brevi per far riferimento a nomi
di tipo molto più lunghi. Per impiegare il tipo
System.Text.RegularExpressions.Regex, per esempio, che nel framework
Microsoft .NET rappresenta un’espressione regolare, è possibile
utilizzare il type accelerator regex, rendendo il codice più leggibile e
sintetico:

PS C:\Users\ikmju> $expr = [regex] "."


PS C:\Users\ikmju> $expr.GetType().FullName
System.Text.RegularExpressions.Regex

NO Le espressioni regolari sono analizzate approfonditamente nel Capitolo


TA 13.

Tabella 3.2 - I type accelerator disponibili in PowerShell 2.0.


Type accelerator Tipo sotteso
adsi System.DirectoryServices.DirectoryEntry
adsisearcher System.DirectoryServices.DirectorySearcher
array System.Array
bool System.Boolean
byte System.Byte
char System.Char
decimal System.Decimal
double System.Double
float System.Single
hashtable System.Collections.Hashtable
int System.Int32
ipaddress System.Net.IPAddress
long System.Int64
powershell System.Management.Automation.PowerShell
pscustomobject System.Management.Automation.PSObject
psmoduleinfo System.Management.Automation.PSModulelnfo
psobject System.Management.Automation.PSObject
psprimitivedictionary System.Management.Automation.PSPrimitiveDictionary
ref System.Management.Automation.PSReference
regex System.Text.RegularExpressions.Regex
runspace System.Management.Automation.Runspaces.Runspace
runspacefactory System.Management.Automation.Runspaces.RunspaceFactory
scriptblock System.Management.Automation.ScriptBlock
single System.Single
string System.String
switch System.Management.Automation.SwitchParameter
type System.Type
wmi System.Management.ManagementObject
wmiclass System.Management.ManagementClass
wmisearcher System.Management.ManagementObjectSearcher
xml System.Xml.XmlDocument

L’Extended Type System


L’Extended Type System (ETS) è una tecnologia che permette alla
shell di integrare gli oggetti che elabora con proprietà e metodi che
non sono definiti dalla classe di provenienza degli stessi, utilizzando
alcuni file in formato xml che descrivono le aggiunte da effettuare per
ciascun tipo.
I file di descrizione delle integrazioni hanno l’estensione ps1xml e sono
presenti nella cartella dell’eseguibile di PowerShell, recuperabile
mediante la variabile automatica $PsHome.. Gli oggetti System.IO.FileInfo,,
per esempio, all’interno di PowerShell contengono la proprietà
BaseName, che consente di recuperare il nome di un file
tralasciandone l’estensione. Questa proprietà non è contemplata tra
quelle della classe ma è definita all’interno del file types.pslxml.

Una trattazione completa dell’Extended Type System di PowerShell è al di fuori


NO
degli obiettivi di questo libro; il Capitolo 9, tuttavia, ne analizza alcuni aspetti
TA
importanti.

Gli operatori
Gli operatori binari sono particolari elementi sintattici del linguaggio
di scripting della shell che consentono di effettuare un’operazione tra
due oggetti per ritornarne, come risultato, un terzo. A questi si
contrappongono gli operatori unari, così chiamati perché, a
differenza dei primi, operano a fronte di un unico oggetto.
Ogni operatore è individuato da un simbolo: nel caso degli operatori
binari questo va interposto tra gli oggetti su cui è chiamato ad agire,
che assumono il nome di operando di sinistra e operando di
destra. Per gli operatori unari, invece, il simbolo va fatto seguire
(con alcune eccezioni) all’unico operando.
La sintassi degli operatori binari è schematizzabile come segue:

<Operando di sinistra> <Operatore> <Operando di destra>

Per gli unari, invece, vale questo schema:

<Operando><Operatore>

L’operazione compiuta da un particolare operatore è determinata


dalla natura degli operandi; nonostante il concetto di operatore
nasca da un ambito prettamente numerico, infatti, all’interno del
framework Microsoft.NET questi elementi sintattici possono essere
applicati a qualsiasi tipo, a patto che quest’ultimo sia progettato per
supportarli.
All’interno di PowerShell esistono diverse categorie di operatori,
utilizzabili in ambiti diversi del linguaggio di scripting. Tra questi, gli
operatori aritmetici sono quelli più semplici e utilizzati più di
frequente; nonostante sia dedicato ampio spazio ai diversi tipi di
operatore nel seguito del libro, questa sezione presenta una lista
degli operatori della categoria menzionata, in modo tale che il lettore
possa prendervi confidenza. Gli operatori aritmetici binari riconosciuti
da PowerShell sono:
Operatore Descrizione
+ effettua in genere un’addizione o una concatenazione degli
operandi
- sottrae, in genere, l’operando di destra da quello di sinistra
* restituisce, in genere, il prodotto tra i due operandi
/ divide, in genere, l’operando di sinistra per quello di destra
e restituisce il quoziente
% effettua, in genere, la stessa operazione dell’elemento
precedente ma ritorna il resto

Gli operatori aritmetici unari supportati dalla shell sono:


Operatore Descrizione
++ incrementa, in genere, il valore dell’operando
-- decrementa, in genere, il valore dell’operando

A titolo di esempio, nello script che segue si impiega l’operatore + tra


due interi per ottenerne un terzo, corrispondente alla somma dei
primi:

PS C:\Users\ikmju> $test = 3 + 2
PS C:\Users\ikmju> $test
5

In quest’altro script si verifica il corretto funzionamento dell’operatore


% quando è utilizzato tra un intero e un numero decimale:

PS C:\Users\ikmju> 97.8 % 3
1.8

In quest’esempio, poi, è possibile notare come, nonostante le


stringhe supportino come ci si aspetterebbe l’operatore + (di fatto,
restituendo la concatenazione tra i due operandi), in realtà non
supportano l’operatore - e in tale circostanza viene generato un
errore:

PS C:\Users\ikmju> "power" + "shell"


powershell
PS C:\Users\ikmju> "power" - "shell"
Chiamata al metodo non riuscita. [System.String] non contiene un metodo
denominato 'op_Subtraction'.
In riga:l car:lC
+ "power" - <<<< "shell"
+ Categorylnfo : Invalidoperation
(op_Subtraction:String) [],
RuntimeException
+ FullyQualifìedErrorId : MethodllotFound

Nello script che segue, inoltre, si può notare come l’operatore ++


produca il risultato atteso quando è applicato a un operando intero:

PS C:\Users\ikmju> $x = 9
PS C:\Users\ikmju> $x++
PS C:\Users\ikmju> $x
10

Mentre lo stesso fallisce se è applicato a un operando di tipo stringa:

PS C:\Users\ikmju> $x = "test"
PS C:\Users\ikmju> $x++
L'operatore '++' può essere utilizzato solo su numeri- L'operando è un
oggetto 'System.String'
In riga:1 car:5
+ $x++ <<<<
+ Categorylnfo : Invalidoperation: (test:String) [], RuntimeException
Lavorare con gli script

Una guida dettagliata allo sviluppo e all’esecuzione


script esterni, con alcune importanti nozioni sugli
degli
scope delle variabili e i contesti di esecuzione
dei comandi.

Non appena PowerShell perde il ruolo di strumento accessorio e


acquista quello di piattaforma preferenziale di sviluppo e di gestione
di Windows, si viene spesso a creare la necessità di raggruppare e
isolare i diversi blocchi di istruzioni, così da aumentarne la possibilità
di riutilizzo e favorire un’organizzazione ottimale. PowerShell facilita
il raggiungimento di questo obiettivo consentendo l’esecuzione di
script esterni, eseguibili in modo simile a quanto è stato indicato per i
cmdlet.

Gli script esterni


Uno script esterno di PowerShell è semplicemente un file di testo
che, se richiamato dalla shell, porta all’esecuzione delle istruzioni
che contiene. Per poter essere eseguiti, gli script esterni devono
avere l’estensione .PSI, sigla che include l’acronimo del nome del
prodotto: nell’intenzione degli sviluppatori della shell, il numero
successivo sarebbe dovuto aumentare con il progredire della
piattaforma e avrebbe dovuto indicare la versione minima della shell
in grado di eseguire lo script di interesse. A partire dalla versione 2.0,
però, Microsoft ha fatto un passo indietro su questa scelta, favorendo
un metodo alternativo – peraltro sempre esistito in PowerShell – per
indicare i requisiti minimi degli script, metodo analizzato nel seguito
del capitolo. L’estensione . psI, quindi, rimane quella ufficiale degli
script esterni di PowerShell, a prescindere dalla versione del
prodotto richiesta o impiegata.

I commenti
Poiché gli script esterni contengono tipicamente svariate righe di
istruzioni differenti, risulta utile inserire dei commenti testuali
all’interno dei file di codice. Questa pratica, largamente utilizzata
dagli sviluppatori di software di tutto il mondo, consente di
documentare i diversi passaggi delle operazioni effettuate all’interno
degli script e rende molto più semplice la manutenzione del codice.
Per inserire una riga di commento all’interno del codice è sufficiente
anteporre al testo desiderato il carattere cancelletto (#): la shell
ignora questo simbolo e tutto ciò che lo segue.
Tutte le considerazioni fatte per gli script fino a qui valgono anche
per il prompt, dove, di conseguenza, digitando una riga di solo
commento non viene prodotto alcun output:

PS C:\Users\ikmju> # Questo è un commento


PS C:\Users\ikmju>

Mentre il codice che segue lancia correttamente il cmdlet Get-Process:

PS C:\Users\ikmju> Get-Process # Commento

A partire dalla versione 2.0, inoltre, PowerShell rende possibile


l’inserimento di commenti che occupano più di una riga: è sufficiente
racchiudere il blocco di testo tra <# e #>.

PS C:\Users\ikmju> <# Questo è un commento


>> che occupa più di una riga
#> >>
PS C:\Users\ikmju>
Anche in questo caso la shell ignora completamente il commento
ma, a differenza del commento su riga singola che ignora qualsiasi
testo fino al ritorno a capo, questa modalità mantiene effettiva
l’eventuale porzione di riga non interessata dal commento. Nel
codice che segue, infatti, il cmdlet Get-Process viene eseguito
correttamente:

PS C:\Users\ikmju> <# Commento #> Get-Process <# Commento #>

I criteri di esecuzione
A differenza dei cmdlet, i cui obiettivi sono ben definiti, isolati e
documentati, gli script esterni potrebbero potenzialmente contenere
istruzioni dannose per il sistema, a insaputa dell’utilizzatore: per tale
ragione, nel pieno rispetto della Microsoft Trustworthy Initiative - il
documento di visione e strategia sulla sicurezza informatica
approvato nel 2002 dall’allora CTO di Microsoft, Craig Mundie -
PowerShell implementa un meccanismo automatico di verifica e
blocco degli script esterni, chiamato criterio di esecuzione
(execution policy). Questa funzionalità non pone vincoli sui comandi
effettivamente eseguibili, però consente comunque di stabilire alcune
semplici regole di esecuzione e di evitare che gli utenti le violino
inconsapevolmente. Il criterio di esecuzione può assumere sei
possibili differenti livelli, dove Restricted è il valore predefinito e non
consente l’esecuzione di alcuno script esterno. I due livelli
immediatamente successivi impediscono alla shell di eseguire script
a meno che non dispongano di firma digitale: mentre AllSigned
verifica questa norma per qualsiasi script esterno, RemoteSigned
limita l’analisi ai soli file scaricati da Internet. Il livello Unrestricted
non richiede alcuna firma digitale ma comporta la visualizzazione di
un avviso non appena si cerchi di eseguire script scaricati da
Internet. Il livello Bypass, infine, non impone alcuna restrizione né
comporta la visualizzazione di alcun messaggio di avviso. Benché
compaia nella lista dei possibili valori dei criteri di esecuzione, il
valore Undefined, invece, indica l’assenza di un’impostazione.
Esistono cinque differenti ambiti (scope) in cui è possibile applicare i
criteri appena esposti e la shell determina quale livello utilizzare in
base all’ordine di precedenza degli ambiti:
• i prime due ambiti, Configurazione computer e Configurazione
utente, sono impostabili tramite i criteri di gruppo di Windows;
• l’ambito successivo, Process, governa l’esecuzione del
processo corrente: lanciando l’eseguibile di PowerShell è
possibile indicare il criterio di esecuzione per l’ambito Process
mediante il parametro -ExecutionPolicy;
• l’ambito CurrentUser, poi, afferisce all’utente che ha eseguito il
processo della shell;
• LocaiMacnine regola l’esecuzione all’interno della macchina
corrente.
Per recuperare il criterio di esecuzione attivo all’interno della shell è
possibile eseguire il cmdlet Get-ExecutionPolicy:

PS C:\Users\ikmju> Get-ExecutionPolicy
Restricted

Come anticipato, il risultato è determinato verificando quale livello sia


impostato per ciascun ambito: seguendo l’ordine di precedenza, il
primo ambito che non abbia l’impostazione Undefined determina il
criterio di esecuzione attivo. L’unica eccezione prevede però che, nel
caso in cui per tutti gli ambiti sia impostato il criterio Undefined, il
risultato finale sia Unrestricted.
Utilizzando lo switch -List, Get-ExecutionPolicy ritorna infine i criteri di
esecuzione impostati per ciascun ambito contemplato dalla shell:

PS C:\Users\ikmju> Get-ExecutionPolicy -List

Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine RemoteSigned

Il cmdlet Set-ExecutionPolicy, infine, consente di reimpostare il criterio di


esecuzione per gli ambiti che non siano governati dai criteri di
gruppo di Windows: il parametro -ExecutionPolicy (posizionale, al primo
posto) indica il livello desiderato, mentre -Scope (posizionale, al
secondo posto) specifica l’ambito di interesse. Dei tre ambiti gestibili
con questo cmdlet, Process ha una validità limitata alla sessione
corrente e, una volta chiusa la shell, il valore eventualmente
impostato non è più valido; per CurrentUser e LocalMachine, invece, il
sistema memorizza l’impostazione nel registry e la ricarica a ogni
successiva riapertura di PowerShell. Ecco, per esempio, come
modificare il criterio di esecuzione dell’utente corrente, in maniera
tale che il livello sia Unrestricted:

PS C:\Users\ikmju> Set-ExecutionPolicy Unrestricted CurrentUser

Modifica ai criteri di esecuzione


I criteri di esecuzione facilitano la protezione dagli script non attendibili.
La modifica dei criteri di esecuzione potrebbe esporre l'utente ai rischi di
sicurezza descritti nell'argomento della Guida about_Execution_Policies.
Modificare i criteri di esecuzione?
[S] Sì [N] No [O] Sospendi [?] Guida (il valore predefinito è "S"):

Per rendere nulla l’impostazione del criterio di esecuzione per un


particolare ambito è sufficiente, infine, eseguire il cmdlet Set-
ExecutionPolicy specificando il criterio Undefined.

Il sistema User Access Control, disponibile a partire da Windows Vista, potrebbe


NO impedire l’impiego del cmdlet Set-ExecutionPolicy generando un errore all’interno
TA della shell. In questo caso è sufficiente eseguire la shell con i privilegi di
amministrazione.

Anche se in un ambiente di produzione è consigliabile impiegare un


criterio di esecuzione più restrittivo, studiando PowerShell e gli
esempi del seguito del libro è conveniente impostare un livello che
consenta l’esecuzione di script non firmati digitalmente, come per
esempio Unrestricted.

Hello, world
Per il lettore con precedenti esperienze legate al mondo dello
sviluppo software, il titolo di questa sezione rappresenta sicuramente
l’emblema del codice sorgente: per anni si è tentato di dimostrare la
semplicità d’uso dei linguaggi di programmazione utilizzando un
piccolo saggio delle loro funzionalità, mediante un blocco di istruzioni
che visualizza a video proprio la scritta “hello, world”.
Data la natura e gli obiettivi del sistema, PowerShell permette di
visualizzare facilmente del testo nell’interfaccia dell’host e dispone di
alcuni metodi differenti per produrre tale risultato: il resto del capitolo
ne adopera uno tra quelli messi a disposizione dalla shell, nel
tentativo di ricreare la tradizionale atmosfera di “hello, world”,
respirata dagli sviluppatori di tutto il mondo, anche in PowerShell.
Il codice che segue utilizza il cmdlet Write-Host per visualizzare la
famosa scritta a video; si tratta di un cmdlet tra i più semplici, che
genera una rappresentazione testuale del valore fornito tramite il
parametro –Object (unico posizionale). Nel caso delle stringhe,
ovviamente, viene utilizzato direttamente il valore fornito.

PS C:\Users\ikmju> Write-Host 'hello, world'


hello, world

È possibile, quindi, inserire quest’unica riga di codice all’interno di un


file di script, chiamato per esempio HelloWorld.ps1, ed eseguirlo: la shell
prevede diverse modalità per effettuare il lancio di file di questo tipo,
a seconda della directory che contiene il file desiderato. Per creare e
modificare i file di script è possibile utilizzare l’interfaccia di
PowerShell ISE oppure qualsiasi editor di testo semplice, come il
Blocco note di Windows, salvando i documenti con l’estensione .PSI.
Se lo script si trova nel path allora è sufficiente digitarne il nome,
anche omettendo l’estensione; per la shell le directory contenute nel
path godono, infatti, di un’importanza particolare e sono considerate
più sicure rispetto alle altre. Ecco allora che, nel caso appena
esposto, i due comandi che seguono sono equivalenti:

PS C:\Users\ikmju> HelloWorld.ps1
hello, world

PS C:\Users\ikmju> HelloWorld
hello, world

Nel caso lo script sia memorizzato in una directory non compresa nel
path, invece, la shell richiede che il nome del file sia accompagnato
dal suo percorso.

PS C:\Users\ikmju> C:\Users\ikmju\HelloWorld.ps1
hello, world
La regola vale anche per gli script che risiedono nella directory
corrente: PowerShell in questo caso richiede che sia specificato il
percorso relativo .\:

PS C:\Users\ikmju> .\HelloWorld.ps1
hello, world

In tutti i casi esposti, quando il nome dello script (o il suo percorso,


se indicato) contiene degli spazi è necessario racchiuderlo tra apici:
in questa circostanza, però, la shell riconoscerebbe l’istruzione
immessa come una semplice stringa, ritornata di default
nell’interfaccia dell’host.
Se lo script degli esempi precedenti avesse un nome del tipo Nome con
spazi.ps1, pertanto, eseguire il codice che segue non porterebbe al
risultato atteso:

PS C:\Users\ikmju> 'Nome con spazi.ps1'


Nome con spazi.ps1

Quando si presenta questa situazione è necessario utilizzare


l’operatore di chiamata &, una funzionalità della shell in grado di
eseguire uno script (o un eseguibile) fornendo la stringa che ne
rappresenta il nome e, opzionalmente, il percorso. L’esempio
precedente, quindi, si può correggere anteponendo la keyword
dell’operatore alla stringa digitata:

PS C:\Users\ikmju> & 'Nome con spazi.ps1'


hello, world

Lo scope
PowerShell, di default, mantiene isolate le variabili definite negli
script esterni, in maniera tale che il chiamante non possa accedervi
né modificarne i valori. Questa pratica, chiamata in gergo scope, è
molto utilizzata nei linguaggi di sviluppo moderni perché consente di
incapsulare funzionalità indipendenti le une dalle altre e di facilitare,
quindi, il riutilizzo del codice.
La logica che governa gli scope è semplice: se una variabile (o un
altro tra gli elementi esposti nel seguito del libro) è definita a livello
globale (global scope) – ovvero al di fuori di qualsiasi script – allora è
visibile ovunque, script compresi, ma modificabile solo da
un’istruzione eseguita a livello globale.
Se invece la variabile è definita a livello di script (script scope) allora
è visibile e modificabile solo da un’istruzione eseguita a livello dello
stesso script e risulta invisibile nel resto del codice.
Quando, poi, all’interno di un primo script si esegue un secondo
script, le variabili definite nel primo script sono visibili a entrambi (non
a livello globale) ma risultano modificabili solo dal codice del primo,
mentre le variabili definite nel secondo sono visibili e modificabili solo
da quest’ultimo ma invisibili altrove.
Lo stesso ragionamento si applica nel caso in cui è il secondo script
a richiamarne un terzo e così via: in sostanza, le variabili definite
all’interno di un particolare scope sono visibili all’interno dello scope
stesso e degli scope aperti da quest’ultimo eseguendo altri script, ma
sono modificabili solo all’interno del primo.
Nell’esempio che segue sono definiti due script indipendenti,
chiamati Primo.PSI e Secondo.PSI, creati e richiamati con lo scopo di
rendere più chiari i concetti appena esposti. Il contenuto di primo.PSI è
il seguente:

# Contenuto di Primo-PSI

$a = 'Primo'
$b = 'Primo'

'$a -> ' + $a


'$b -> ' + $b
'$c -> ' + $c

'Esecuzione di Secondo.PSI...'

.\Secondo.PSI
'Fine esecuzione di Secondo.PSI...'

'$a -> ' + $a


'$b -> ' + $b
'$c -> ' + $c

Il contenuto di Secondo.PSI, invece, è più breve e corrisponde a:

# Contenuto di Secondo.PSI

$b = 'Secondo'
$c = 'Secondo'
'$a -> ' + $a
'$b -> ' + $b
'$c -> ' + $c

Alla riga di comando, infine, viene eseguito questo blocco:

$a = 'Globale'

'$a -> ' + $a


'$b -> ' + $b
'$c -> ' + $c

'Esecuzione di Primo.PSI...'
.\Primo.PSI
'Fine esecuzione di Primo.PSI...'

'$a -> ' + $a


'$b -> ' + $b
'$c -> ' + $c

Si tratta, in buona sostanza, della verifica del valore di tre variabili:


$a, $b e $c. La riga di comando richiama il primo script che, di seguito,

richiama il secondo: nello scope globale è definita solo la variabile $a,


nello scope script di primo livello sono definite $a e $b, mentre in
quello di secondo livello è definita anche $c.
Il risultato (depurato dalle righe del prompt, per renderlo più leggibile)
è questo:

$a -> Globale
$b ->
$c ->

Esecuzione di Primo.PSI...
$a -> Primo
$b -> Primo
$c ->

Esecuzione di Secondo.PSI...
$a -> Primo
$b -> Secondo
$c -> Secondo
Fine esecuzione di Secondo.PSI...

$a -> Primo
$b -> Primo
$c ->
Fine esecuzione di Primo.PSI...

$a -> Globale
$b ->
$c ->
Come si può notare, dunque, all’uscita di ogni scope le variabili sono
ripristinate al valore assunto prima dell’entrata, di fatto isolandole.
PowerShell consente, però, di aggirare le regole di isolamento e
protezione dello scope. Può essere utile, per esempio, definire una
variabile in uno script esterno e renderla disponibile,
successivamente all’esecuzione, nello scope del chiamante, così
come può essere necessario nascondere una variabile definita in
uno scope agli scope figlio, per meglio proteggere, per esempio, la
logica di un particolare script. La shell risponde a queste esigenze
permettendo di indicare lo scope al quale ci si riferisce quando si
recupera o si imposta il valore di una determinata variabile: è
sufficiente inserire il nome dello scope, seguito dal simbolo dei due
punti (:), tra il consueto carattere dollaro ($) e il nome della variabile.
Tutti i cmdlet illustrati nel capitolo precedente e che operano sulle
variabili, inoltre, consentono di indicare lo scope desiderato
specificandone il nome tramite il parametro -Scope.
I nomi ammessi per indicare gli scope sono Global, Script, Local e
Private.

Global scope e script scope


Il global scope (Global) e lo script scope (Script) corrispondono alle
due modalità analizzate in precedenza.
Se all’interno di uno script esterno si utilizza la variabile $global:test,
per esempio, si comunica alla shell l’intenzione di lavorare con la
variabile $test definita all’interno del global scope.
Dunque, creando uno script chiamato GlobalScopeTest.PSI con
questo contenuto:

$global:test = 'Global'
$script:test = 'Script'
$test = 'Nessuna indicazione'

ed eseguendo, all’avvio della shell, questo blocco:

PS C:\Users\ikmju> $test = 'Valore originale'


PS C:\Users\ikmju> .\GlobalScopeTest.ps1
PS C:\Users\ikmju> $test
la shell ritorna la scritta Global, perché lo script esterno ha impostato
a tale valore la variabile $test residente nel global scope.
Variando leggermente lo script esterno dell’esempio precedente è
possibile ritornare e visualizzare a video il valore assunto da $test
all’interno dello script stesso:

$global:test = 'Global'
$script:test = 'Script'
$test
$test = 'Nessuna indicazione'
$test

In questo caso PowerShell visualizza dapprima la scritta Script, poi il


testo Nessuna indicazione: come si può notare, quindi, lo script scope è lo
scope attivo in fase di esecuzione degli script esterni.

Il local scope
Il local scope (Local) rappresenta in ogni momento lo scope corrente
e corrisponde sempre, quindi, a uno dei due valori precedenti (o la
shell sta eseguendo uno script esterno oppure no). Il local scope è lo
scope predefinito quando si recupera o si imposta il valore di una
variabile.
Le tre righe che seguono, quindi, producono il medesimo risultato se
eseguite all’interno del global scope:

$global:test = 'Test'
$local:test = 'Test'
$test = 'Test'

Il private scope
Il private scope (Private), infine, corrisponde allo scope corrente ma,
a differenza di tale valore, le variabili definite con questa opzione
sono visibili e modificabili solo nello scope di definizione ma non
negli scope figlio, come invece avviene normalmente: la normale
propagazione della visibilità di una variabile negli scope figlio, quindi,
è interrotta.
Tramite questo scope, per esempio, è possibile isolare le variabili
definite all’interno di uno script in maniera tale che siano manipolabili
e visibili unicamente tramite il codice dello script stesso.
Creando, quindi, uno script PrivateScopeTest.PSI con questo contenuto:

$private:test = 'Primo'
.\PrivateScopeTest2.ps1

e un secondo script, PrivateScopeTest2.PSI, con quest’altro:

$test

e lanciando il primo dalla riga di comando, la shell non mostra a


video alcun testo, poiché la variabile utilizzata nel primo script è
visibile e modificabile unicamente dal codice di quest’ultimo.
Le variabili definite all’interno del private scope sono sempre
accessibili anche tramite il local scope o specificando l’effettivo
scope di appartenenza (global o script scope). Ciò che rende una
variabile privata oppure visibile agli scope figlio è la prima
impostazione: se si utilizza il private scope la variabile è privata,
anche nel caso in cui vi si acceda successivamente utilizzando uno
scope differente da private. In caso contrario, la variabile è sempre
visibile agli scope figlio, anche se successivamente vi si accede
mediante il private scope.
Pertanto, modificando leggermente il corpo dello script definito in
precedenza con questo codice:

$test = 'Valore originale'


$private:test = 'Primo'

.\PrivateScopeTest2.ps1

ed eseguendolo al prompt, la shell si comporta in maniera


completamente differente, trattando la variabile $test come pubblica,
ignorando quindi il private scope indicato nella seconda riga e
visualizzando a video la scritta Primo.

Il dot sourcing
PowerShell consente, inoltre, di lanciare qualsiasi script esterno
evitando l’apertura di un nuovo scope figlio, eseguendone il codice
all’interno dello scope corrente. Questa funzionalità è nota come dot
sourcing – termine che richiama la sintassi impiegata – e consente,
quindi, di evitare completamente il meccanismo d’isolamento e
protezione delle variabili. Per lanciare uno script in questa modalità è
sufficiente impiegare la stessa sintassi dell’operatore di chiamata &,
utilizzando il simbolo del punto (.) al posto del simbolo &.
È possibile, per esempio, definire uno script chiamato
DotSourcingTest.PSI come segue:

$private:x = 'uno'
$script:y = 'due'
$z = 'tre'

ed eseguire questo blocco di istruzioni alla shell (si noti il punto che
precede l’indicazione dello script):

. .\DotSourcingTest.PSI
$x
$y
$z

La shell risponde, contrariamente a quanto farebbe utilizzando il


classico operatore di chiamata &, visualizzando a video il valore di
tutte e tre le variabili.
Anche se il dot sourcing potrebbe sembrare una funzionalità che va
contro corrente rispetto alle pratiche di incapsulamento del codice,
questa tecnica rende possibili scenari di utilizzo della shell che
altrimenti sarebbero riservati solo a chi ha avuto precedenti
esperienze di sviluppo.

I parametri
Isolare il codice utilizzato più di frequente negli script esterni porta
senz’altro a un’organizzazione eccellente del proprio lavoro e a un
notevole risparmio di tempo. Talvolta, però, sorge la necessità di
variare solo leggermente le operazioni effettuate da uno script,
cambiando, per esempio, il valore fornito a un cmdlet.
Per venire incontro a questa necessità, PowerShell consente, alla
stregua di quanto è possibile fare con i cmdlet, di specificare dei
parametri anche per gli script.

L’istruzione param
Per definire i parametri di uno script esterno è sufficiente utilizzare
l’istruzione param e indicare, all’interno di una coppia di parentesi
tonde, la lista dei nomi dei parametri accettati, separati dal carattere
virgola:

param ($parametrol/ $parametro2, ...)

Per essere valida, l’istruzione param deve sempre essere inclusa


nella prima riga di comando di uno script, solo dopo eventuali righe
di commento.
I parametri degli script sono specificabili in fase di esecuzione così
come avviene per i cmdlet, con la differenza che, di default, tutti i
parametri sono sia nominali sia posizionali. Gli script dotati di
parametri sono inoltre riconosciuti automaticamente dalla shell, che
ne facilita l’utilizzo tramite il complemento automatico.
Diventa quindi possibile creare uno script chiamato paramTest.PSI,
dotato di questo codice:

# ParamTest .PSI - Ritorna il processo con l'identificativo fornite

param ($processID)

Write-Host 'Elaborazione in corso...


Get-Process -Id $processId

ed eseguirlo al prompt in questo modo:

PS C:\Users\ikmju> .\ParamTest.PSI -processld 0


Elaborazione in corso...

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
0 0 0 24 0 0 Idle

oppure, in maniera del tutto equivalente, in quest’altro modo:

PS C:\Users\ikmju> .\ParamTest.PSI o
[...]

Richiamando lo script con un valore non compatibile con quello


atteso, però, lo script è lanciato ma ne viene completata solo una
parte:

PS C:\Users\ikmju> .\ParamTest.PSI -processld a


Elaborazione in corso...
Get-Process : Cannot bind parameter 'Id'. Cannot convert value "a" to type
"System.Int32". Error: "Input string was not in a correct format."
[...]

È possibile evitare queste situazioni e, così come avviene per la


definizione delle variabili, definire il tipo di dato richiesto anche per i
parametri, chiamati, di conseguenza parametri fortemente tipizzati.

I parametri fortemente tipizzati


I parametri fortemente tipizzati si distinguono da quelli che non lo
sono semplicemente per la presenza del nome del tipo, tra parentesi
quadre, indicato prima di ogni parametro, come per le classiche
variabili:

param ([tipol]$parametrol, [tipo2]$parametro2, ...)

Lo script dell’esempio precedente, pertanto, può essere rivisto in


maniera tale da specificarne il tipo di parametro, un intero a 32 bit
(Int32):

# ParamTest. PSI - Ritorna il processo con l'identificativo fornite

param ([Int32]$processId)

Write-Host 'Elaborazione in corso...


Get-Process -Id $processId

Così come avviene per i cmdlet, l’impiego di valori non compatibili


con quanto atteso dai parametri degli script ne blocca
automaticamente l’esecuzione, ancora prima che questa sia ceduta
al codice richiamato:

PS C:\Users\ikmju> .\ParamTest.PSI -processId a


C:\Users\ikmju\ParamTest.PSI : Cannot process argument transformation on
parameter 'processld'. Cannot convert value "a" to type "System.Int32".
Error: "Input string was not in a correct format."

Informazioni sullo script corrente


PowerShell permette di accedere ad alcune utilissime informazioni
sullo script (o sul blocco di codice o sulla funzione, argomento
trattato nel Capitolo 16) correntemente in esecuzione.
Tra le variabili automatiche (approfondite nel Capitolo 3), infatti, la
shell mette a disposizione $Myinvocation, un oggetto dotato di
alcune interessanti proprietà utilizzabili all’interno del proprio codice.

Tabella 4.1 - Alcune proprietà di $MyInvocation.


Proprietà Descrizione
MyCommand Contiene un riferimento allo script o alla funzione in
esecuzione
ScriptLineNumber Restituisce il numero di riga dello script corrente
OffsetInLine Restituisce il numero di colonna per l’istruzione
corrente
ScriptName Contiene il nome dello script corrente
Line Contiene la stringa della riga corrente

La proprietà Mycommand, per esempio, consente di risalire al nome


del file di script in esecuzione, per cui, eseguendo lo script
MylnvocationTest.PSI, definito in questo modo:

# Ritorna le informazioni sul comando in esecuzione


$MyInvocation -MyCommand

la shell ne ritorna il nome completo:

PS C:\Users\ikmju> .\MyInvocationTest.PSI

CommandType Name Definition


----------- ---- ----------
ExternalScript MyInvocationTest.PSI C:\Users\ikmju\MyInvocationTest.PSI

Tramite la proprietà invocationName, inoltre, è addirittura possibile


recuperare la modalità di chiamata dello script e sapere se questo è
stato lanciato mediante l’operatore di chiamata &, il dot sourcing
oppure utilizzandone direttamente il nome.

L’indicazione dei prerequisiti


La shell consente di indicare con precisione, attraverso l’istruzione
#requires, quali sono i prerequisiti per l’esecuzione di uno script. È

possibile specificare, infatti, sia la versione minima di PowerShell


richiesta sia eventuali snap-in da cui dipende il funzionamento del
codice.
La sintassi di #requires prevede che questo comando possa essere
inserito più volte in uno stesso script, a patto che le righe in cui viene
utilizzato non contengano anche altri comandi.
Il parametro -Version è utilizzato per indicare la versione minima di
PowerShell in grado di eseguire lo script; nonostante il sistema
ammetta la presenza di più righe #requires con questo parametro,
viene presa in considerazione solo la prima. Il cmdlet Get-Help, per
esempio, a partire dalla versione 2.0 di PowerShell, introduce lo
switch -Online, che se applicato alla richiesta della guida in linea per
un determinato cmdlet ne apre la relativa pagina web (utilizzando il
browser di default del sistema) sul sito Microsoft TechNet.
Creando uno script, chiamato RequiresTest.PSI, che utilizza questa
funzionalità, quindi, è possibile indicare il requisito della versione 2.0
della shell in questo modo:

#requires -version 2.0

param ($commandName)
Get-Help $commandName -Online

Tentando di eseguire lo script con la versione 1.0 della shell viene


visualizzato un messaggio di errore:

PS C:\Users\ikmju> .\RequiresTest.PSI -commandllame Get-Command


Impossibile eseguire lo script 'RequiresTest.PSI' perché contiene
un'istruzione "((requires" alla riga 1 per Windows Pow
erShell versione 2.0. La versione richiesta dallo script non corrisponde
alla versione 1.0 di Windows Powershell attualmente in esecuzione.
In riga:l car:19
+ .\RequiresTest.PSI <<<< -commandllame Get-Command
+ Categorylnfo : ResourceUnavailable: (RequiresTest.PSI:String)
[], ScriptRequiresException
+ FullyQualifiedErrorld : ScriptRequiresUnmatchedPSVersion
Figura 4.1 - L’output dello script, che apre automaticamente l’help online del comando.

Utilizzando il parametro -PsSnapIn, infine, #requires verifica la presenza


dello snap-in specificato: se lo script in esame dipende da
PowerShell Community Extensions, per esempio, è possibile
indicarne la dipendenza in questo modo:

#requires -PsSnapIn Pscx


Gli alias

Una discussione sul sistema degli alias di PowerShell,


sulla gestione
loro e sulla creazione di alias
persistenti.

L’ingegnosa architettura, la completa integrazione con il framework


Microsoft .NET e la notevole quantità di comandi disponibili
potrebbero rendere difficile l’adozione di PowerShell da parte di un
utente di una shell ordinaria, magari proveniente dal mondo *nix
piuttosto che dagli interpreti dei comandi sviluppati in precedenza da
Microsoft.
Gli sviluppatori di PowerShell, per tale ragione, hanno scelto di
dotare il sistema di un meccanismo chiamato alias, in grado di
agevolare gli utenti provenienti da altri prodotti, abituati a utilizzare
comandi con nomi completamente differenti. Un alias è
sostanzialmente un nome alternativo per indicare un comando
esistente.
Il cmdlet Get-ChildItem, per esempio, utilizzato per recuperare la lista
dei file all’interno di una cartella, ha obiettivi simili al comando dir,
presente nelle precedenti shell fornite da Microsoft, e al comando ls,
presente invece nelle shell *nix. Un utente in possesso di
un’esperienza pregressa con uno di questi due comandi troverebbe
pertanto più semplice avvicinarsi a PowerShell se potesse
continuare a utilizzarne il nome.
La buona notizia è che sia dir sia is sono alias di Get-ChildItem
all’interno di Power-Shell e, pertanto, si possono impiegare al prompt
(o negli script) al posto del nome ufficiale del cmdlet:

PS C:\Users\ikmju> dir

Directory: Microsoft-PowerShe11 - Core\FileSystern::C:\Users\ikmju

Mode LastWriteTime Length Name


---- ------------- ------ ----
d-r-- 10/21/2009 7:11 AM <DIR> Contacts
d-r-- 1/21/2010 4:20 PM <DIR> Desktop
d-r-- 1/20/2010 9:05 PM <DIR> Documents
d-r-- 1/22/2010 2:57 PM <DIR> Downloads
[...]

Fortunatamente, poi, quando si richiede la guida in linea per un alias


viene ritornata la guida per il cmdlet corrispondente:

PS C:\Users\ikmju> Get-Help ls

NOME
Get-ChildItem

RIEPILOGO
Ottiene gli elementi e gli elementi figlio in una o più posizioni
specificate.
[...]

La shell dispone di molti alias standard, creati da Microsoft, ma può


facilmente ospitare anche alias creati dall’utente, validi per la
sessione in cui vengono definiti e sottoposti alle stesse regole di
isolamento e protezione previste per le variabili, in materia di scope.

Gestire gli alias


PowerShell mette a disposizione il cmdlet Get-Alias per recuperare la
lista degli alias utilizzabili: si tratta di un cmdlet molto semplice, in
grado di eseguire solo ricerche tra le corrispondenze esistenti tra
alias e comandi.
Richiamando Get-Alias senza specificare alcun parametro vengono
ritornati tutti gli alias attivi nella sessione corrente:
PS C:\Users\ikmju> Get-Alias

CommandType Name Definition


----------- ---- ----------
Alias % ForEach-Object
Alias ? Where-Object
Alias ?: Invoke-Ternary
Alias ?? Invoke-NullCoalescing
Alias ac Add-Content
Alias asnp Add-PSSnapIn
Alias cat Get-Content
Alias chdir Set-Location
Alias clc Clear-Content
Alias clear Clear-Host
[...]

Al lettore più accorto non è probabilmente sfuggito che gli oggetti


ritornati da Get-Alias appartengono tutti al tipo
System.Management.Automation.AliasInfo, uno dei cinque tipi ritornati da Get-

Command; la chiamata a Get-Alias dell’esempio precedente, infatti, è


equivalente a questa riga:

PS C:\Users\ikmju> Get-Command -CommandType Alias

Usufruendo del parametro -Name (unico posizionale) è possibile filtrare


per nome gli alias ritornati da Get-Alias, anche tramite wildcard. Ecco,
per esempio, come recuperare gli alias il cui nome termina con ps:

PS C:\Users\ikmju> Get-Alias *ps

CommandType Name Definition


----------- ---- ----------
Alias gps Get-Process
Alias ps Get-Process
Alias saps Start-Process
Alias spps Stop-Process

Con il parametro -Definition, invece, si porta a termine il processo


inverso, ovvero si recuperano gli alias filtrandoli in base al nome del
comando a cui puntano. Eseguendo lo script che segue, per
esempio, si recuperano tutti gli alias del cmdlet Remove-Item:

PS C:\Users\ikmju> Get-Alias -Definition Remove-Item

CommandType Name Definition


----------- ---- ----------
Alias del Remove-Item
Alias erase Remove-Item
Alias rd Remove-Item
Alias ri Remove-Item
Alias rm Remove-Item
Alias rmdir Remove-Item

Come anticipato, tra gli elementi elencati da Get-Alias molti hanno una
diretta corrispondenza con il relativo comando delle shell classiche
più conosciute, come CMD e COMMAND.COM sotto Windows e Bash, Ksh nei
sistemi *nix: le Tabelle 6.1 e 6.2 ne elencano alcuni tra gli alias più
importanti per i due sistemi citati.

Tabella 5.1 - I principali alias di derivazione Windows.


Cmdlet Alias
Write-Host echo
Clear-Host cls
Get-Help help
Get-ChildItem dir
Set-Location cd, chdir
Get-Location cd
Copy-Item copy
Rename-Item ren, rename
Move-Item move
Remove-Item del, rd, erase, rmdir
Get-Content type
Push-Location pushd
Pop-Location popd
Select-String find, findstr
Get-Process tlist, tasklist
Stop-Process tkill, taskkill

Tabella 5.2 - I principali alias di derivazione *nix.


Cmdlet Alias
Write-Host echo
Clear-Host clear
Get-Help man
Get-ChildItem ls
Set-Location cd
Get-Location pwd
Copy-Item cp
Rename-Item mv
Move-Item mv
Remove-Item rm, rmdir
Get-Content cat
Push-Location pushd
Pop-Location popd
Select-String grep
Get-Process ps
Stop-Process kill
Tee-Object tee
Set-FileTime touch

Oltre che per rendere più facile l’apprendimento di PowerShell,


infine, gli alias sono stati concepiti anche per consentire una
riduzione della dimensione delle righe di comando: chi utilizza
quotidianamente la shell, d’altra parte, può trovare molto
vantaggioso il risparmio di tempo dovuto alla minor quantità di tasti
da digitare. Per questa ragione la shell è dotata anche di alias
proprietari, facilmente riconducibili ai comandi correlati per via della
forte assonanza rispetto ai nomi di questi ultimi. Tra i cmdlet che si
occupano di gestire i servizi Windows, per esempio, il cmdlet Get-
Service è dotato dell’alias gsv, Start-Service è eseguibile utilizzando sasv
mentre Stop-Service si può lanciare tramite spsv.
Lo stesso Get-ChildItem, utilizzato in precedenza, dispone di un altro
alias, proprietario, in assonanza con il nome del comando: gci.

NO La gestione dei servizi Windows è trattata approfonditamente nel Capitolo


TA 21.

Creare nuovi alias


La creazione di nuovi alias all’interno della sessione corrente è
delegata al cmdlet New-Alias che, tramite i parametri -Name e -
Value, consente di indicare, rispettivamente, il nome e il comando di
riferimento del nuovo alias. Eseguendo il codice che segue, per
esempio, si crea un nuovo alias wh per il cmdlet Write-Host:
PS C:\Users\ikmju> New-Alias wh Write-Host

L’alias appare di conseguenza nell’output ritornato da Get-Alias e


qualsiasi esecuzione successiva può dunque avvalersene:

PS C:\Users\ikmju> wh hello, world


hello world

Nel caso esista già un alias con il nome indicato tramite -Name, il
cmdlet genera un errore:

PS C:\Users\ikmju> New-Alias wh Write-Output


New-Alias : Alias non consentito. Alias denominato 'wh' già esistente.

In tal caso è possibile forzare la nuova creazione usufruendo dello


switch -Force, di fatto sovrascrivendo il valore originale. Piuttosto che
forzare una creazione, tuttavia, è preferibile modificare l’alias
utilizzando le tecniche esposte nel paragrafo successivo. Anche se
la grande maggioranza degli alias predefiniti è correlata a cmdlet, è
consentito legare il valore del parametro -Value anche a eseguibili
Windows, script esterni, funzioni create dall’utente (argomento
approfondito nel Capitolo 16) oppure a un file da aprire mediante
l’applicazione predefinita nel sistema.
Eseguendo il codice che segue, quindi, è possibile creare un nuovo
alias chiamato editor, collegato al Blocco note di Windows:

PS C:\Users\ikmju> New-Alias editor notepad

Il nuovo comando editor, quindi, può essere utilizzato per aprire (o


creare) file di testo:

PS C:\Users\ikmju> editor Test.txt

L’unico alias predefinito legato a un eseguibile Windows è ise, che


richiama l’interfaccia ISE di PowerShell:

PS C:\Users\Administrator> Get-Alias ise

CommandType Name Definition


----------- ---- ----------
Alias ise powershell_ise.exe
Modificare gli alias esistenti
Set-Alias è probabilmente il cmdlet più versatile tra quelli messi a
disposizione dalla shell per manipolare gli alias: esso consente,
infatti, sia di modificare un alias esistente reimpostandone qualsiasi
proprietà (a esclusione del nome), sia di creare nuovi alias,
rendendo di fatto superfluo l’uso del cmdlet illustrato nel paragrafo
precedente. Gli sviluppatori di PowerShell hanno probabilmente
creato New-Alias con l’intenzione di rendere il codice più leggibile,
anche se, effettivamente, si sarebbe potuto raggiungere lo stesso
obiettivo impiegando un alias.
La principale differenza tra New-Alias e Set-Alias è che il primo cmdlet,
come è stato illustrato, genera un errore quando si tenta di
sovrascrivere un alias esistente (a meno che non si utilizzi il
parametro -Force), mentre il secondo no. I parametri dei due cmdlet
sono identici.
Con l’obiettivo di modificare l’alias editor, creato nel paragrafo
precedente, e associarvi WordPad piuttosto che il Blocco note di
Windows, è quindi possibile eseguire questa riga di codice:

PS C:\Users\Ikmju> Set-Alias editor 'C:\Program Files\Windows NT\


Accessories\wordpad.exe

A differenza dell’eseguibile notepad.exe, che si trova nel path, per wordpad.exe ?


NO
51 è stato in questo caso necessario specificare il percorso completo del file, così
TA
come avviene quando si lancia un eseguibile dalla riga di comando.

Sia New-Alias sia Set-Alias consentono di specificare lo scope sul quale


si intende operare, tramite il parametro -Scope; il concetto di scope,
applicato alle variabili e approfondito nel Capitolo 4, si applica in
maniera identica anche agli alias.
Il codice che segue, per esempio, se inserito all’interno di uno script
collega l’eseguibile di MSBuild (il sistema di build automatizzate, amato
dagli sviluppatori Microsoft) all’alias buiid, ma rende il nuovo
comando valido solo nell’ambito dello script (e degli scope figli):

Set-Alias build C:\Windows\Microsoft.NET\Framework\v3.5\MSBuild.exe -Scope:Script


Rimuovere gli alias
Gli alias, così come avviene per le variabili, sono rimossi
automaticamente dalla shell al termine della vita dello scope a essi
associato (oppure al termine della sessione).
La versione corrente di PowerShell non è dotata di un cmdlet
specifico per eliminare anticipatamente gli alias; per ottenere questo
risultato, infatti, è necessario sfruttare il cmdlet Remove-Item, concepito
per la rimozione generica di elementi e approfondito nel Capitolo 17.
Tramite il parametro -path, posizionale, è possibile specificare il nome
dell’oggetto da eliminare: data la natura di Remove-Item, tuttavia, non è
possibile indicare il nome dell’alias così com’è ma è necessario
specificare la natura dell’oggetto facendo precedere il nome dal
prefisso Alias:.
La rimozione dell’alias editor, definito in precedenza, è dunque
conseguibile con questo comando:

PS C:\Users\ikmju> Remove-Item Alias:editor

Purtroppo Remove-Item non consente in questo caso di specificare lo


scope desiderato, ma elimina sempre l’alias appartenente allo scope
più vicino partendo da quello corrente. Una futura versione della
shell, probabilmente, rimedierà a questa lieve incoerenza nella
gestione degli alias.

Alias persistenti
Come anticipato, al termine della sessione gli alias definiti dall’utente
sono completamente rimossi dal sistema; anche se gli alias
predefiniti sembrano godere di un trattamento differente, che pare
mantenerli in vita tra una sessione e l’altra, in realtà questi ultimi
sono caricati automaticamente all’avvio di PowerShell, alla stregua
di un inserimento manuale da parte di un operatore.
Per favorire un facile riutilizzo anche dei cmdlet definiti dall’utente, la
shell mette a disposizione due cmdlet in grado di esportare e
importare gli alias utilizzabili nella sessione corrente.
Il cmdlet Export-Alias consente di esportare tutti o una parte degli alias
attivi all’interno di un file. Si tratta di default di un file in formato CSV
(Comma-Separated Value), ovvero un testo i cui valori sono separati
da virgola. Tuttavia, specificando tramite il parametro -As il valore
Script, è possibile generare uno script PowerShell che contiene una
riga di chiamata a Set-Alias per ogni elemento esportato.
Eseguendo questa riga di codice, tutti gli alias della sessione
corrente sono esportati in formato CSV nel file Alias.csv:

PS C:\Users\ikmju> Export-Alias Alias.csv

I file CSV così generati sono caricabili in sessioni successive,


utilizzando il cmdlet Import-Alias e specificando il nome del file da
importare:

PS C:\Users\ikmju> Import-Alias Alias.csv


Un po’ di logica

operatori logici supportati da


Un’attenta analisi degli
PowerShell per confrontare le espressioni e verificare
un insieme di condizioni.

Anche se i concetti esposti nei capitoli precedenti consentono già di


creare script di notevole interesse, quando le esigenze crescono è
necessario rispondere con una tecnologia evoluta, in grado di
contenere i tempi e i costi di sviluppo. Gli script studiati nei capitoli
precedenti non sono infatti sufficientemente articolati per gestire
problematiche complesse, dove è prevista una variazione del flusso
di esecuzione del codice.
L’obiettivo di questo capitolo è illustrare i concetti fondamentali che
governano la logica degli script e gettare le basi per comprendere e
gestire il flusso di esecuzione all’interno di PowerShell.

La logica booleana
Così come avviene per la maggior parte dei linguaggi di sviluppo,
anche quello di scripting della shell è in grado di variare il flusso
delle operazioni da compiere in base a una determinata condizione;
in questo modo gli script possono adattare il proprio funzionamento
ed eseguire interi blocchi di comandi differenti, secondo le necessità.
Questo meccanismo, che assomiglia molto a quello di un interruttore
a due stati, è governato dalla logica booleana, una branca della
matematica nata nel secolo scorso che ha molte applicazioni
pratiche sia in informatica sia in elettronica.
Secondo questa logica, ogni espressione può essere considerata
vera oppure falsa; questi due stati, chiamati valori logici o valori
booleani, sono rappresentati nel framework Microsoft .NET tramite il
tipo System.Boolean e sono disponibili nella shell anche come
variabili automatiche: $true indica il vero mentre $false il falso. Il
recupero del tipo della variabile $true, infatti, ne conferma l’identità:

PS C:\Users\ikmju> $true.GetType().FullName System.Boolean

Entrambe le variabili automatiche, quindi, sono istanze del tipo


System.Boolean che, se richiamate dalla shell, vengono
rappresentate da una stringa che ne indica il valore:

PS C:\Users\ikmju> $true
True
PS C:\Users\ikmju> $false
False

Per valutare se una determinata condizione è soddisfatta


PowerShell ne considera, dunque, l’espressione associata: se il
valore dell’espressione è vero allora la condizione è soddisfatta, in
caso contrario no. Creare una condizione per l’esecuzione di un
blocco, quindi, consiste in prima battuta nel definire un’espressione
che, quando valutata dalla shell, ritorna il valore $true.

NO La più semplice tra le espressioni di questo tipo è naturalmente la variabile $true


TA stessa, che soddisfa sempre, per definizione, la condizione cui è legata.

La shell è sempre in grado di convertire un’espressione in un valore


booleano: nei blocchi condizionali porta a termine quest’operazione
in autonomia ma, utilizzando l’operatore di cast come valore
booleano, come evidenziato nel Capitolo 3, è possibile raggiungere
lo stesso obiettivo manualmente.
Ecco, per esempio, come è possibile convertire il risultato del cmdlet
Get-Process in un valore logico:
PS C:\Users\ikmju> [bool](ps explorer)
True

NOT ps è un alias del cmdlet Get-


A Process

Il framework stesso dispone di molti metodi che ritornano valori di


verità, come EndsWith() della classe System.String, argomento
approfondito nel Capitolo 12. Questo metodo ritorna $true quando la
stringa in cui viene richiamato termina con il valore fornito come
parametro:

PS C:\Users\ikmju> 'Efran'.EndsWith('an')
True

Confrontare le espressioni
PowerShell mette a disposizione dell’utente alcuni operatori in grado
di confrontare due distinte espressioni e generare un valore di verità,
in virtù della natura del confronto effettuato.
Tutti gli operatori di questo tipo – chiamati operatori di confronto -
sono preceduti da un trattino (-) e vanno interposti tra le due
espressioni da confrontare, che assumono il nome di operando di
sinistra e operando di destra.
La sintassi è schematizzabile come segue:

<Operando di sinistra> –<Operatore> <Operando di destra>

Gli operatori –eq e –ne


L’operatore più semplice è –eq (acronimo di equal to) e verifica che i
due operandi siano uguali: in tal caso restituisce $true, altrimenti
$false.

L’espressione che segue, per esempio, produce il valore $true se


viene lanciata nell’anno 2010:

PS C:\Users\ikmju> (Get-Date).Year -eq 2010


True

NOT Il cmdlet Get-Date è trattato in modo approfondito nel Capitolo


A 15.
Il confronto delle espressioni tramite -eq è case insensitive e non fa
quindi differenza tra maiuscole e minuscole. Pertanto, le due
espressioni che seguono ritornano entrambe $true:

PS C:\Users\ikmju> 'Efran' -eq Efran'


True
PS C:\Users\ikmju> 'Efran' -eq 'eFrAn'
True

Quando gli operandi sono di tipo diverso, l’operatore -eq tenta di


convertire quello di destra nel tipo di sinistra; se l’operazione va a
buon fine il confronto avviene normalmente, mentre quando la
conversione non è consentita i due operandi sono considerati
disuguali.
Questa conversione automatica dei tipi degli operandi porta a effetti
non sempre facilmente prevedibili: nello script che segue, per
esempio, la posizione dei due operandi è determinante per l’esito del
confronto e nei due casi produce risultati differenti!

PS C:\Users\ikmju> 1 -eq '01


True
PS C:\Users\ikmju> '01' -eq 1
False

Quando uno dei due operandi è $null, infine, l’esito del confronto è
negativo, mentre quando lo sono entrambi l’esito è positivo:

PS C:\Users\ikmju> $null -eq 123


False
PS C:\Users\ikmju> 123 -eq $null
False
PS C:\Users\ikmju> $null -eq $null
True

L’opposto di -eq è rappresentato dall’operatore -ne (acronimo di not


equal to), che ritorna $true in tutti i casi in cui il primo operatore
ritorna $false e viceversa. L’espressione che segue, per esempio,
ritorna sempre $false:

(1 + 2) -ne 3

e, poiché -ne è l’opposto di -eq, è possibile riscrivere l’espressione


precedente utilizzando una doppia uguaglianza:
((1 + 2) -eq 3) -eq $false

Anche in questo caso il risultato sarà sempre $false.

Il valore dell’espressione 1+2, infatti, è sempre uguale al valore dell’espressione 3,


NO
quindi si arriva a confrontare $true con $false, che non è mai vero e quindi
TA
restituisce sempre $false.

I due operatori di questa sezione sono completamente speculari,


quindi nel caso in cui entrambi gli operandi siano entrambi nulli
l’operatore -ne restituisce $false:

PS C:\Users\ikmju> $null -ne $null


False

Gli operatori -lt e -gt


Quando l’operando di sinistra è minore di quello di destra l’operatore
-lt (acronimo di less than) ritorna il valore $true, mentre ritorna $false

nel caso sia uguale o maggiore. Nel caso di espressioni numeriche o


di date, il comportamento dell’operatore -lt è facilmente prevedibile;
lo script che segue, per esempio, ritorna $true:

3 -lt 5

Quest’altro script, invece, ritorna $true se il giorno in cui lo si esegue


è antecedente al 19 gennaio 2038, appena in tempo per sfuggire allo
Unix Millennium Bug:

(Get-Date) -lt (Get-Date -Year 2038 -Month 1 -Day 19)

Quando si confrontano delle stringhe, invece, il sistema determina


quale tra le due espressioni è precedente all’altra in un’ipotetica lista
ordinata alfabeticamente; anche in questo caso la shell opera in
modalità case insensitive.
Tutte le espressioni che seguono ritornato $true:

PS C:\Users\ikmju> 'como' -lt 'domodossola'


True
PS C:\Users\ikmju> 'COMO' -lt 'dOmOdOsSoLa'
True
PS C:\Users\ikmju> 'Catania' -lt 'Como'
True

Alcuni casi, d’altra parte, sono meno ovvi dei precedenti e


potrebbero trarre in inganno il lettore alle prime armi con PowerShell;
confrontando le due espressioni numeriche che seguono, infatti, la
shell si comporta come ci si aspetterebbe:

PS C:\Users\ikmju> 123 -lt 2


False

Utilizzandone la rappresentazione in stringa, però, si ottiene il


risultato opposto:

PS C:\Users\ikmju> '123' -lt '2'


True

Quando gli operandi sono di tipo diverso, inoltre, la shell tenta,


anche in questo caso, di convertire l’operando di destra nel tipo
dell’operando di sinistra; se l’operazione è possibile il confronto
procede senza intoppi, mentre se l’operazione non è consentita
viene generato un errore simile a questo:

PS C:\Users\Ikmju> 123 -lt 'abc'


Argomento dell'operatore '-lt' errato: Impossibile confrontare "123" con
"abc". Errore: "Impossibile convertire il valore "abc" nel tipo "System.
Int32". Errore: "Formato della stringa di input non corretto."".
In riga:l car :S
+ 123 -It <<<< 'abc'
+ Categorylnfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorld : BadOperatorArgument

Se uno dei due operandi è pari a $null, infine, l’operatore -lt recupera
il valore di default del tipo dell’operando non nullo e lo utilizza per il
confronto; quando invece entrambi gli operandi sono nulli il confronto
non va mai a buon fine:

PS C:\Users\ikmju> 123 -lt $null


False
PS C:\Users\ikmju> $null -It (Get-Date)
True
PS C:\Users\ikmju> $null -lt $null
False

L’operatore -gt (acronimo di greater than) si comporta in maniera


esattamente opposta a -it e ritorna il valore $true solo quando
l’operando di sinistra è maggiore di quello di destra.
L’espressione che segue ritorna $true se nel sistema sono attivi più di
50 processi, $false altrimenti:

(Get-Process).Count -gt 50

Poiché si tratta di due operatori gemelli, tutte le considerazioni valide


per l’operatore -lt lo sono anche per l’operatore -gt. Tuttavia, quando
si confrontano due espressioni nulle anche per -gt l’esito
dell’operazione è sempre $false:

PS C:\Users\ikmju> $null -gt $null


False

Gli operatori -le e -ge


Questi operatori costituiscono una sorta di variante rispetto a -lt e -
gt. L’operatore -le (acronimo di less than or equal to) ha il medesimo

obiettivo di -lt, ma ritorna $true non solo nei casi previsti da


quest’ultimo, ma anche quando un operando è uguale all’altro.
Nell’esempio che segue, quindi, solo l’ultima espressione ritorna
$false:

PS C:\Users\ikmju> 123 -le 456


True
PS C:\Users\ikmju> 123 -le 123
True
PS C:\Users\ikmju> 123 -le 5
False

Di riflesso, l’operatore -ge (acronimo di greater than or equal to)


ritorna $true quando l’operatore di sinistra è maggiore o uguale a
quello di destra:

PS C:\Users\ikmju> 456 -ge 123


True
PS C:\Users\ikmju> 456 -ge 456
True
PS C:\Users\ikmju> 5 -ge 456
False

Le considerazioni fatte in precedenza per gli operandi nulli utilizzati


con -lt e -gt si applicano anche per -le e -ge, con la differenza che, a
dispetto del valore di default utilizzato nel caso di un unico operando
nullo, l’uguaglianza con tale valore non è mai verificata.
Confrontando, per esempio, il valore di default per il tipo System.Int32
(ovvero o) con $nuii, gli operatori -le e -ge non restituiscono mai $true,
come invece ci si potrebbe aspettare:

PS C:\Users\ikmju> 0 -le $null


False
PS C:\Users\ikmju> $null -ge C
False

Gli operatori -like e -notlike


L’operatore -like permette di confrontare due espressioni utilizzando i
wildcard, noti anche come caratteri jolly; il confronto tramite
wildcard è una tecnica molto utilizzata in diversi ambiti
dell’informatica, ed è presente nella grande maggioranza delle shell,
sia nei sistemi Windows sia nei sistemi *nix.
Quando viene utilizzato l’operatore -like, in sostanza, la shell
converte i due operandi in stringhe e le confronta: a differenza di -eq,
però, questo operatore rileva nella stringa di destra la presenza di
eventuali wildcard, ovvero caratteri segnaposto che non
rappresentano sé stessi bensì altre sequenze di caratteri.
Senza scendere nei particolari dell’utilizzo dei wildcard, argomento
approfondito nel Capitolo 13, è utile in questo contesto analizzare un
paio di dimostrazioni per capire le potenzialità di questo operatore.
Usando il wildcard *, per esempio, è possibile richiedere
all’operatore -like di ritornare $true solo quando l’operando di sinistra
inizia con una determinata sequenza di caratteri, ignorando il resto:

PS C:\Users\ikmju> 'Padova' -like 'pa*'


True
PS C:\Users\ikmju> 'Pescara' -like 'pa*'
False

Ancora, prefissando una sequenza di caratteri con il wildcard *, si


richiede all’operatore -like di ritornare $true solo quando l’operando di
sinistra termina con una determinata sequenza di caratteri:

PS C:\Users\ikmju> 'Padova' -like '*va'


True
L’operatore -notlike fa esattamente l’opposto di -like e ritorna $true in
tutti quei casi in cui quest’ultimo ritornerebbe $false e viceversa.
Nello script che segue, per esempio, la shell ritorna $false perché
l’espressione di sinistra, una volta convertita in stringa, comincia con
la sequenza 123:

PS C:\Users\ikmju> 12345 -notlike '123*'


False

Gli operatori -like e -notlike non sono soggetti a problemi di


conversione nel caso in cui gli operandi siano di tipo differente.
Questo perché, com’è stato evidenziato nel Capitolo 3, qualsiasi
oggetto all’interno del framework, grazie al design del framework
Microsoft.NET, è convertibile in una stringa. Gli operandi pari a $null
sono automaticamente convertiti in una stringa vuota.

Gli operatori -match e -notmatch


Questi operatori rappresentano una sorta di evoluzione rispetto ai
precedenti: -match e il suo opposto, -notmatch, sono infatti simili agli
operatori -like e -notiike, ma non utilizzano i wildcard per effettuare i
confronti bensì uno strumento molto più potente: le espressioni
regolari.
Anche in questo caso, poiché le espressioni regolari sono trattate in
maniera approfondita nel Capitolo 13, nel seguito di questo
paragrafo sono riportate solo alcune dimostrazioni con l’obiettivo di
illustrare brevemente le modalità d’uso degli operatori -match e -
notmatch.

Nello script che segue, per esempio, PowerShell ritorna $true solo
quando le espressioni di sinistra hanno lo stesso formato di un
codice di avviamento postale italiano e sono quindi composte da
cinque cifre:

PS C:\Users\ikmju> '35100' -match '^\d{5}$'


True
PS C:\Users\ikmju> 'test' -match '^\d{5}$'
False
PS C:\Users\ikmju> 'abc35100cde' -match '^\d{5}$'
False
In quest’altro script, invece, l’operatore -notmatch ritorna $false quando
le espressioni di sinistra (o la loro rappresentazione in stringa)
iniziano e terminano con lo stesso carattere; restituisce $true in caso
contrario:

PS C:\Users\ikmju> 'Efran Cobisi' -notmatch '^(.).*\1$'


True
PS C:\Users\ikmju> 'si sedes non is' -notmatch '^(.).*\1$'
False
PS C: \Users\ikmju> 3502399813 -notmatch '^(.).*\l$'
False

Così come per gli operatori -like e -notlike, anche per -match e -notmatch
valgono le considerazioni già fatte in materia di trattamento di
operandi di tipo differente e operandi nulli.

Il lettore con precedenti esperienze nelle shell *nix può facilmente notare come gli
operatori -match e -notmatch siano un efficiente sostituto del tool a riga di
NO comando grep, mentre il lettore proveniente dalle precedenti shell Windows può
TA constatare come la funzionalità delle espressioni regolari, introdotte con Windows
Scripting Host, abbia finalmente un ruolo di prim’ordine all’interno degli strumenti
offerti dalla shell.

Gli operatori -is e -isnot


Gli operatori -is e -isnot eseguono un confronto tra un’espressione
qualsiasi e un tipo di Microsoft .NET: si tratta di due costrutti molto
utili quando si adoperano oggetti di tipo differente e si ha la
necessità di variare il flusso di esecuzione del codice in base a ciò
che si sta elaborando, di volta in volta.
L’operatore -is ritorna $true quando l’operando di sinistra, che può
essere un’espressione qualsiasi, è del tipo indicato dall’operando di
destra, che può essere un’istanza di System.Type oppure
un’espressione che possa essere convertita in tale tipo. L’operatore -
isnot, invece, fa esattamente l’opposto e ritorna $true quando
l’operando di sinistra non è del tipo specificato dall’operando di
destra.
In alternativa a un’istanza di System.Type, inoltre, l’operando di destra
per entrambi questi operatori accetta sia un type accelerator sia una
stringa con il nome del tipo desiderato. L’espressione che segue, per
esempio, ritorna il valore $true perché il cmdlet Get-Date restituisce
sempre un oggetto del tipo System.DateTime (qui recuperato grazie al
type accelerator corrispondente):

PS C:\Users\ikmju> (Get-Date) -is [datetime]


True

In questo script, invece, l’operatore -is ritorna $false, perché


l’operando di sinistra è del tipo System.Double e non del tipo System.Int32:

PS C:\Users\ikmju> 123.5 -is 'System.Int32'


False

In quest’altro caso la shell determina che l’espressione di sinistra è


effettivamente un intero a 32 bit, pertanto l’operatore -isnot ritorna
$false:

PS C:\Users\ikmju> 123 -isnot [Type]::GetType('System.Int32')


False

Entrambi gli operatori descritti in questa sezione consentono al solo


operando di sinistra di avere un valore pari a $null: in tale circostanza
l’espressione non è mai del tipo indicato, per cui -is ritorna sempre
$false mentre -isnot sempre $true.

PS C:\Users\ikmju> $null -is [datetime]


False
PS C:\Users\ikmju> $null -isnot [datetime]
True

Nel caso sia l’operando di destra a essere nullo, viene generato un


errore simile a questo:

PS C:\Users\Ikmju> 'abc' -is $null


L'operando destro di '-is' deve essere un tipo.
In riga:1 car:10
+ 'abc' -is <<<< $null
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : IsOperatorRequiresType

Gli operatori case sensitive


Come anticipato in precedenza, per gli operatori analizzati fino a
questo punto tutte le possibili operazioni di confronto tra stringhe
sono case insensitive: si tratta di una decisione presa da Microsoft
per rendere più semplice e coerente con il resto del sistema
operativo l’impiego della shell.
Il confronto case sensitive tra stringhe, sensibile quindi alla
differenza tra maiuscole e minuscole, è reso possibile da una nuova
serie di operatori del tutto simili ai precedenti, che si distinguono da
questi ultimi per la lettera c di case sensitive, frapposta tra il simbolo
del trattino (-) e l’acronimo di ciascun elemento. L’unica differenza tra
i due gruppi è dunque la diversa modalità di trattamento delle
stringhe: per il resto si tratta di operatori gemelli. Le considerazioni
fatte per il primo gruppo valgono anche per il secondo e quando non
si confrontano stringhe, quindi, si ottengono risultati identici da parte
di entrambe le tipologie.
L’operatore -ceq, pertanto, è l’analogo case sensitive di -eq, mentre
l’operatore -cne è l’analogo case sensitive di -ne e così via; gli
operatori -is e -isnot, invece, non hanno alcuna controparte di questo
tipo perché non si occupano del confronto tra stringhe.

Tabella 6.1 - Gli operatori case sensitive.


Operatore case sensitive Equivalente case insensitive
-ceq -eq
-cne -ne
-clt -lt
-cgt -gt
-cle -le
-cge -ge
-clike -like
-cnotlike -notlike
-cmatch -match
-cnotmatch -notmatch

Le due espressioni utilizzate nell’esempio dell’operatore -eq, dunque,


se modificate per utilizzare -ceq generano risultati differenti:

PS C:\Users\ikmju> 'Efran' -ceq 'Efran'


True
PS C:\Users\ikmju> 'Efran' -ceq 'eFrAn'
False
La sensibilità alla differenza tra maiuscole e minuscole influisce, di
conseguenza, anche sull’ordinamento di cui tiene conto PowerShell
per determinare quale stringa venga prima dell’altra durante le
operazioni di confronto con gli operatori -clt, -cgt,
-cle e -cge.

Nonostante all’interno del framework Microsoft.NET sia possibile


usufruire di un numero cospicuo di ordinamenti differenti, definiti
dallo standard Unicode per ogni cultura e lingua del mondo, gli
operatori di confronto della shell utilizzano un ordinamento neutrale,
che corrisponde a quello previsto per la lingua inglese: è
consigliabile prestare attenzione a questo dettaglio perché, come
analizzato nel Capitolo 9, la shell espone alcuni metodi per ordinare
esplicitamente insiemi di oggetti, che sfruttano, di default, la cultura
attiva per l’utente corrente (che potrebbe non essere quella inglese!).

Verificare più condizioni


All’interno di PowerShell, così come in qualsiasi altro linguaggio di
sviluppo, è molto frequente la necessità di raggruppare condizioni
differenti ed eseguire determinati blocchi di istruzioni solo in casi
specifici; la logica booleana, d’altra parte, prevede la possibilità di
collegare più espressioni tra loro per generare un unico valore di
verità, utilizzando dei connettivi logici.
PowerShell risponde a questa esigenza fornendo una serie di
operatori in grado di agire sulle espressioni e produrre il valore
logico desiderato.

L’operatore -and
L’operatore -and restituisce il valore $true quando entrambe le
espressioni a cui è frapposto sono riconducibili al valore $true (sono,
dunque, vere). Tramite questo operatore logico è quindi possibile
unire due condizioni tra loro in maniera tale da produrne una terza,
che è vera solo quando sono vere entrambe le prime due.
Eseguendo questo blocco, infatti, si ottiene il risultato di verità logica:

PS C:\Users\ikmju> $true -and $true


True
In quest’altro caso, invece, la condizione non è soddisfatta:

PS C:\Users\ikmju> (1 -eq 1) -and (2 -gt 3)


False

Poiché, come analizzato in precedenza, ogni espressione è


convertita automaticamente in un valore logico da PowerShell, è
possibile creare con facilità condizioni complesse che impiegano
questo operatore. Ecco, per esempio, come è possibile ottenere
dalla shell il valore $true solo in un particolare giorno della settimana
e solo quando un determinato file esiste:

PS C:\Users\ikmju> (Test-Path 'Archive.bak') -and ((Get-Date).DayOfWeek -eq


Thursday')
False

NO Il cmdlet Test-Path ritorna $true quando l’elemento (o il percorso) fornito


TA esiste.

Tabella 6.2 - Tabella della verità per l’operatore -and.


Operando di sinistra Operando di destra Risultato
$false $false $false
$true $false $false
$false $true $false
$true $true $true

L’operatore -or
Questo operatore è simile al precedente, ma ritorna $true non solo
quando entrambi gli operandi sono veri, ma anche nel caso in cui lo
sia solo uno dei due.
Eseguendo questo blocco, quindi, si ottiene, come per l’operatore
precedente, la verità logica:

PS C:\Users\ikmju> $true -or $true

True

In quest’altro caso, invece, il risultato è ancora $true:


PS C:\Users\ikmju> (1 -eq 1) -or (2 -gt 3)

True

Tabella 6.3 - Tabella della verità per l’operatore -or.


Operando di sinistra Operando di destra Risultato
$false $false $false
$true $false $true
$false $true $true
$true $true $true

L’operatore -xor
L’operatore -xor (acronimo di exclusive or) consente di vagliare due
condizioni e di ritornare $true quando una sola delle due è vera,
$false in tutti gli altri casi. Nonostante sia generalmente meno
utilizzato rispetto ai precedenti, questo operatore permette di creare
condizioni molto complesse in un blocco di codice di dimensioni
ridotte.
L’espressione che segue, di conseguenza, genera il risultato
opposto rispetto ai due operatori analizzati in precedenza:

PS C:\Users\ikmju> $true -xor $true


False

Mentre questa genera lo stesso risultato di -or (opposto a quello di -

and):

PS C:\Users\ikmju> (1 -eq 1) -xor (2 -gt 3)


True

Infine, questo blocco ritorna $true solo quando esiste il primo file
oppure il secondo, ma non nel caso in cui esistano entrambi oppure
nessuno dei due:

PS C:\Users\ikmju> (Test-Path 'Archive.bak') -xor (Test-Path 'Log.bak')


False

Tabella 6.4 - Tabella della verità per l’operatore -xor.


Operando di sinistra Operando di destra Risultato
$false $false $false
$true $false $true
$false $true $true
$true $true $false

L’operatore -not
Mentre tutti gli operatori analizzati in precedenza utilizzano due
espressioni distinte per generarne una terza, l’operatore -not
ammette un unico operando e genera un valore logico opposto a
quello dell’espressione di riferimento. Se questo operatore è
applicato a un’espressione vera, pertanto, produce un risultato falso;
viceversa, se l’espressione di partenza è falsa, -not restituisce un
risultato vero. Dal punto di vista sintattico questo operatore si colloca
alla sinistra dell’espressione desiderata, diversamente dagli altri
operatori unari: essendocene solo uno, però, quando ci si riferisce
all’operando non lo si qualifica con la posizione assunta.
L’espressione che segue, per esempio, ritorna il valore $true quando
un determinato file non esiste:

PS C:\Users\ikmju> -not (Test-Path 'Archive.bak')


True

L’operatore -not può essere utilizzato anche attraverso il simbolo del


punto esclamativo (!), tipicamente impiegato nei linguaggi di sviluppo
di derivazione C:

PS C:\Users\ikmju> !(2 -gt 3)


True

Tabella 6.5 - Tabella della verità per l’operatore -not.


Operando Risultato
$false $true
$true $false
Il flusso di esecuzione

Le istruzioni che permettono di creare blocchi


condizionali all’interno della shell e i cicli, con cui è
possibile ripetere le istruzioni in base al presentarsi di
una o più condizioni.

Nel capitolo precedente sono stati analizzati i concetti fondamentali


che consentono all’utente di PowerShell di definire nuove condizioni
all’interno del proprio codice, in base a una o più espressioni
eventualmente connesse tramite operatori logici. In questo capitolo,
invece, sono esaminati quei costrutti presenti nel linguaggio di
scripting della shell che rendono possibile la modifica e la gestione
del flusso di esecuzione del codice, sfruttando le tecniche illustrate in
precedenza per definire le condizioni.

I blocchi condizionali
All’interno di PowerShell, così come avviene per la maggior parte dei
linguaggi di sviluppo, è possibile raggruppare una o più istruzioni
affinché siano eseguite solo nel caso in cui una determinata
condizione sia soddisfatta.
Il nome, la sintassi e l’impiego di questi blocchi, chiamati in gergo
blocchi condiziona li, risultano familiari al lettore con precedenti
esperienze di sviluppo nei linguaggi di discendenza C, grazie allo
sforzo profuso da Microsoft per facilitare l’adozione della piattaforma
di scripting della shell da parte del maggior numero possibile di
utenti.

Il blocco if/else/elseif
Il blocco condizionale più elementare, if, prevede l’esecuzione delle
istruzioni contenute al suo interno solo nel caso in cui l’espressione
desiderata risulti pari
a $true.
Questo blocco è composto dalla keyword if seguita dall’espressione
che funge da condizione - racchiusa tra parentesi tonde - e, a
seguire, dal blocco di operazioni da compiere nel caso in cui la
condizione sia soddisfatta, racchiuso tra parentesi graffe. La sintassi
di base di questo blocco è la seguente:

if (<condizione>) {
<elenco comandi>
}

Lo script che segue, per esempio, se lanciato durante il tipico orario


d’ufficio, visualizza a video un messaggio:

$hour = (Get-Date).Hour

if (($hour -gè 9) -and ($hour -le 18)) {


Write-Host 'Buon lavoro!
}

NOT Il cmdlet Get-Date è trattato in modo approfondito nel Capitolo


A 15.

Il blocco if consente di specificare, mediante la keyword else, un


ulteriore insieme di istruzioni da eseguire qualora la condizione di
partenza non sia soddisfatta; si tratta di un secondo blocco di codice
opzionale, valido solo se usato in seguito a un blocco if. La keyword
else non prevede, quindi, l’indicazione di alcuna espressione ma

richiede, a seguire, la presenza del blocco correlato.


Volendo, per esempio, riutilizzare lo script precedente per
visualizzare a video un messaggio alternativo quando non lo si
esegue in orario d’ufficio, è possibile scrivere del codice simile a
questo:

$hour = (Get-Date).Hour

if (($hour -gè 9) -and ($hour -le 18)) {


Write-Host 'Buon lavoro!
}
else {
Write-Host 'Relax...'
}

Il blocco if ammette, infine, la presenza di zero o più keyword elseif,


utilizzabili per concatenare una serie di test condizionali in seguito al
blocco if principale (ma sintatticamente posti prima dell’eventuale
keyword else).
Per determinare quale insieme di istruzioni eseguire all’interno di un
blocco if, la shell verifica dapprima la condizione indicata dalla
keyword if e se questa è soddisfatta, ne esegue il rispettivo blocco.
In caso contrario, invece, la verifica procede con il blocco elseif
successivo, se presente, vagliando la rispettiva condizione ed
eseguendo le istruzioni indicate in caso di successo. In caso
negativo PowerShell verifica, quindi, il blocco elseif successivo, se
presente, e così via; le istruzioni indicate dalla keyword else, se
presenti, sono infine eseguite se tutti i test specificati dalle keyword
precedenti non sono andati a buon fine.
La sintassi completa di questo blocco condizionale è dunque
rappresentabile dallo schema che segue:

if (<condizione>) {
<elenco comandi>
}
elseif (<condizione>) {
<elenco comandi>
}
elseif (<condizione>) {
<elenco comandi>
}
...
else {
<elenco comandi>
}
Continuando a integrare lo script precedente, quindi, è possibile
aggiungere una serie di keyword elseif all’esempio, in maniera tale
da visualizzare a video messaggi diversi in base all’orario di
esecuzione dello script:

$hour = (Get-Date).Hour
$minute = (Get-Date).Minute

if (($hour -ge 9) -and ($hour -le 18)) {


Write-Host 'Buon lavoro!'
}
elseif (($hour -gt 18) -and ($hour -le 21)) {
Write-Host 'Buona serata!'
}
elseif ((($hour -gt 7) -and ($minute -ge 30)) -and ($hour -lt 9)) {
Write-Host 'Sveglia!'
}
else {
Write-Host 'Relax...'
}

A mano a mano che il numero di test condizionali aumenta, d’altra


parte, l’impiego dei blocchi analizzati fino a questo punto tende a
rendere il codice prolisso e poco leggibile; nel caso in cui tutti i test
siano riconducibili alla stessa espressione, però, PowerShell dispone
di un blocco alternativo, che risolve brillantemente questi problemi.

Il blocco switch
Il blocco switch permette di verificare il valore di una particolare
espressione a fronte di una lista di possibilità predefinite dall’utente
ed eseguire l’insieme di istruzioni corrispondente.
Nonostante questo blocco condizionale abbia una semantica e una
sintassi molto simili ai blocchi omonimi presenti nei linguaggi di
derivazione C, PowerShell ne offre una variante ricca di funzionalità
innovative.
Un’istruzione switch di base prevede l’impiego della relativa keyword
e l’indicazione dell’espressione da verificare, seguita dalla lista delle
condizioni da testare, caso per caso, cui sono associati i blocchi di
codice da eseguire nel caso in cui le relative condizioni siano
soddisfatte.
Nell’utilizzo più semplice, un blocco di questo tipo risulta quindi
equivalente a una serie di istruzioni if, una di seguito all’altra, dove
l’operando di sinistra è in comune tra tutte e ogni condizione è un
confronto di uguaglianza rispetto a un’altra particolare espressione.
La sintassi di base del blocco switch è la seguente:

switch (<espressione>) {
<valore> { <elenco comandi> }
<valore> { <elenco comandi> }
<valore> { <elenco comandi> }
...
}

Nello script che segue, per esempio, la shell visualizza a video un


messaggio in base all’ora corrente:

$hour = (Get-Date).Hour

switch ($hour) {
12 { Write-Host 'Ora di pranzo' }
20 { Write-Host 'Ora di cena' }
}

Così come avviene per gli operatori di confronto, anche nel caso di
switch il paragone tra l’espressione da verificare e quella dei diversi
casi avviene facendo il cast di questi ultimi verso il tipo della prima.
Lo script che segue, pertanto, è perfettamente lecito (anche se non
porta ad alcun risultato):

$test = 619

switch ($test) {
235.2 { Write-Host 'Un numero in virgola mobile' }
(Get-Date) { Write-Host 'Una data' }
'xyz' { Write-Host 'Una stringa' }
}

Là dove il cast non è possibile, d’altra parte, la shell considera la


condizione non soddisfatta e prosegue con l’analisi delle condizioni
successive, senza generare alcun errore.
Se utilizzata in luogo di una condizione, la keyword default indica che
il blocco di codice che la segue deve essere eseguito nel caso in cui
non sia trovata alcuna corrispondenza tra i casi previsti del blocco
switch. Si tratta, in buona sostanza, di una sorta di caso particolare
che viene preso in considerazione quando tutte le altre condizioni
non sono soddisfatte; per tale ragione può esserci al massimo un
caso default per blocco switch.
Riprendendo il primo esempio di questa sezione, quindi, è possibile
utilizzare la keyword default per visualizzare comunque a video un
messaggio, anche se l’ora corrente non è tra quelle esplicitamente
contemplate:

$hour = (Get-Date).Hour

switch ($hour) {
12 { Write-Host 'Ora di pranzo' }
20 { Write-Host 'Ora di cena' }
default { Write-Host 'Ora di studiare PowerShell!' }
}

Anche se la posizione del caso default, rispetto agli altri casi, non varia il risultato
NO
del blocco switch, è preferibile collocare questo caso nell’ultima posizione
TA
disponibile, al fine di aumentare la leggibilità del codice.

Quando le espressioni confrontate sono stringhe il comportamento


predefinito del blocco switch consiste nel paragonare i diversi oggetti
in modalità case insensitive. Lo script che segue, di conseguenza,
visualizza a video entrambi i messaggi:

PS C:\Users\ikmju> switch ('Efran') {


>> 'efran' { Write-Host 'Primo caso' }
>> 'EFRAN' { Write-Host 'Secondo caso' }
>> }
>>
Primo caso
Secondo caso

PowerShell consente di terminare prematuramente l’esecuzione di


un blocco switch utilizzando la keyword break all’interno del codice
di uno dei casi; usata in questo modo, quindi, l’istruzione break è in
grado di interrompere l’esame dei casi successivi, presenti all’interno
del blocco condizionale. È possibile modificare l’esempio
precedente, dunque, facendo in modo che l’istruzione switch soddisfi
al massimo uno tra i casi previsti (nel codice che segue il secondo
caso non è mai preso in considerazione):

PS C:\Users\ikmju> switch ('Efran') {


>> 'efran' { Write-Host 'Primo caso'; break }
>> 'EFRAN' { Write-Host 'Secondo caso'; break }
>> }
>>
Primo caso
Alla stregua delle diverse possibilità offerte dagli operatori di
confronto analizzati nel capitolo precedente, la modalità con cui il
blocco switch confronta le stringhe può essere variata affinché il
motore impieghi i wildcard oppure le espressioni regolari. Utilizzando
l’opzione -wildcard, infatti, il blocco interpreta i confronti tra espressioni
di tipo stringa come se fosse utilizzato l’operatore -like. Lo script che
segue, per esempio, utilizza il blocco switch per una semplice
distinzione dell’area geografica di un codice di avviamento postale
italiano:

PS C:\Users\ikmju> switch -wildcard ('35123') {


>> '30*' { Write-Host 'Provincia di Venezia' }
>> '35*' { Write-Host 'Provincia di Padova' }
>> '20*' { Write-Host 'Provincia di Milano' }
>> }
>>
Provincia di Padova

L’opzione -regex, d’altro canto, indica all’istruzione switch di effettuare i


confronti tra le stringhe utilizzando le espressioni regolari, così come
avviene per l’operatore -match. In questo script, per esempio, viene
visualizzato un messaggio a seconda del formato della stringa di
input:

PS C:\Users\ikmju> switch -regex ('35123') {


>> '^\d{11}$' { Write-Host 'Partita IVA' }
>> '^\d{5}$' { Write-Host 'CAP' }
>> default { Write-Host '?' }
>> }
>>
CAP

Grazie all’opzione -casesensitive, inoltre, è possibile richiedere


all’istruzione switch di eseguire tutti i confronti in modalità case
sensitive, impiegando il sistema desiderato.
Ecco uno degli script impiegati in precedenza, modificato per
adoperare l’opzione

-casesensitive:

PS C:\Users\ikmju> switch -casesensitive ('EFRAN') {


>> 'efran' { Write-Host 'Primo caso'; break }
>> 'EFRAN' { Write-Host 'Secondo caso'; break }
>> }
>>
Secondo caso

Ancora, lo stesso script adattato per usare i wildcard in modalità


case sensitive:

PS C:\Users\ikmju> switch -wildcard -casesensitive ('EFRAN') {


>> 'ef*' { Write-Host 'Primo caso'; break }
>> '*AN' { Write-Host 'Secondo caso'; break }
>> }
>>
Secondo caso

Il blocco switch consente, infine, di specificare una qualsiasi


espressione in luogo del valore da testare per un particolare caso: la
shell, infatti, in tale circostanza effettua il cast a valore logico
dell’espressione e, nel caso in cui esso corrisponda a $true, esegue il
blocco di istruzioni corrispondente. Per questa tipologia di condizioni
PowerShell rende disponibile il valore dell’espressione principale
fornita all’istruzione switch tramite la variabile automatica $_.
Lo script che segue è la revisione dell’esempio utilizzato nella
sezione dedicata al blocco if, adattato per sfruttare la compattezza
dell’istruzione switch:

switch (Get-Date) {
{ ($_.Hour -gè 9) -and ($_.Hour -le 18) } { Write-Host 'Buon lavoro!' }
( ($_.Hour -gt 18) -and ($_.Hour -le 21) } { Write-Host 'Buona serata!' }
( (($_.Hour -gt 7) -and ($_.Minute -gè 30)) -and ($_.Hour -lt 9) } { Write-
Host 'Sveglia!' }
default { Write-Host 'Relax...' }
}

NO All’interno di questo esempio la variabile automatica $_ fa riferimento al valore


TA ritornato da Get-Date, fornito all’istruzione switch.

La sintassi completa del blocco switch, a questo punto, può essere


estesa per comprendere le ulteriori opzioni contemplate da
PowerShell:

switch [-regex|-wildcard][-casesensitive] (<espressione>) {


<valore>|{ <espressione> } { <elenco comandi> }
<valore>|{ <espressione> } { <elenco comandi> }
...
default { <elenco comandi> }
}
L’istruzione switch, quindi, permette di creare in poche righe di codice
blocchi condizionali molto complessi, dove è possibile mischiare con
facilità tecniche di confronto eterogenee.

I cicli
Un ciclo, in informatica, consiste in un insieme di comandi eseguiti a
ripetizione, fino al raggiungimento di una determinata condizione; chi
utilizza PowerShell, così come qualsiasi altro linguaggio di scripting,
può trovare nei cicli uno strumento indispensabile per raggiungere il
livello di automazione desiderato all’interno dei propri script.

Il ciclo while
La keyword while introduce il ciclo più elementare disponibile
all’interno della shell, il cui obiettivo consiste nel ripetere un
determinato blocco di comandi fintantoché una particolare
condizione è soddisfatta. La sintassi del ciclo while è rappresentabile
mediante questo schema:

while (<condizione>) {
<elenco comandi>
}

La condizione su cui si basa il ciclo può essere un’espressione di


qualsiasi tipo: a ogni iterazione del ciclo la shell ne effettua il cast per
ottenere un valore logico ed esegue i comandi previsti nel caso in cui
tale valore sia pari a $true.
Nello script che segue, per esempio, il blocco di istruzioni interno
viene eseguito per tre volte:

$iterazione = 0

while ($iterazione -lt 3) {


Write-Host 'hello, world'
$iterazione++
}

Alla prima esecuzione la variabile $iterazione è pari a o, quindi la


condizione che sia minore di 3 è soddisfatta. Nel blocco la variabile
viene incrementata e alla seconda iterazione la condizione iniziale è
ancora soddisfatta ($iterazione è infatti pari a 1); alla terza iterazione la
variabile è inizialmente pari a 2 ma, in seguito all’incremento finale,
raggiunge il valore 3, circostanza che invalida la condizione di
partenza e causa la mancata esecuzione di nuove iterazioni.
L’esecuzione del ciclo a questo punto è conclusa. Poiché il ciclo while
non pone vincoli al numero di ripetizioni da eseguire, d’altra parte,
conviene prestare la massima attenzione alla condizione fornita al
blocco; è sempre possibile creare – magari inconsapevolmente – dei
cicli senza termine:

PS C:\Users\ikmju> while ($true) {


>> Write-Host 'Loop infinito...'
>> }
>>
Loop infinito...
Loop infinito...
Loop infinito...
Loop infinito...
[...]

L’esecuzione di questo script può essere annullata utilizzando la combinazione di


NO
tasti Ctrl+C, sia all’interno dell’interfaccia a console testuale di PowerShell sia
TA
all’interno dell’ISE.

Il ciclo do..while
Questo ciclo si distingue dal precedente per il fatto che la condizione
necessaria per procedere con le iterazioni viene verificata in seguito
all’esecuzione di ogni blocco di istruzioni, anziché in precedenza.
Questa peculiarità porta il blocco di istruzioni a essere sempre
eseguito, pertanto, almeno una volta.
La sintassi del ciclo do..while è rappresentabile nello schema che
segue:

do {
<elenco comandi>
}
while (<condìzione>)

Lo script della sezione precedente, quindi, può essere riadattato in


maniera semplice per utilizzare questo ciclo, producendo tre
messaggi come nel caso precedente:

do {
Write-Host 'hello, world'
$iterazione++
}
while ($iterazione -lt 3)

Cambiando la condizione, però, e utilizzando una condizione che


non può mai essere soddisfatta, è possibile notare come il ciclo
do..while porti comunque a termine la prima iterazione:

PS C:\Users\ikmju> do {
>> Write-Host 'hello, world'
>> }
>> while ($false)
>>
hello, world

Il ciclo do..until
Il ciclo do..until rappresenta una variante poco utilizzata del ciclo
do..while e consente di invertire la verifica effettuata sulla condizione
del ciclo: ferma restando la prima iterazione, infatti, il blocco di
comandi viene ripetuto solo quando la condizione fornita non è
soddisfatta.
In modo molto simile al precedente, la sintassi di questo ciclo è
riassumibile con questo schema:

do {
<elenco comandi>
}
until (<condizione>)

Lo script che segue, per esempio, visualizza a video una scritta fino
a quando la variabile $iterazione risulta uguale a un determinato
valore:

$iterazione = 0

do {
Write-Host 'hello, world'
$iterazione++
}
until ($iterazione -eq 3)

In realtà, dal momento che i cicli do..while e do..until sono


perfettamente simmetrici, non è raro vedere utenti della shell che si
limitano a utilizzare il primo costrutto, anche a costo di impiegare
condizioni negate.

Il ciclo for
Il ciclo for è una versione più completa del ciclo while e consente di
specificare la condizione, le istruzioni di inizializzazione del blocco e
i comandi da ripetere all’interno di un’unica istruzione.
La sintassi di questo ciclo è illustrata da questo schema:

for (<inizializzazione>; <condizione>; <ripetizione>) {


<elenco comandi>
}

I comandi di inizializzazione sono di solito impiegati per assegnare


valori di partenza alle variabili utilizzate all’interno del ciclo e
vengono eseguiti prima che il ciclo inizi. La condizione, invece, è
l’espressione verificata dalla shell prima di effettuare ogni iterazione:
come nel caso di while, anche la condizione di for deve essere
soddisfatta affinché il ciclo possa continuare.
I comandi specificati nel blocco di ripetizione, infine, sono eseguiti al
termine di ogni iterazione e tipicamente vanno a incrementare il
valore delle variabili definite nel blocco di inizializzazione.
Visualizzare tre messaggi a video utilizzando il ciclo for, pertanto, è
semplice come eseguire questo script:

for ($iterazione = 0; $iterazione -lt 3; $iterazione++) {


Write-Host 'hello, world
}

Il ciclo for è il più versatile tra quelli contemplati dalla shell: se non è
fornita una condizione, infatti, il blocco interno è ripetuto senza limite,
mentre i comandi di ini zializzazione e di iterazione sono opzionali;
peculiarità che rendono questo costrutto l’ideale quando si ha la
necessità di scrivere del codice snello. Nello script che segue, per
esempio, si sfrutta la compattezza del ciclo for per recuperare i
multipli di un intero:

PS C:\Users\ikmju> for ($iterazione = 1; ; $iterazione++) {


>> 3 * $iterazione
>> }
>>
3
5
9
12
[...]

Il ciclo foreach
L’ultimo ciclo illustrato in questo capitolo ha l’obiettivo di facilitare il
recupero di elementi che appartengono a una determinata
collezione. Il ciclo foreach, infatti, consente di snellire la procedura di
esecuzione di un determinato blocco di codice a fronte di ciascun
elemento di un insieme di oggetti.
La sintassi di questo ciclo è semplice e include la definizione della
collezione da utilizzare, della variabile impiegata all’interno del ciclo,
cui abbinare di volta in volta ciascun elemento della collezione, e
infine un elenco di comandi da eseguire a ogni iterazione:

foreach ($<elemento> in <collezione>) {


<elenco comandi>
}

Il blocco di comandi è ripetuto fino a quando la collezione non


restituisce ulteriori elementi.
Fornendo l’output del cmdlet Get-Process al ciclo foreach, per esempio, è
possibile estrarre dalla lista dei processi in esecuzione quelli lanciati
nell’ultima ora:

foreach ($process in Get-Process) {


if ($process.StartTime) {
$processAge = (Get-Date) - $process.StartTime
if ($processAge.TotalHours -le 1) {
$process
}
}
}

Ogni elemento ritornato da Get-Process, infatti, da luogo a una nuova


iterazione del ciclo foreach, all’interno della quale la variabile $process
corrisponde all’elemento estratto.
NO Nello script precedente il primo blocco if verifica che l’oggetto rappresentato da
TA $process disponga di una proprietà StartTime (se questa fosse nulla, infatti,
l’espressione equivarrebbe a $false e la condizione non sarebbe soddisfatta):
alcuni processi di sistema (come System e Idle), infatti, non possiedono questa
informazione.

Variare il flusso dei cicli con break e continue


PowerShell consente di variare il normale flusso di esecuzione dei
cicli analizzati fino a questo punto, concedendo all’utente la
possibilità di interrompere prematuramente il ciclo o permettendo al
flusso corrente di tornare immediatamente all’inizio del ciclo, con una
nuova iterazione.
In maniera non molto diversa da quanto avviene per il blocco switch,
infatti, la shell mette a disposizione dell’utente la keyword break per
terminare in qualsiasi momento l’esecuzione del ciclo corrente. In
questo script, per esempio, la keyword break è impiegata per uscire
dal ciclo non appena la variabile $i raggiunge il valore specificato:

PS C:\Users\ikmju> $i = 0
PS C:\Users\ikmju>
PS C:\Users\ikmju> while ($true) {
>> Write-Host 'Prima'
>>
>> $i++
>> if ($i -eq 3) {
>> break
>> }
>>
>> Write-Host 'Dopo'
>> }
>>
Prima
Dopo
Prima
Dopo
Prima

La keyword continue, infine, porta a termine l’iterazione corrente ma


continua il ciclo e prosegue con l’eventuale iterazione successiva.
Per effetto di entrambe le keyword, quindi, in questo esempio la shell
visualizza a video solo i numeri da 101 a 103:

for ($i = 1; ; $i++) {


if ($i -le 100) {
continue
}
$i

if ($i -eq 103) {


break
}
}

Se sono presenti più cicli nidificati l’uno nell’altro, sia break sia
continue hanno effetto nell’ambito del ciclo più vicino; per ovviare a
questa limitazione è possibile aggiungere in prima battuta
un’etichetta distintiva ai cicli di interesse, prefissando l’istruzione di
inizio di ciascun ciclo con il simbolo due punti (:) seguito dal testo
desiderato, secondo questo schema:

:<etichetta> while|do|for|foreach ...

Successivamente è necessario specificare tale etichetta nelle


operazioni di variazione di flusso di esecuzione, facendola seguire
alla keyword break o continue:

break <etichetta>
continue <etichetta>

Il termine del ciclo o l’eventuale esecuzione dell’iterazione


successiva sono in questo modo legati al ciclo individuato
dall’etichetta specificata: in assenza di tale indicazione, la variazione
di flusso procede come analizzato in precedenza, a prescindere
dalla presenza di etichette associate ai cicli.
Nello script che segue, per esempio, il flusso di esecuzione del ciclo
esterno (cui è stata assegnata l’etichetta esterno) è regolato grazie a
un’apposita istruzione break presente nel ciclo interno:

:esterno for ($x = 1; ; $x++) {


$y = 1

while ($y -le 3) {


$x * $y

if ($x * $y -eq 9) {
break esterno
}

if ($y -eq 3) {
break
}

$y++
}
}
NO L’utilizzo di etichette all’interno del proprio codice è una pratica che tende a
TA peggiorarne la leggibilità e pertanto se ne sconsiglia l’impiego.
La pipeline

aggregare
Un approfondimento sulla capacità della shell di
i comandi tra loro all’interno della pipeline, studiando
come avviene l’associazione tra oggetti e
parametri e analizzando alcuni tra i cmdlet più
importanti per questa funzionalità.

I concetti esposti fino a questo punto consentono di gestire con


facilità alcuni aspetti legati all’automazione dei sistemi Windows, sia
grazie alla potenza degli innumerevoli cmdlet presenti in PowerShell
sia grazie alla piattaforma di scripting, che ne permette la gestione e
un utilizzo complesso.
La shell, tuttavia, prevede un’ulteriore modalità d’uso dei cmdlet, che
permette di aggregare più righe di comando tra loro con il duplice
obiettivo di raggiungere un livello ancora più elevato di integrazione
tra un cmdlet e l’altro e rendere più sintetici gli script: il seguito del
capitolo è dedicato a tale modalità e alle importanti implicazioni che
da essa conseguono.

Aggregare i cmdlet
Quasi tutte le shell moderne sono dotate di un meccanismo in grado
di facilitare il passaggio del risultato di un comando a quello
successivo, come input; si tratta di una soluzione che risponde alla
frequente esigenza di evitare l’impiego di variabili temporanee
all’interno degli script, poiché il più delle volte se ne potrebbe fare a
meno e la loro presenza, alla lunga, rende il codice più difficile da
leggere e da mantenere.
All’interno di CMD, per esempio, è possibile lanciare un qualsiasi
applicativo a console testuale e combinarlo con il comando MORE, in
maniera tale che l’output del primo sia gestito e visualizzato pagina
dopo pagina dal secondo. La combinazione dei due comandi
avviene frapponendo tra il primo e il secondo il carattere pipe ( ι ),
come in questo esempio:

type test-txt | more

In modo analogo, la maggior parte delle shell di derivazione *nix


consente di avvalersi della stessa sintassi per raggiungere il
medesimo obiettivo:

cat test-txt | more

All’interno di PowerShell la funzionalità di aggregazione dei comandi


è stata ripresa, partendo probabilmente dalla più matura
implementazione dei sistemi *nix, ampliata e adattata per renderne
possibile l’utilizzo all’interno del framework Microsoft .NET: ne è nato
è uno strumento di estrema potenza e versatilità, che presenta
caratteristiche innovative rispetto ai prodotti precedenti. Nonostante
la sintassi di aggregazione dei comandi sia identica a quella delle
shell classiche, infatti, la trasmissione di dati da un elemento al
successivo, nel caso di PowerShell, non è limitata al contenuto
testuale ma è estesa all’intero ecosistema degli oggetti disponibili!
La riga di comando costituita da più blocchi separati da pipe prende
il nome di pipeline e viene eseguita dalla shell da sinistra verso
destra: ogni risultato fornito dal primo blocco genera una nuova
esecuzione del secondo blocco, diventandone l’input. Ogni output
prodotto dal secondo blocco, poi, alimenta il terzo blocco,
eseguendolo, e così via fino al termine dei blocchi disponibili; tutti i
risultati prodotti dall’ultimo blocco sono ritornati alla shell, che di
default li visualizza a video. Qualsiasi riga di comando di PowerShell
può essere, pertanto, considerata una pipeline con un unico blocco.
La maggior parte dei cmdlet disponibili è in grado di sfruttare il
meccanismo del passaggio di oggetti tramite la pipeline rendendo la
scrittura del codice più agevole e immediata: la riga che segue, per
esempio, termina tutti i processi della calcolatrice di Windows
(l’eseguibile cale.exe):

Get-Process cale I Stop-Process

NOT La gestione dei processi è trattata nel Capitolo


A 21.

Il primo blocco, infatti, recupera tutti i processi il cui nome è caie:

Get-Process cale

Ogni oggetto ritornato da questa istruzione genera, quindi,


un’esecuzione del blocco successivo, che viene alimentato
dall’oggetto che ne ha scatenato l’esecuzione; il passaggio di oggetti
da un blocco della pipeline al successivo avviene grazie a un
efficiente meccanismo di associazione, approfondito nel seguito del
capitolo, che si occupa di legare un particolare parametro del cmdlet
del blocco di destinazione con l’oggetto in esame. Nello script
precedente, per esempio, gli oggetti ritornati da Get-Process sono
abbinati al parametro –InputObject di stop-process e i due blocchi di
questa pipeline sono equivalenti al passaggio manuale dell’output
del primo cmdlet tramite una variabile temporanea:

$processes = (Get-Process calc)


Stop-Process -InputObject $processes

o, ancora, al passaggio diretto dell’espressione all’interno di un’unica


riga:

Stop-Process -InputObject (Get-Process cale)

Entrambe le alternative, benché valide, non si avvalgono tuttavia


delle agevolazioni che la shell concede alla pipeline, poiché è
necessario specificare manualmente il parametro di destinazione da
utilizzare per ciascun blocco e il codice che ne risulta è sicuramente
meno “naturale” ed elegante rispetto al primo.
Il secondo e ultimo blocco dell’esempio precedente, infine, non
emette alcun risultato perché Stop-Process, di default, non restituisce
nulla: la shell, di conseguenza, non visualizza alcun output per
l’intera pipeline in oggetto.

La pipeline e i parametri dei cmdlet


Come anticipato, quando un oggetto fluisce da un blocco al
successivo all’interno della pipeline, PowerShell si occupa di
determinare a quale dei parametri del cmdlet del nuovo blocco
associarlo, prima di proseguire con l’elaborazione. L’associazione
dei parametri (parameter binding) avviene in maniera totalmente
automatica e si basa sul tipo di oggetto fornito in input e sui metadati
associati ai parametri del cmdlet in questione.

I metadati sono informazioni legate agli oggetti, inserite dagli sviluppatori dei
NO
cmdlet e facilmente recuperabili dalla shell; il framework Microsoft.NET dispone di
TA
un avanzato sistema di recupero e gestione dei metadati, basato sugli attributi.

I parametri che accettano oggetti dalla pipeline ammettono due


diverse modalità di associazione: il passaggio per valore e il
passaggio per nome di proprietà.
Quando un parametro accetta dalla pipeline il passaggio per valore
(ByValue), PowerShell verifica che l’oggetto fornito sia del tipo
ammesso dal parametro stesso o che sia possibile una conversione
dal tipo del primo al tipo del secondo: se questo avviene
l’associazione ha avuto successo e l’operazione termina qui; in caso
contrario la verifica procede con gli altri eventuali parametri.
Se il parametro accetta dalla pipeline il passaggio per nome di
proprietà (ByProper-tyName), invece, la shell ricava il nome del
parametro in oggetto e tenta di recuperare il valore di una proprietà
con il medesimo nome dall’oggetto fornito: se questa esiste e
appartiene al tipo del parametro o a un tipo convertibile in
quest’ultimo allora l’associazione ha successo e l’operazione termina
qui, altrimenti procede con gli altri eventuali parametri.
II comando Get-Help consente di recuperare con facilità, per ciascun
cmdlet, le infor mazioni relative al passaggio di oggetti tramite la
pipeline; utilizzando lo switch -Full, infatti, Get-Help aggiunge al proprio
output alcuni dettagli per ciascun parametro, importanti quando si
desidera utilizzare la pipeline. Richiamando la guida in linea per Stop-
Process, per esempio, il sistema evidenzia come in questo cmdlet
siano presenti tre diverse proprietà in grado di accettare oggetti dalla
pipeline (-Id, -InputObject e -Name):

PS C:\Users\ikmju> Get-Help Stop-Process -Full

NAME
Stop-Process

[...]

PARAMETERS

[...]

-Id <Int32[]>
Specifies the process IDs of the processes to be stopped. To specify
multiple IDs, use commas to separate the IDs. To find the PID of a process, type
"get-process". The parameter name ("Id") is optional.

Required? true
Position? 1
Default value
Accept pipeline input? true (ByPropertyName)
Accept wildcard characters? false

-InputObject <Process[]>
Stops the processes represented by the specified process objects. Enter
a variable that contains the objects, or type a command or expression that gets the
objects.

Required? true
Position? named
Default value
Accept pipeline input? true (ByValue)
Accept wildcard characters? false

-Name <string[]>
Specifies the process names of the processes to be stopped. You can type
multiple process names (separated by commas) or use wildcard characters.

Required? true
Position? named
Default value
Accept pipeline input? true (ByPropertyName)
Accept wildcard characters? False
Per determinare se un parametro ammette il passaggio di oggetti
dalla pipeline, quindi, è sufficiente verificare tramite Get-Help (o
consultando la guida in formato .CHM, disponibile con PowerShell
ISE) se alla relativa voce Accept pipeline input? corrisponde il valore true:
in tal caso il sistema visualizza, a seguire, la modalità di passaggio
consentita (ßyValue oppure ByPropertyName).
Come si evince dall’output della guida, dunque, il cmdlet stop-process
ammette di essere alimentato tramite il passaggio di oggetti
provenienti dalla pipeline a patto che questi rispettino almeno una di
queste condizioni:
• siano oggetti di tipo Process o collezioni di questi ultimi oppure
oggetti convertibili in questo tipo;
• abbiano una proprietà id di tipo intero (o una collezione di interi)
oppure di un tipo convertibile in intero (o collezione);
• abbiano una proprietà Name di tipo stringa (o una collezione di
stringhe) oppure di un tipo convertibile in stringa (o collezione).
È possibile, d’altra parte, che l’oggetto fornito dalla pipeline soddisfi
più di uno di questi criteri: gli stessi oggetti ritornati da Get-Process,
infatti, li soddisfano tutti! Ognuno di questi è di tipo Process e
dispone sia della proprietà id sia della proprietà Name:

PS C:\Users\ikmju> Get-Process | Get-Member

TypeName: System-Diagnostics - Process

Name MemberType Definition


---- ---------- -----------
[...]
Name AliasProperty Name = ProcessName
[...]
Id Property System.Int32 Id {get;}
[...]
ProcessName Property System.String ProcessName {get;}

La proprietà Name, in effetti, è un nome alternativo per la proprietà ProcessName.


NO
Per quanto concerne l’associazione dei parametri, PowerShell non fa differenza
TA
tra i due concetti grazie all’Extended Type System.

Per ovviare a questa eventualità, Microsoft ha diviso a livello


macroscopico l’operazione di associazione dei parametri con gli
oggetti provenienti dalla pipeline in quattro fasi distinte, che la
rendono deterministica e facilmente prevedibile.
Nella prima sono considerati solo i parametri che accettano il
passaggio di oggetti per valore (ByValue) e la shell effettua
l’associazione solo quando il tipo di questi ultimi è il medesimo del
parametro (o una collezione): non viene tentata, quindi, alcuna
ulteriore conversione di tipi.
Nella seconda fase, poi, la shell prende in considerazione i parametri
che accettano il passaggio di oggetti per nome di proprietà
(ByPropertyName) e, anche in questo caso, l’associazione avviene solo
nel caso in cui il tipo sia il medesimo di quello del parametro (o una
collezione).
Nella terza fase sono ripresi nuovamente tutti i parametri della prima
(ByValue) e viene tentata una conversione dell’oggetto nel tipo atteso
(o in una collezione): se la conversione ha successo il sistema
associa il valore, altrimenti procede.
Nella quarta e ultima fase, infine, PowerShell riprende i parametri
recuperati nella seconda fase (ByPropertyName) e, analogamente a
quanto avviene nella fase precedente, tenta la conversione
dell’oggetto fornito nel tipo atteso (o in una collezione).
Nel caso in cui un oggetto fornito tramite pipeline fallisca tutte e
quattro le fasi di associazione con i parametri di un determinato
cmdlet, la shell termina l’esecuzione del comando con un errore,
simile a questo:

PS C:\Users\ikmju> Get-Date | Stop-Process


Stop-Process : Impossibile associare l'oggetto di input a qualsiasi parametro del
comando. Il comando non accetta l'input da pipeline oppure l'input e le relative
proprietà non corrispondono ad alcun parametro che accetta l'input da pipeline.
In riga:l car:24
+ Get-Date | Stop-Process <<<<
+ CategoryInfo : InvalidArgument: (11/02/2010 13.05.10:
PSObject) [Stop-Process], ParameterBindingExceptio
n
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.
Commands.StopProcessCommand

Ispezionare l’associazione dei parametri


Nonostante l’apparente semplicità e naturalezza con cui PowerShell
gestisce l’associazione degli oggetti provenienti dalla pipeline con i
parametri dei cmdlet, le quattro fasi dell’operazione sono molto
complesse e si avvalgono di numerose e diverse tecniche di
conversione, che includono quelle disponibili nel framework
Microsoft .NET - il lettore con precedenti esperienze di sviluppo in
questo ambiente può ricordare il paradigma delle classi TypeDescriptor
e TypeConverter – e sono ampliate con ulteriori tecniche, proprietarie
della shell.
Grazie al cmdlet Trace-Command è fortunatamente possibile prendere
visione di tutte le singole attività effettuate durante le operazioni di
associazione; fornendo a questo comando la pipeline desiderata,
infatti, il sistema visualizza a video qualsiasi informazione possa
essere utile per determinare perché, per esempio, una particolare
associazione non sia andata a buon fine. Nell’utilizzo più semplice
del comando, la sintassi è questa:

Trace-Command -Name ParameterBinding -Expression <espressione> -PSHost

Il valore ParameterBinding assegnato al parametro -Name indica a Trace-


Command di visualizzare solo le informazioni relative all’associazione dei

parametri: ci sono, infatti, diversi tipi di informazioni che è possibile


visualizzare, recuperabili con il cmdlet

Get-TraceSource.

Il parametro -Expression fornisce al cmdlet l’espressione da testare,


mentre lo switch -PSHost indica che le informazioni ritornate devono
essere visualizzate direttamente nell’interfaccia host di PowerShell.
Tornando all’ultimo esempio della sezione precedente, dove si
tentava senza successo di aggregare il cmdlet Get-Date con Stop-Process,
è possibile chiedere a PowerShell di generare un rapporto
dettagliato dei tentativi di associazione effettuati. L’output del
comando è davvero prolisso, ma vale la pena osservare i numerosi
tentativi di associazione effettuati per il solo cmdlet Stop-Process:

PS C:\Users\ikmju> Trace-Command -Name ParameterBinding -Expression { Get-Date |


Stop-Process ) -PSHost
[...]
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Stop-Process]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Stop-
Process]
[...]
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Stop-
Process]
DEBUG: ParameterBinding Information: 0 : PIPELINE object TYPE =
[System.DateTime]
[...]
DEBUG: ParameterBinding Information: 0 : Parameter [InputObject] PIPELINE INPUT
ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 : BIND arg [2/11/2010 1:45:06 PM] to
parameter
[InputObject]
DEBUG: ParameterBinding Information: 0 : Binding collection parameter InputObject:
argument type [DateTime],
parameter type [System.Diagnostics.Process[]], collection type Array, element type
[System.
Diagnostics.Process], no
coerceElementType
DEBUG: ParameterBinding Information: 0 : Creating array with element type
[System.
Diagnostics.Process] and 1
elements
DEBUG: ParameterBinding Information: 0 : Argument type DateTime is not IList,
treating
this as scalar
DEBUG: ParameterBinding Information: 0 : BIND arg [2/11/2010 1:45:06 PM] to param
[InputObject] SKIPPED
DEBUG: ParameterBinding Information: 0 : Parameter [Id] PIPELINE INPUT
ValueFromPipelineByPropertyName NO COERCION
DEBUG: ParameterBinding Information: 0 : Parameter [Name] PIPELINE INPUT
ValueFromPipelineByPropertyName NO
COERCION
DEBUG: ParameterBinding Information: 0 : Parameter [InputObject] PIPELINE INPUT
ValueFromPipeline WITH COERCION
DEBUG: ParameterBinding Information: 0 : BIND arg [2/11/2010 1:45:06 PM] to
parameter
[InputObject]
DEBUG: ParameterBinding Information: 0 : COERCE arg to
[System.Diagnostics.Process[]]
DEBUG: ParameterBinding Information: 0 : Trying to convert argument value
from
System.Management.Automation.PSObject to System.Diagnostics.Process[]
DEBUG: ParameterBinding Information: 0 : ENCODING arg into collection
DEBUG: ParameterBinding Information: 0 : Binding collection parameter
InputObject:
argument type
[PSObject], parameter type [System.Diagnostics.Process[]], collection type Array,
element type
[System.Diagnostics.Process], coerceElementType
DEBUG: ParameterBinding Information: 0 : Creating array with element type
[System.
Diagnostics.Process] and
1 elements
DEBUG: ParameterBinding Information: 0 : Argument type PSObject is not IList,
treating this as scalar
DEBUG: ParameterBinding Information: 0 : COERCE arg to
[System.Diagnostics.Process]
DEBUG: ParameterBinding Information: 0 : Trying to convert argument
value from
System.Management.Automation.PSObject to System.Diagnostics.Process
DEBUG: ParameterBinding Information: 0 : CONVERT arg type to param
type using
LanguagePrimitives.ConvertTo
DEBUG: ParameterBinding Information: 0 : ERROR: ERROR: COERCE FAILED:
arg
[2/11/2010 1:45:06 PM] could
not be converted to the parameter type [System.Diagnostics.Process]
DEBUG: ParameterBinding Information: 0 : Parameter [Id] PIPELINE INPUT
ValueFromPipelineByPropertyName WITH
COERCION
DEBUG: ParameterBinding Information: 0 : Parameter [Name] PIPELINE INPUT
ValueFromPipelineByPropertyName WITH
COERCION

Si noti in particolare la riga che, al termine della quarta fase, denota


l’impossibilità nel-l’effettuare la conversione tra il tipo DateTime e il tipo
Process (ERROR: COERCE FAILED). Le informazioni ritornate da Trace-Command non

hanno solo uno scopo formativo, ma ricoprono un ruolo di notevole


importanza durante le fasi di analisi e verifica del proprio codice.
Nonostante questo cmdlet e l’annesso Get-TraceSource siano per lo più
destinati agli sviluppatori di cmdlet, la loro presenza può
rappresentare un valido strumento di supporto nei casi di più difficile
soluzione.

La pipeline in azione
Tutti i cmdlet analizzati fino a questo punto del libro supportano la
pipeline e possono impiegare questa tecnologia per creare potenti
aggregati di comandi; alcuni cmdlet, d’altra parte, sono nati quasi
esclusivamente per essere impiegati all’interno della pipeline ed è
davvero raro osservarne qualcuno al di fuori. Si tratta per lo più di
cmdlet generici, in grado di interagire con qualsiasi tipo di oggetto e
di fungere da collettori tra i blocchi della pipeline.
Data la natura di questi cmdlet, molti di essi sono in grado di
interagire agevolmente con le collezioni e gli array di oggetti,
argomento approfondito nel Capitolo 10.

Where-Object
where-object consente di creare un filtro all’interno della pipeline tra il

blocco che lo precede e quello che lo segue; questo cmdlet, infatti,


accetta un’espressione che viene valutata a ogni esecuzione del
blocco e, se questa ritorna $true, l’oggetto fornito in input viene
passato al blocco successivo; in caso contrario no. All’interno
dell’espressione fornita a Where-Object il sistema consente di fare
riferimento all’oggetto che alimenta l’esecuzione corrente
impiegando la variabile automatica $_. La semplice sintassi di Where-
Object è rappresentata, dunque, da questo schema:

Where-Object -FilterScript <script>

Nello script che segue, per esempio, il sistema recupera tutti i


processi avviati nelle ultime due ore:

PS C:\Users\ikmju> Get-Process | Where-Object { $_.StartTime -gt (Get-Date).


AddHours(-2) }

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
256 10 9412 19584 121 7.69 2636 hh
639 39 59104 57544 265 2.25 2496 iexplore
66 4 1244 5576 60 0.33 720 notepad
[...]

In questo caso, infatti, il cmdlet Get-Process fornisce a where-object tutti i


processi attivi nel sistema, uno alla volta; per ogni processo, where-
object verifica se la condizione fornita è soddisfatta o meno: la
variabile automatica $_ corrisponde al processo in fase di esame e il
risultato dell’espressione corrisponde a $true se questo è stato
avviato entro due ore a partire dall’ora attuale (ottenuta tramite Get-
Date), altrimenti $false. Se il risultato è $true, d’altra parte, l’oggetto di

input è restituito alla pipeline, che lo inoltra al blocco successivo: in


questo caso non ci sono ulteriori blocchi, quindi il processo viene
visualizzato a video.
NOT Il cmdlet Get-Date è trattato in modo approfondito nel Capitolo
A 15.

Il cmdlet Where-Object è talmente utilizzato all’interno di PowerShell che


Microsoft lo ha dotato di due alias, di cui uno davvero cortissimo:
where e il carattere punto interrogativo (?). Tra i due alias

generalmente si preferisce il secondo, che favorisce la compattezza


del codice mantensendo, tuttavia, un’ottima leggibilità. Lo script
precedente, quindi, potrebbe essere ridotto a questo:

ps | ? { $_.StartTime -gt (date).AddHours(-2) }


Sort-Object
11 cmdlet Sort-Object è in grado di ordinare la sequenza di oggetti
forniti tramite pipeline in base a una o più proprietà di questi ultimi.
La nuova sequenza, ordinata, è poi restituita alla pipeline per
l’elaborazione nei blocchi successivi. Il criterio predefinito del
comando si occupa di ordinare gli elementi dal più piccolo al più
grande, evitando di prendere in considerazione eventuali differenze
tra maiu-scole e minuscole; grazie allo switch -Descending, d’altra parte,
è possibile richiedere a Sort-Object un ordinamento inverso, mentre
impiegano lo switch -CaseSensitive si obbliga il cmdlet a impiegare un
criterio case sensitive.
Lo switch -Unique, infine, consente di evitare il ritorno alla pipeline di
oggetti duplicati. La sintassi di Sort-Object è riassumibile in questo
schema:

Sort-Object [-Descending] [-CaseSensitive] [-Unique]

I processi ritornati nell’esempio della sezione precedente, pertanto,


potrebbero es sere ordinati in base al consumo di memoria
all’interno del sistema, aggiungendo semplicemente un ulteriore
blocco alla pipeline:

Get-Process |
Where-Object { $_.StartTime -gt (Get-Date).AddHours(-2) } |
Sort-Object WorkingSet -Descending

II cmdlet Sort-Object è spesso impiegato per mezzo dell’alias sort.

Select-Object
Select-Object è un cmdlet che permette di effettuare proiezioni ed
estrazioni dei valori che provengono dalla pipeline.
Questo comando è in grado di limitare il numero di oggetti ritornati
tramite la pipeline; fornendo un valore al parametro -Skip, infatti,
Select-Object ignora il numero di elementi specificato, ritornandone

tramite pipeline la parte rimanente. I parametri -First e -Last, inoltre,


permettono di specificare il numero di elementi da recuperare,
rispettivamente, dall’inizio e dalla fine della serie di oggetti elaborati.
La sintassi usata per limitare gli oggetti ritornati è:
Select-Object [-Skip <valore>] [-First <valore>] [-Last <valore>]

Recuperando ancora una volta l’esempio del paragrafo precedente,


è dunque possibile estendere tale script in maniera tale da
recuperare il processo avviato nelle ultime due ore che occupa il
maggior quantitativo di memoria di sistema:

Get-Process |
Where-Object { $_.StartTime -gt (Get-Date)-AddHours(-2) } |
Sort-Object WorkingSet -Descending
Select-Object -First 1

Usufruendo dei parametri -Property (unico posizionale) e -


ExcludeProperty, inoltre, select-object è in grado di creare, per ciascun
oggetto fornito in input, un nuovo oggetto di un tipo creato ad hoc,
che contiene le sole proprietà specificate. In questo script, per
esempio, la shell ritorna alcuni oggetti che contengono le copie delle
proprietà Description e StartTime del tipo Process:

PS C:\Users\ikmju> Get-Process | Select-Object -Property Description, StartTime

Description StartTime
----------- ---------
Apple Mobile Device Service 2/11/2010 9:33:12 AM
AMD External Events Client Module 2/11/2010 9:33:11 AM
AMD External Events Service Module 2/11/2010 9:33:09 AM
Windows Calculator 2/11/2010 11:13:08 AM

I nuovi oggetti, d’altra parte, non hanno più alcun legame con il tipo
di oggetto di partenza; il nuovo tipo creato ad hoc, infatti, appartiene
al namespace fittizio

Selected:

PS C:\Users\ikmju> Get-Process | Select-Object -Property Description, StartTime |


Get-Member

TypeName: Selected-System-Diagnostics - Process


[...]

Tramite il parametro -ExpandProperty, inoltre, è possibile chiedere a


Select-Object di recuperare una particolare proprietà degli oggetti forniti

tramite pipeline e, nel caso questa sia una collezione di oggetti, di


ritornare, oggetto per oggetto, gli elementi che la compongono,
tramite pipeline.
La sintassi usata da select-object per eseguire una proiezione degli
oggetti forniti tramite pipeline può essere riassunta secondo questo
schema:
Select-Object [-Property <nome>] [-ExcludeProperty <nome>] [-ExpandProperty <nome>[

Facendo ancora ricorso a Get-Process e alla classe Process, l’esempio


che segue estrae nella pipeline i moduli (eseguibile e librerie) a cui il
processo più esoso di memoria fa capo:

PS C:\Users\ikmju> Get-Process
>> Sort-Object WorkingSet -Descending
>> Select-Object -First 1 -ExpandProperty Modules
>>

Size(K) ModuleName FileName


------- ---------- --------
664 iexplore.exe C:\Program Files\Internet Explorer\iexplore.exe
1264 ntdll.dll C:\Windows\SYSTEM32\ntdll.dll
848 kernel32.dll C:\Windows\system32\kernel32.dll
296 KERNELBASE.dll C:\Windows\system32\KERNELBASE.dll
640 ADVAPI32.dll C:\Windows\system32\ADVAPI32.dll
[...]

L’alias select può essere utilizzato al posto di Select-Object.

ForEach-Object
Il cmdlet ForEach-object consente di eseguire in pochissimo codice un
determinato blocco di istruzioni per ogni oggetto fornito tramite
pipeline. A parte il valore associato generalmente tramite pipeline, il
comando utilizza il parametro -process (unico posizionale) per
specificare un blocco di istruzioni da eseguire a ogni iterazione; è
prevista, inoltre, l’indicazione di un blocco di istruzioni da eseguire
prima delle iterazioni tramite il parametro -Begin e un blocco da
eseguire al termine, mediante il parametro -End. All’interno del blocco
specificato tramite -process il sistema consente il recupero dell’oggetto
fornito tramite la pipeline mediante la variabile automatica $_.
La sintassi di questo cmdlet è riassumibile secondo questo schema:

ForEach-Object -Process <script> [-Begin <script>] [-End <script>[

Al posto del nome del cmdlet è frequente utilizzare uno dei due alias:
foreach e il carattere percentuale (%). Anche in questo caso tra i due è
preferibile utilizzare il secondo, che rende il codice meno prolisso e
ha una forma che ricorda in qualche maniera l’obiettivo del
comando.
Facendo uso di ForEach-Object e della capacità di memorizzare i dati su
file fornita dal cmdlet Set-Content, approfondito nel Capitolo 20, è
possibile creare uno script che richiede la lista dei processi in
esecuzione in una macchina remota e, per ciascuno di essi,
recupera il nome e il percorso dell’eseguibile principale, salvando poi
il tutto in un file di testo in locale:

$log = ''

Get-Process -ComputerName 10-0-1-23


ForEach-Object { $log += $_.MainModule.FileName + [Environment]::NewLine ]

$log | Set-Content ProcessLog.txt

NO Le stringhe e l’elaborazione del testo sono concetti trattati nel Capitolo


TA 12.

Poiché i parametri -Begin e -End lo permettono, infine, è possibile


ridurre ulteriormente lo script precedente, specificando le istruzioni di
inizializzazione della variabile $log tramite il primo e utilizzando il
secondo per ritornare alla pipeline la variabile stessa, utilizzata poi
da set-content per memorizzare il file:

Get-Process -ComputerName -
ForEach-Object { $log += $_.MainModule-FileName + [Environment]::NewLine
} -Begin { $log = '' } -End { $log }
Set-Content ProcessLog.txt

Nonostante script come questi siano senz’altro corretti e sintetici, è


di norma preferibile utilizzare la prima variante e astenersi dal
concentrare troppe funzionalità all’interno di ForEacn-object, pena una
diminuzione della leggibilità del codice.

Group-Object
Il cmdlet Group-object, infine, consente di raggruppare gli oggetti che
provengono dalla pipeline in base a una o più proprietà; una volta
effettuata l’elaborazione, il comando ritorna alla pipeline un oggetto
che rappresenta il gruppo per ogni insieme trovato. Nell’utilizzo di
base, ognuno di questi gruppi è un’istanza di Grouplnfo, un tipo che
permette di recuperare la collezione degli elementi che formano il
gruppo (tramite la proprietà Group), il numero di tali elementi (tramite la
proprietà count), i valori delle proprietà che individuano il gruppo
(grazie alla proprietà values) e la rappresentazione testuale di questi
ultimi (mediante la proprietà Name). La sintassi di base è
rappresentabile da questo schema:

Group-Object -Property <nome> [-CaseSensitive]

Il cmdlet si attende dei valori dalla pipeline e un nome di proprietà (o


più di uno, separati dalla virgola) tramite cui effettuare il
raggruppamento. Anche in questo caso esiste uno switch (-
CaseSensitive) in grado di abilitare i confronti per la determinazione dei

gruppi in modalità case sensitive. Il parametro -Property, inoltre, è


l’unico posizionale.
Lo script che segue utilizza Group-object per raggruppare per
estensione l’elenco degli elementi della cartella C:\Windows:

PS C:\Users\ikmju> Get-Childltem C:\Windows | Group-Object Extension

Count Name Group


----- ---- -----
61 {addins, AppCompat, AppPatch, assembly...}
1 .NET {Microsoft.NET}
20 .log {accessdll.log, apptune.log, avmadd32.log, avmsetup.log...}
20 .exe {amcap.exe, bfsvc.exe, explorer.exe, FixCamera.exe...}
[...]

Il raggruppamento è possibile grazie all’esistenza della proprietà


Extension nelle istanze del tipo Fiieinfo e del tipo Directoryinfo, ritornate

dalla chiamata a Get-ChildItem. I gruppi ritornati da Group-object, pertanto,


sono tanti quante sono le estensioni presenti nella cartella richiesta.

NO Il cmdlet Group-Object è in grado di rappresentare i gruppi individuati tramite


TA strutture chiamate array associativi, cui è dedicato ampio spazio nel Capitolo 11.
L’output

Un’analisi puntuale sulle diverse tipologie di


visualizzazione supportate dalla shell e sui cmdlet di
output, in grado di consumare il prodotto della
pipeline.

Come in una potente catena di montaggio, l’esecuzione della


pipeline all’interno di PowerShell può produrre, come si è visto nel
capitolo precedente, alcuni risultati per ciascun blocco, sotto forma di
oggetti del framework Microsoft .NET. Gli eventuali risultati prodotti
dall’ultimo blocco sono quindi ritornati alla shell, che si occupa di
crearne una rappresentazione testuale e di visualizzarla a video.
Tuttavia, quest’ultima attività è variabile e gli oggetti prodotti dalla
pipeline possono essere consumati in modalità diverse, di cui la
rappresentazione testuale a video è semplicemente quella
predefinita della shell.
In questo capitolo è analizzato il meccanismo che PowerShell
utilizza per generare del testo a partire da un oggetto e sono
illustrate le tecniche per personalizzarlo; sono poi discusse tutte le
alternative a questa modalità di consumazione degli oggetti della
pipeline, per poi passare a un approfondimento sulle capacità di
acquisizione dell’output testuale fornito dagli eseguibili Windows con
interfaccia a console.

La visualizzazione
La peculiarità principale di PowerShell, come si è potuto apprendere
nel corso del libro, è quella di essere una shell orientata agli oggetti;
questa caratteristica, naturalmente, implica che l’esecuzione di
qualsiasi espressione all’interno di questa piattaforma genera, infine,
uno o più oggetti del framework Microsoft.NET.
Sia l’interfaccia a console sia l’ISE di PowerShell e quasi tutte le
interfacce alternative non prodotte da Microsoft, d’altra parte, sono in
grado di visualizzare l’output generato dalla shell impiegando solo
del testo; non sarebbe possibile, infatti, riuscire a prevedere il
funzionamento e la complessità degli oggetti impiegati e utilizzare, di
conseguenza, rappresentazioni differenti per ciascuna tipologia.
Raffigurare un numero o una data (o, banalmente, una stringa) può
sembrare un’attività semplice, ma cercare di “visualizzare” un
oggetto astratto come un processo, per esempio, porterebbe a
risultati parziali o inconcludenti.

Come anticipato nel Capitolo 1, nella community open source CodePlex è


presente un’interfaccia alternativa per PowerShell chiamata PoshConsole
NO (http://poshconsole.codeplex.com), in grado di visualizzare oggetti grafici (come le
TA miniature dei file di immagine) direttamente all’interno della console. Nonostante
l’originalità dell’idea, la rappresentazione visuale di tipi complessi è un’attività che
non può essere estesa facilmente a qualsiasi oggetto.

Per generare rappresentazione testuali soddisfacenti a partire da un


oggetto, PowerShell impiega diverse tecniche, note come tecniche di
formattazione, a seconda della tipologia di oggetto e della
numerosità delle eventuali proprietà che questo espone. Prima di
analizzare come la shell renda automatica questa attività è
indispensabile soffermarsi ed esaminare i vari processi che la
compongono.

Visualizzare i tipi primitivi


La shell riconosce nativamente alcuni tipi fondamentali del
framework Microsoft .NET ed è in grado di generarne in autonomia
delle rappresentazioni testuali, appoggiandosi principalmente al
metodo ToString(),illustrato nel Capitolo 3, la cui disponibilità è sempre
garantita per ogni oggetto. Tra questi tipi, chiamati in gergo tipi
primitivi, figurano tutti i numerici (Byte, Intlö, Int32, Single, Double), le
stringhe (string) e le collezioni di oggetti (approfondite nel Capitolo
10).
Ecco perché, dunque, eseguendo delle espressioni dove il risultato è
un tipo primitivo, la shell visualizza a video, di rimando, il valore
immesso convertito in testo:

PS C:\Users\ikmju> 123 - 456


-333

PS C:\Users\ikmju> 'powershell.it'
powershell.it

PS C:\Users\ikmju> ps | % { $_.Name } I select -first |


AcroRd32

D’altra parte, il risultato ottenuto sarebbe stato equivalente a quello


ritornato chiamando manualmente il metodo ToString() per ciascuna
espressione:

PS C:\Users\ikmju> (123 - 456).ToString()


-333

PS C:\Users\ikmju> 'powershell - it'.ToString()


powershell - it

PS C:\Users\ikmju> (ps | % { $_.Name } I select -first 1).ToString()


AcroRd32

Si tratta, probabilmente, del trattamento che chiunque si


aspetterebbe per questo tipo di dati così frequentemente utilizzati.

Visualizzare i tipi complessi


Tutti i tipi che PowerShell non riconosce come primitivi sono gestiti
da un raffinato sistema di visualizzazione, che permette di
rappresentare a video gli oggetti secondo tre diverse modalità, su
richiesta dell’utente:
• come tabella;
• come tabella estesa;
• come lista.
La visualizzazione a tabella recupera le proprietà esposte
dall’oggetto che si desidera rappresentare a video e produce una
tabella di testo, dove ogni colonna rappresenta una proprietà e ogni
riga un’istanza di oggetto.
La visualizzazione a tabella estesa consente di ottimizzare lo
spazio disponibile per visualizzare le informazioni provenienti,
tipicamente, da una collezione di oggetti (come una lista di file e
cartelle). Raffigura, infatti, un’unica proprietà tra quelle esposte,
utilizzando più righe e più colonne per ridurre lo spazio richiesto a
video. La visualizzazione a lista, infine, recupera le proprietà
esposte dall’oggetto che si desidera rappresentare a video e da
queste genera una o più righe: ciascuna presenta una sorta di
etichetta iniziale che denota il nome della proprietà, cui segue il
rispettivo valore.
Ognuna delle visualizzazioni descritte è attivabile impiegando uno
dei rispettivi cmdlet di formattazione descritti nelle prossime sezioni,
distinguibili all’interno della shell per via del verbo Format.

Esiste una quarta visualizzazione, di tipo ibrido, la quale genera un testo che
racchiude le informazioni secondo uno schema che ricorda quello usato per
serializzare i dati tramite il protocollo JSON (probabilmente noto al lettore con
NO
esperienze di sviluppo in ambito web), dove il sistema si occupa di recuperare
TA
quante più informazioni possibili per ogni proprietà. L’impiego di questa modalità di
visualizzazione è assai raro, pertanto il seguito del libro non la prende in
considerazione.

Format-Table
Il cmdlet Format-Table è sicuramente il più utilizzato nella categoria dei
comandi di formattazione e consente di generare una
visualizzazione a tabella per gli elementi che gli vengono forniti.
Dispone di un parametro -InputObject cui è possibile fornire l’oggetto
da visualizzare, anche se è di norma preferibile sfruttare la pipeline;
il parametro -Property (unico posizionale), poi, consente di indicare i
nomi delle proprietà dell’oggetto di input da visualizzare, separandoli
tramite il carattere virgola (,). La larghezza della tabella generata è
determinata in base al numero delle colonne ma, specificando lo
switch -AutoSize, il cmdlet è in grado di adattarla automaticamente alla
lunghezza massima dei testi che ciascuna cella è chiamata a
contenere.
Quando un testo relativo a un valore di una proprietà è troppo lungo,
poi, viene automaticamente troncato e specificando lo switch -Wrap è
possibile fare in modo che le righe della tabella possano occupare
più di una riga fisica della console testuale. Lo switch -HideTableHeaders,
infine, permette di disattivare la visualizzazione delle intestazioni di
colonna, di default visibili. La sintassi di base per questo cmdlet è:

... | Format-Table <Proprietà1, Proprietà2, ...> [-AutoSize] [-Wrap] [-


HideTableHeaders]

Per ottenere una vista in formato tabella con le informazioni sui nomi
e sullo stato dei servizi attivi, per esempio, è quindi possibile
sfruttare questo script:

PS C:\Users\ikmju> Get-Service |
>> ? { $_.Status -eq 'Running' } |
>> Format-Table Name,Status
>>

Name Status
---- ------
AMD External Events Utility Running
AppHostSvc Running
Apple Mobile Device Running
[...]

Com’è possibile notare, d’altra parte, la larghezza del testo ottenuto


è troppo elevata. Utilizzando lo switch -AutoSize è possibile rimediare
al problema e avere un output più compatto, largo quanto basta:

PS C:\Users\ikmju> Get-Service |
>> ? { $_.Status -eq 'Running' } |
>> Format-Table Name,Status -AutoSize
>>

Name Status
---- ------
AMD External Events Utility Running
AppHostSvc Running
Apple Mobile Device Running
AudioEndpointBuilder Running
[...]

Utilizzando lo switch -AutoSize il cmdlet è costretto ad analizzare in anticipo tutti


gli oggetti provenienti dalla pipeline: se prima era possibile generare una riga di
testo per ogni elemento recuperato, infatti, in questo caso è necessario attendere
NO
affinché la shell determini le dimensioni ottimali per ciascuna colonna. Le
TA
performance, in questa circostanza, potrebbero peggiorare a causa della
necessità di mantenere in memoria tutti i risultati della pipeline, mentre a video
non apparirebbe nulla fino al termine dell’elaborazione.

Il cmdlet Format-Table è in grado, inoltre, di raggruppare gli oggetti


forniti in base al valore di una particolare proprietà, indicata
attraverso il parametro -GroupBy: il risultato consiste nella
visualizzazione di più tabelle, una per ogni valore distinto individuato.
Il vincolo per l’impiego di questa tecnica è quello di fornire a Format-
Table una collezione di oggetti precedentemente ordinati in base alla

proprietà indicata ai fini del raggruppamento; se questo non avviene,


infatti, il cmdlet raggruppa gli oggetti per valori contigui all’interno
della sequenza.
Desiderando, per esempio, visualizzare sotto forma di tabella i
servizi Windows del sistema locale, raggruppati per stato, è possibile
eseguire questo blocco:

PS C:\Users\ikmju> Get-Service |
>> Sort Status |
>> Format-Table Name, Status -GroupBy Status -AutoSize
>>

Status: Stopped

Name Status
---- ------
RpcLocator Stopped
SCardSvr Stopped
SCPolicySvc Stopped
ReportServer Stopped
[...]

Status: Running

Name Status
---- ------
wcncsvc Running
Netman Running
CertPropSvc Running
BITS Running
W3SVC Running
[...]

Come si può notare, in questo caso lo switch -AutoSize è applicato a


ogni singola tabella generata.
Il cmdlet Format-Table è utilizzato frequentemente per mezzo dell’alias
predefinito ft.

Format-Wide
Questo cmdlet visualizza tramite una tabella estesa gli oggetti che gli
vengono forniti; si tratta, tipicamente, di una valida alternativa
all’impiego di Format-Table quando ciò che si desidera rappresentare a
video consiste in un’unica proprietà di una serie di oggetti.
Format-Wide condivide con il cmdlet precedente la maggior parte dei

parametri: -InputObject consente di specificare l’oggetto da


visualizzare, ma anche in questo caso è preferibile l’impiego della
pipeline. I parametri -AutoSize e -GroupBy hanno la stessa funzione
osservabile per Format-Table, anche se la resa, a video, è ovviamente
adattata alla tipologia di visualizzazione a tabella estesa. Per -
AutoSize, tra l’altro, rimangono valide le considerazioni in termini di

performance fatte per il cmdlet precedente.


Il parametro -Property (unico posizionale), infine, si contraddistingue
dal precedente perché ammette l’indicazione di un’una sola anziché
di diverse proprietà. La sintassi di base per questo cmdlet è la
seguente:

... | Format-Wide <Proprietà> [-AutoSize]

Volendo, quindi, visualizzare a video la lista dei servizi Windows in


esecuzione nella macchina locale, è possibile avvalersi di Format-Wide
per ottenerne la rappresentazione più compatta possibile.
Nell’esempio che segue, viene recuperata una visualizzazione in
tabella estesa in base al nome di ciascun servizio:

PS C:\Users\ikmju> Get-Service |
>> ? { $_.Status -eq 'Running' }
>> Format-Wide Name -AutoSize
>>

1-vmsrvc AeLookupSvc AppHostSvc Appinfo BFE BITS


CertPropSvc CryptSvc DcomLaunch Dhc Dnscache DPS
EventLog EventSystem FontCache3.0.0.0 gpsvc IKEEXT
iphlpsvc
[...]

Come anticipato, anche Format-Wide consente di indicare una proprietà


in base alla quale effettuare un raggruppamento dei risultati. Anche
in questo caso è necessario fornire al cmdlet una sequenza di
oggetti ordinati in base alla proprietà indicata per portare a termine il
raggruppamento.
Modificando l’esempio precedente, quindi, è possibile ottenere la
visualizzazione in formato tabella estesa dei servizi Windows del
computer locale, raggruppati per stato:

PS C:\Users\ikmju> Get-Service |
>> Sort Status |
>> Format-Wide Name -AutoSize -GroupBy Status
>>

Status: Stopped

RpcLocator SCardSvr SCPolicySvc


ReportServer RasMan RemoteAccess
RemoteRegistry SDRSVC sppsvc
sppuinotify SQLBrowser SNMPTRAP
seclogon SensrSvc SharedAccess
[...]

Status: Running

wcncsvc Netman CertPropSvc BITS


W3SVC WAS Browser Bonjour Service
CscService TVersityMediaServer TrkWks Themes
DcomLaunch LanmanWorkstation UxSms upnphost
[...]

Come per Format-Table, anche in questo caso lo switch -AutoSize è


applicato a ogni
singola tabella generata.
Il cmdlet Format-Wide è utilizzato frequentemente mediante l’alias
predefinito fw.

Format-List
Il cmdlet Format-List permette di rappresentare un oggetto come una
lista di proprietà dello stesso, dove ogni riga corrisponde a una
coppia proprietà–valore; poiché lo spazio adibito a contenere
ciascun valore è abbastanza elevato, in rapporto alla larghezza in
caratteri della finestra della console, si corre meno il rischio che tali
valori siano troncati. Questa visualizzazione, che assomiglia per certi
versi a una rotazione della visualizzazione a tabella, può in
determinati casi diventare più agevole. Come i due cmdlet
precedenti, anche Format-List consente di indicare l’oggetto desiderato
tramite il parametro -InputObject: anche in questo caso, tuttavia, è
generalmente preferibile impiegare la pipeline. Il solito parametro -
Property, poi, consente di indicare le proprietà da visualizzare,

separate dal carattere virgola (,); il parametro -GroupBy, infine,


consente di specificare la proprietà secondo cui raggruppare i
risultati forniti. Anche in questo caso, il cmdlet è limitato a dei
raggruppamenti contigui, quindi è necessario fornire come input una
sequenza ordinata, in base alla proprietà del raggruppamento. La
sintassi di base del cmdlet è questa:

... | Format-List <Proprietà1>, <Proprietà2>, ...

Recuperare la visualizzazione lista dei servizi Windows del computer


locale, quindi, è semplice quanto eseguire questo blocco:

PS C:\Users\ikmju> Get-Service | Format-List Name, Status

Name : AeLookupSvc
Status : Stopped
Name : ALG
Status : Stopped
Name : AMD External Events Utility
Status : Running
[...]

Com’è possibile notare, il cmdlet inserisce una riga vuota tra le


proprietà di un oggetto e il successivo.
Il raggruppamento tramite -GroupBy, infine, agisce in maniera simile
agli altri cmdlet di formattazione:

PS C:\Users\ikmju> Get-Service |
>> Sort-Object Status |
>> Format-List Name -GroupBy Status
>>

Status: Stopped

Name : ReportServer
Name : RpcLocator

Name : SCardSvr
[...]

Status: Running

Name : Bonjour Service

Name : CertPropSvc

Name : LanmanWorkstation
[...]

È possibile impiegare il cmdlet Format-List anche mediante l’alias


predefinito fl.

Viste predefinite
Nell’ottica di rendere il sistema di visualizzazione degli oggetti il più
semplice ed efficiente possibile, PowerShell si avvale di una serie di
viste predefinite, che consentono di esplicitare sia il tipo di
visualizzazione desiderato (tra quelli illustrati in precedenza) sia le
proprietà interessate, per ciascun tipo di oggetto del framework
Microsoft.NET.
Ogni vista predefinita ha un nome, che si può utilizzare con uno
qualsiasi dei cmdlet di formattazione: Format-Table, Format-Wide e Format-
List, infatti, dispongono di un ulteriore parametro -view, tramite il quale

è possibile indicare la vista predefinita da usare per visualizzare a


video l’output. Il parametro -view è utilizzabile come alternativa a -
Property e l’impiego di entrambi genera un errore.

Le viste predefinite sono descritte tramite strutture in formato XML,


raggruppate all’interno di alcuni file distinguibili per l’estensione
.format.ps1xml, posizionati nella cartella che contiene l’eseguibile di

PowerShell e caricati in memoria all’avvio di quest’ultimo.


Posto che la cartella menzionata è recuperabile grazie alla variabile
automatica $PSHOME, la lista dei file di viste predefinite è recuperabile
facilmente:

PS C:\Users\Ikmju> Get-ChildItem terri $PSHOME\* - format.pslxml

Directory: C:\Windows\System32\WindowsPowerShell\v1.0
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 16.7.2009 19:21 27338 Certificate.format.ps1xml
-a--- 16.7.2009 19:21 27106 Diagnostics.Format.ps1xml
-a--- 16.7.2009 19:21 72654 DotNetTypes.format.ps1xml
-a--- 16.7.2009 19:21 24857 FileSystem.format.ps1xml
-a--- 16.7.2009 19:21 257847 Help.format.ps1xml
-a--- 16.7.2009 19:21 89703 PowerShellCore.format.ps1xml
-a--- 16.7.2009 19:21 18612 PowerShellTrace.format.ps1xml
-a--- 16.7.2009 19:21 20120 Registry.format.ps1xml
-a--- 16.7.2009 19:21 24498 WSMan.Format.ps1xml

All’interno di questi file, ogni vista predefinita è rappresentata da un


nodo contenitore view, cui fanno capo obbligatoriamente un nodo Name,
che indica il nome dell’elemento, e un nodo ViewSelectedBy, che
contiene, tipicamente, il riferimento al tipo del framework Microsoft
.NET supportato dalla vista. All’interno del nodo view è poi presente
un elemento che identifica il tipo di visualizzazione della vista (a
tabella, a tabella estesa oppure a lista), al cui interno sono esplicitate
le proprietà da utilizzare. Un successivo elemento, GroupBy, se
aggiunto al nodo view consente di raggruppare i risultati secondo una
o più proprietà.
La struttura di ogni vista predefinita è schematizzabile così:

<View>
<Name>NomeVistaPredefinita</Name>
<ViewSelectedBy>
<TypeName>Namespace.NomeTipo</TypeName>
</ViewSelectedBy>
[<TableControl>...</TableControl>]
[<WideControl>...</WideControl>]
[<ListControl>...</ListControl>]
[<GroupBy>...</GroupBy>]
</View>

Il tipo System.Diagnostic.Process, per esempio, dispone di quattro diverse


viste predefinite all’interno del file DotNetTypes.format.ps1xml; la prima
vista fa riferimento alla visualizzazione a tabella (TableControl) e
include diverse proprietà (nell’estratto compare solo la prima):

<View>
<Name>process</Name>
<ViewSelectedBy>
<TypeName>System.Diagnostics.Process</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Handles</Label>
<Width>7</Width>
<Alignment>right</Alignment>
</TableColumnHeader> - - -
[...]
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>HandleCount</PropertyName>
</TableColumnItem>
[...]
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>

La seconda vista predefinita, chiamata Priority, è incentrata sulla


classe di priorità del processo (PriorityClass) e impiega una
visualizzazione a tabella per mostrare alcuni dati indicativi del
processo (nell’estratto ridotti a uno), raggruppati per priorità:

<View>
<Name>Priority</Name>
<ViewSelectedBy>
<TypeName>System.Diagnostics.Process</TypeName>
</ViewSelectedBy>
<GroupBy>
<PropertyName>PriorityClass</PropertyName>
<Label>PriorityClass</Label>
</GroupBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>20</Width>
</TableColumnHeader>
[...]
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>ProcessName</PropertyName>
</TableColumnItem>
[...]
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>

La terza vista è chiamata StartTime e presenta un raggruppamento dei


processi in base al giorno di avvio degli stessi. Anche in questo caso
la vista predefinita ha una forma tabellare:
<View>
<Name>StartTime</Name>
<ViewSelectedBy>
<TypeName>System.Diagnostics.Process</TypeName>
</ViewSelectedBy>
<GroupBy>
<ScriptBlock>$_-StartTime -ToShortDateString()</ScriptBlock>
<Label>StartTime -ToShortDateString()</Label>
</GroupBy>
<TableControl>
[...]
</TableControl>
</View>

La quarta e ultima vista predefinita, infine, visualizza in formato


tabella estesa il nome

<View>
<Name>process</Name>
<ViewSelectedBy>
<TypeName>System.Diagnostics -Process</TypeName>
</ViewSelectedBy>
<WideControl>
<WideEntries>
<WideEntry>
<WideItem>
<PropertyName>ProcessName</PropertyName>
</WideItem>
</WideEntry>
</WideEntries>
</WideControl>
</View>

Come anticipato, utilizzando uno dei cmdlet di formattazione e


specificando tramite il parametro -view una delle viste predefinite
appena recuperate si usufruisce di questi modelli preconfezionati e
pronti all’uso. Ogni vista è utilizzabile unicamente tramite il cmdlet
che ne ammette la rispettiva visualizzazione: le viste che fanno uso
di TableControl sono utilizzabili unicamente tramite Format-Table, le viste
con widecontrol solo con Format-wide mentre le viste che impiegano
Listcontrol solo mediante Format-List. Negli altri casi viene generato un

errore. Volendo fare uso della vista predefinita priority, per esempio,
è possibile eseguire questo blocco di codice:

PS C:\Users\ikmju> Get-Process |
>> Sort PriorityClass
>> Format-Table -View Priority
>>

ProcessName Id HandleCount WorkingSet


----------- -- ----------- ----------
spoolsv 1464 356 4669440
SnagPriv 3156 72 24576
sqlwriter 468 82 1138688
[...]

PriorityClass: High
ProcessName Id HandleCount WorkingSet
----------- -- ----------- ----------
winlogon 668 113 1048576
wininit 528 79 745472
dwm 1664 130 44453888
[...]

Quando si analizza la semplicità d’uso del meccanismo di


formattazione degli oggetti di PowerShell, le viste predefinite
ricoprono un ulteriore ruolo di primo piano: alcune di esse, infatti,
sono impiegate dai cmdlet di visualizzazione quando non vengono
fornite né alcuna proprietà (tramite il parametro -property) né alcuna
vista predefinita (tramite il parametro -view).
In tal caso, infatti, questi cmdlet analizzano il tipo di oggetto da
visualizzare e recuperano la prima vista disponibile all’interno dei
file .format.ps1xml, in base al tipo recuperato e alla modalità di
visualizzazione di pertinenza del cmdlet in questione. Eseguendo
Format-Table a fronte di una sequenza di processi, per esempio, il

cmdlet recupera la prima vista predefinita che soddisfa il requisito di


associazione al tipo system.Diagnostics.process e la presenza di un nodo
TableControl (correlato alla modalità di visualizzazione a tabella, di

pertinenza di Format-Table): il primo elemento di questo tipo


corrisponde alla prima vista predefinita, illustrata poc’anzi, il cui
nome è semplicemente process. Le due righe che seguono, pertanto,
generano risultati identici:

PS C:\Users\ikmju> ps explorer | ft -View process

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
1192 40 75824 37528 345 78.06 2580 explorer

PS C:\Users\ikmju> ps explorer | ft

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
1192 40 75824 37528 345 78.06 2580 explorer
D’altro canto, quando si visualizza una sequenza di processi tramite
il cmdlet Format-Wide senza specificare né proprietà né viste, questo
recupera la prima vista predefinita per il tipo system.Diagnostics. process e
la modalità tabella estesa, ovvero la quarta vista pre-definita
illustrata in precedenza per questo tipo; si spiega, in questo modo,
perché Format-wide impieghi automaticamente la proprietà ProcessName in
questa circostanza:

PS C:\Users\ikmju> ps explorer | fw
explorer

Proprietà predefinite
Il lettore più accorto potrebbe aver notato che tra le viste predefinite
illustrate in precedenza per system.Diagnostics.process non ne appare
nessuna associata alla visualizzazione a lista (Listcontrol);
richiamando Format-List, d’altra parte, si visualizza solo una quota
parte delle proprietà dei processi e non tutte, come forse ci si
potrebbe aspettare.

PS C:\Users\ikmju> ps explorer | fi

Id : 652
Handles : 477
CPU :12.4176796
Name : explorer

Nel caso siano richiamati senza specificare le proprietà dell’oggetto


da visualizzare e non esista alcuna vista predefinita associata al tipo
in questione, infatti, i cmdlet di formattazione si attivano per
verificare se il tipo stesso espone delle preferenze in merito a quali
proprietà visualizzare in questa circostanza. Per recuperare queste
preferenze il sistema si appoggia all’Extended Type System (ETS)
di PowerShell, introdotto nel Capitolo 3, e verifica che il tipo da
visualizzare sia dotato della proprietà psstandardMembers: se presente, si
tratta di una collezione di riferimenti alle proprietà del tipo stesso,
che la shell può utilizzare in questa occasione. Tornando al tipo
system.Diagnostics.process, per esempio, quando il sistema rileva che non
esiste alcuna vista predefinita per la visualizzazione a lista, tenta di
recuperare l’eventuale valore della proprietà psstandardMembers, che per
questo tipo è definita tramite Extended Type System nel file
types.ps1xml; l’estratto che segue ne illustra la definizione e chiarisce
perché Format-List visualizzi, in maniera predefinita, le proprietà Id,
Handles, CPU e Name:

<Type>
<Name>System.Diagnostics -Process</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Id</Name>
<Name>Handles</Name>
<Name>CPU</Name>
<Name>Name</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</MemberSet>
[...]
</Members>
</Type>

Questo tipo di informazione è recuperabile facilmente anche tramite


la piattaforma di scripting di PowerShell; acquisendo una qualsiasi
istanza del tipo desiderato, infatti, è possibile ricavarne le proprietà
predefinite facendo direttamente riferimento alla proprietà
PSStandardMembers:

PS C:\Users\ikmju> $process- PSStandardMembers.DefaultDisplayPropertySet.


ReferencedPropertyNames
Id
Handles
CPU
Name

Se presente, la proprietà PSStandardMembers è nascosta dall’output standard di


NO
Get-Member: in questo caso è necessario utilizzare lo switch -Force per
TA
visualizzarne la presenza.

L’ultimo caso
Quando il tipo da visualizzare non è dotato di proprietà predefinite né
esiste alcuna vista predefinita per la visualizzazione desiderata, i
cmdlet di formattazione sopperiscono alla mancanza dell’indicazione
delle proprietà da visualizzare in maniera differente:
• Format-Table visualizza le prime dieci proprietà dell’oggetto;
• Format-Wide impiega la prima proprietà dell’oggetto;
• Format-List utilizza tutte le proprietà dell’oggetto.
Una volta compreso il meccanismo di formattazione degli oggetti è
finalmente possibile fare un passo indietro e studiare come questo
processo sia correlato alla pipeline e alla consumazione degli
oggetti.

Consumare l’output
Come anticipato all’inizio del capitolo, la visualizzazione all’interno
dell’interfaccia dell’host non è l’unica modalità di consumazione degli
oggetti provenienti dalla pipeline; PowerShell dispone, infatti, di una
serie di cmdlet creati per sfruttarne l’output. Questi cmdlet, che per
convenzione sono dotati del verbo out, se inseriti all’interno della
pipeline consumano tutti gli oggetti generati dai blocchi che li
precedono, per acquisirne una rappresentazione testuale da inviare
alla periferica di output di competenza per ciascun comando.
Poiché i cmdlet di output non emettono, a loro volta, alcun oggetto
all’interno della pipeline, eventuali blocchi che li succedono sono
ignorati dalla shell: di norma, pertanto, sono sempre posti come
ultimo blocco delle pipeline.
Se si apprestano a consumare delle stringhe o collezioni di stringhe,
i cmdlet di output non elaborano ulteriormente questi oggetti, ma si
limitano a inviarli alla periferica di output di competenza; in caso
contrario, la shell impiega preventivamente uno dei cmdlet di
formattazione per generare la rappresentazione testuale dell’output
della pipeline. Il cmdlet di formattazione utilizzato è determinato in
base al primo oggetto emesso dalla pipeline: se questo contiene più
di cinque proprietà viene usato Format-List, altrimenti il cmdlet
prescelto è Format-Table. In entrambi i casi, in ogni modo, i cmdlet sono
richiamati senza esplicitare né proprietà né viste predefinite e la shell
si avvale, dunque, delle tecniche esposte nei paragrafi precedenti.
Out-Host
Il cmdlet Out-Host invia la rappresentazione testuale dell’output della
pipeline all’interfaccia host di PowerShell, per la visualizzazione.
Si tratta senz’altro del cmdlet più utilizzato all’interno di PowerShell,
perché il sistema lo impiega in maniera predefinita per consumare
l’output dei comandi. Al termine della pipeline, infatti, se la shell
rileva che sono stati emessi degli oggetti ma nessun blocco li ha
consumati, esegue out-Host passandoglieli. Tutte le pipeline che
emettono oggetti, ma non contengono cmdlet di output, dunque,
utilizzano in maniera trasparente questo cmdlet. I due blocchi che
seguono, di conseguenza, sono identici:

PS C:\Users\ikmju> ps I select -First 3

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
103 4 1212 3352 32 0.05 1760
AppleMobileDeviceService
170 5 1444 4296 49 0.14 1268 atieclxx
122 4 864 2712 27 0.02 916 atiesrxx

PS C:\Users\ikmju> ps I select -First 3 | Out-Host

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
103 4 1212 3352 32 0.05 1760
AppleMobileDeviceService
170 5 1444 4296 49 0.14 1268 atieclxx
122 4 864 2712 27 0.02 916 atiesrxx

Poiché i cmdlet di output si avvalgono in maniera trasparente dei


cmdlet di formattazione, se necessario, è possibile osservare come,
nel caso di system.Diagnostics. Process, la visualizzazione predefinita sia a
tabella e l’output del comando che segue sia dunque identico ai due
precedenti:

PS C:\Users\ikmju> ps I select -first 3 I Format-Table I Out-Host

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
103 4 1212 3352 32 0.05 1760
AppleMobileDeviceService
170 5 1444 4296 49 0.14 1268 atieclxx
122 4 864 2712 27 0.02 916 atiesrxx

In quest’ultimo caso, infatti, Out-Host gestisce l’output di Format-Table, che


corrisponde a una collezione di stringhe.
out-Host consente di visualizzare l’output in modalità paginata
utilizzando lo switch -Paging, alla stregua di quanto è possibile con il
comando more, presente nelle precedenti shell Microsoft e nella
maggior parte di quelle di derivazione *nix.

PS C:\Users\ikmju> Get-Command | Out-Host -Paging

CommandType Name Definition


----------- ---- ----------
Alias % ForEach-Object
Alias ? Where-Object
[...]
Alias compare Compare-Object
<SPACE> next page; <CR> next line; Q quit

La dimensione delle pagine visualizzate è determinata dall’host,


anche se sembra attestarsi attorno al valore di 25 righe per pagina
nella maggior parte delle interfacce in commercio. La sintassi del
comando, il cui alias predefinito è oh, è la seguente:
... | Out-Host [-Paging]

Out-GrìdVìew
Introdotto con la versione 2.0 di PowerShell, il potente cmdlet out-
Gridview consente di rappresentare a video, all’interno di una griglia

interattiva, gli oggetti emessi dalla pipeline. Unico nel suo genere,
questo comando fornisce un’utilissima alternativa alla
visualizzazione testuale perché la griglia prodotta consente di
manipolare molto più agevolmente l’output.
Il cmdlet, richiamabile anche tramite l’alias predefinito ogv, dispone di
un unico parametro -Title, tramite il quale è possibile impostare il
testo del titolo della griglia.
La sintassi è rappresentata da questo semplice schema:
... | Out-GridView [-Title <Titolo>]

Ecco come è possibile, per esempio, visualizzare la lista dei servizi


Windows installati nella macchina locale all’interno della griglia
interattiva di PowerShell:
PS C:\Users\ikmju> Get-Service | Out-GridView
Figura 9.1 - L’output di Get-Service, visualizzato da Out-GridView.

La griglia interattiva prodotta dal comando dispone di una serie di


interessanti funzionalità. Cliccando sulle intestazioni delle colonne,
per esempio, è possibile ordinare le righe visualizzate per la colonna
desiderata. Selezionando una qualsiasi riga della griglia è poi
possibile copiarne il contenuto negli appunti usufruendo delle
combinazioni standard Ctrl+C oppure Ctrl+Ins: in questo modo il
sistema copia i valori delle colonne in formato testuale, separandoli
tramite tabulazioni, e così facendo ne consente il riutilizzo all’interno
di qualsiasi foglio di calcolo. La griglia dispone poi di due avanzati
sistemi di filtro interattivo: il primo è attivabile grazie alla casella di
testo posta all’inizio della finestra e visualizza, a mano a mano che si
digita, solo le righe che contengono il testo immesso.
Figura 9.2 - Il filtro interattivo di Out-GridView in azione.

Il secondo filtro, invece, agisce in base a una o più colonne e


consente di specificare, oltre al valore, l’eventuale criterio di
confronto.

Figura 9.3 - La selezione delle colonne nel filtro avanzato di Out-GridView.

Figura 9.4 - L’impostazione del filtro avanzato di Out-GridView.


Infine, tramite il menu contestuale dell’intestazione della griglia è
possibile attivare una finestra di dialogo che permette di includere o
escludere dalla visualizzazione le colonne desiderate (o
indesiderate).

Figura 9.5 - La selezione delle colonne da visualizzare con Out-GridView.

Per poter eseguire questo cmdlet è necessario che il sistema


operativo sia dotato di Microsoft .NET v3.5 SP1 o superiore, perché
la griglia interattiva si basa sulle funzionalità aggiunte a Windows
Presentation Foundation (la porzione del framework Microsoft .NET
destinata agli sviluppatori di interfacce desktop di nuova
generazione) a partire da tale versione. Il cmdlet Out-GridView è
richiamabile anche mediante l’alias predefinito ogv.

Out-String
Questo cmdlet è in grado di “catturare” l’output di una pipeline
all’interno di una stringa. Di default, il comando produce un’unica
stringa, ma è possibile specificare lo switch -Stream per ottenere una
collezione di stringhe, una per ciascuna riga prodotta.
Il parametro -Width consente, infine, di impostare la larghezza delle
righe elaborate dal comando, che per l’host di PowerShell è pari a
80 (la finestra a console, infatti, ha storicamente una dimensione di
80 caratteri in larghezza per 25 in altezza). La sintassi del comando
è:
... | Out-String [-Width <Larghezza>]

anche se, tipicamente, questo cmdlet si utilizza per impostare il


valore di una stringa:
$valore = (... | Out-String [-Width <Larghezza>])

Out-Null
Il cmdlet Out-Null consuma gli oggetti provenienti dalla pipeline, ma
non li invia ad alcuna periferica di output. Questo comando è
tipicamente impiegato per sopprimere la visualizzazione dell’output
da parte di Out-Host per quei blocchi di comando che non ne hanno
bisogno. La sintassi è davvero semplice:
... | Out-Null

Il blocco che segue, per esempio, non produce alcun output a video
perché gli oggetti emessi dalla pipeline nel primo blocco sono
consumati interamente nel secondo, quindi la shell non richiama Out-
Host:

PS C:\Users\ikmju> ps | Out-Null

Si noti che utilizzare Out-Null equivale a effettuare un cast a void dei


risultati ottenuti. L’esempio precedente, pertanto, produce lo stesso
esito di quello che segue:
PS C:\Users\ikmju> [void](ps)

Out-File
Il cmdlet Out-File, il cui obiettivo consiste nel dirottare l’output di una
pipeline all’interno di un file, è approfondito nel Capitolo 20, dedicato
alla manipolazione dei file.
Parte III
Elaborazione dei dati
Gli array

La gestione delle collezioni di oggetti e il supporto


offerto da PowerShell per manipolarne gli elementi,
con un’analisi degli operatori e dei metodi di rilievo
per questo tipo di oggetti.

Nei capitoli precedenti si sono analizzati i concetti di base relativi alla


piattaforma di scripting di PowerShell ed è stato approfondito il
meccanismo di funzionamento della pipeline. Questo capitolo si
concentra, invece, sulle collezioni di oggetti e sugli strumenti che ne
permettono la gestione all’interno della shell. Nella maggior parte dei
linguaggi di sviluppo, con il termine array (o matrice) si indica una
collezione ordinata di oggetti, i cui elementi sono accessibili tramite
un indice numerico. Il framework Microsoft .NET rappresenta questi
oggetti per mezzo di istanze del tipo System.Array e la shell è
organizzata in maniera tale da renderne la fruizione, la gestione e la
manipolazione la più semplice e trasparente possibile.

La creazione
È sufficiente un elenco di espressioni separate da virgola per
generare automaticamente un nuovo array: le espressioni
dell’elenco diventano gli elementi di quest’ultimo, conservandone
l’ordine di partenza.
Definire un nuovo array composto da tre elementi è dunque
semplice come eseguire questo blocco:
$test = 9, 7, 83

Utilizzando il metodo GetType() sulla variabile è possibile recuperare


rapidamente alcune informazioni di base sul tipo che questa
rappresenta:

PS C:\Users\ikmju> $test.GetType()

IsPublic IsSerial Name BaseType


-------- -------- ---- --------
True True Object[] System.Array

Nel nome del tipo – Object[] – la coppia di parentesi quadre che


segue il nome Object denota la presenza di un array; il tipo System.Array,
da cui tutti gli array derivano, fornisce tutte le funzionalità per la
gestione e la manipolazione degli elementi presenti all’interno di
questi oggetti.

Il lettore con precedenti esperienze di sviluppo potrebbe obiettare che l’array


definito nell’esempio non è fortemente tipizzato (ovvero non fa riferimento al tipo
NO
Int32, cui tutti gli elementi appartengono, ma contiene genericamente istanze di
TA
object): per una maggiore semplicità d’uso, infatti, PowerShell gestisce tutti gli
array in maniera identica, considerandoli tutti come collezioni di oggetti generici.

Nel caso in cui si desideri creare un array a partire da un unico


elemento, la shell permette di utilizzare nuovamente il simbolo
virgola (,), da anteporre all’elemento durante l’assegnazione.
In questo script, per esempio, $test è un array con un solo elemento
(il numero 13):

PS C:\Users\ikmju> $test = ,13


PS C:\Users\ikmju> $test.GetType()

IsPublic IsSerial Name BaseType


--------- -------- ---- -------
True True Object[] System.Array

La visualizzazione
La shell permette di visualizzare a video il contenuto di un qualsiasi
array, digitandone semplicemente il nome alla riga del prompt:

PS C:\Users\ikmju> $test
9
7
33

In maniera del tutto trasparente, infatti, quando l’output prodotto da


una pipeline è una collezione di oggetti di tipo primitivo, il sistema la
espande visualizzandone un elemento per riga. Tuttavia, gli array
possono contenere oggetti di qualsiasi tipo del framework Microsoft
.NET e la shell consente persino di utilizzare array con elementi di
tipo differente: in tal caso il sistema determina come visualizzare
l’output in base al tipo degli elementi, riutilizzando l’eventuale vista
nel caso vi sia contiguità rispetto a questa informazione all’interno
della collezione.
Nello script che segue, per esempio, l’array $test è inizializzato
rispettivamente con un intero, un processo e una stringa:

$test = 123, (Get-Process | Select -First 1), 'powershell.it

L’output a video di questo array risulta differenziato in base al tipo:

PS C:\Users\ikmju> $test
123

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ----- ----- ----- ----- ------ -- -----------
472 16 21336 3532 165 5.74 5324 explorer

powershell.it

In quest’altro script, invece, all’interno di $test sono compresi due


processi, contigui:

$first = Get-Process I Select -First 1


$last = Get-Process I Select -Last 1

$test = 123, $first, $last, 456

Osservando l’output a video, in tal caso, è possibile notare come


l’intestazione della visualizzazione a tabella sia condivisa tra i due:

PS C:\Users\ikmju> $test
123
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ----- ----- ----- ----- ------ -- -----------
33 2 1536 3136 35 1268 conime
86 3 2512 4980 55 0,07 2012 wuauclt
456

Come il lettore più attento potrebbe aver notato, gli elementi ritornati
da Get-Process utilizzano la visualizzazione a tabella, di default per
questo tipo di oggetti. Utilizzando Format-List il risultato cambia, come
ci si aspetterebbe:

PS C:\Users\ikmju> $test | Format-List


123

Id : 1268
Handles : 33
CPU :
Name : conime

Id : 3716
Handles : 86
CPU : 0,1101584
Name : wuauclt

456

La natura orientata agli oggetti di PowerShell, d’altra parte, permette


di riutilizzare le nozioni e i concetti validi per gli array anche nel
risultato dei cmdlet: quando questi ultimi generano più di un
elemento, infatti, il sistema ritorna un array di oggetti. La somiglianza
in termini di formattazione degli oggetti a video, dunque, è data dal
fatto che, in entrambi i casi, il sistema manipola array.
Estraendo il tipo del framework Microsoft .NET dal risultato fornito da
Get-Process, per esempio, il sistema conferma quanto illustrato:

PS C:\Users\ikmju> (Get-Process).GetType()

IsPublic IsSerial Name BaseType


-------- -------- ---- --------
True True Object[] System.Array

Recupero e impostazione degli elementi


Così come avviene per la maggior parte dei linguaggi di sviluppo di
derivazione C, anche PowerShell permette di recuperare gli elementi
degli array utilizzando un costrutto chiamato indexer (o, anche,
operatore di indicizzazione), che consiste in una coppia di parentesi
quadre ([]), da far seguire al riferimento dell’array interessato,
all’interno delle quali si specifica l’indice dell’elemento desiderato;
l’indice è un valore numerico intero che parte da zero, in
corrispondenza del primo elemento della collezione.
Nello script che segue, per esempio, sono recuperati il primo e il
terzo elemento di un array di tre valori:

PS C:\Users\ikmju> $test = 9, 7, 83
PS C:\Users\ikmju> $test[0]
9
PS C:\Users\ikmju> $test[2]
83

L’impiego di un indice inesistente non genera errori, ma restituisce il


valore $null:

PS C:\Users\ikmju> $test[3] -eq $null


True

Utilizzando un valore numerico negativo, inoltre, la shell consente di


recuperare con facilità gli elementi a partire dalla fine della collezione
(anziché dall’inizio): in tal caso, il valore -1 corrisponde all’ultimo
elemento dell’array, -2 corrisponde al penultimo e così via.
Riutilizzando l’array definito a inizio sezione, dunque, questo blocco
ritorna il primo e il secondo elemento:

PS C:\Users\ikmju> $test[-3]
9
PS C:\Users\ikmju> $test[-2]
7

Per impostare il valore di un particolare elemento è sufficiente far


seguire al relativo indexer l’operatore di assegnazione (=) e, infine,
l’espressione desiderata.
Ecco come, per esempio, è possibile assegnare al primo elemento di
$test il valore 123:

$test[0] = 123

Nel caso delle assegnazioni, tuttavia, il riferimento a un indice


inesistente porta alla generazione di un errore:

PS C:\Users\ikmju> $test[999] = 'hello world


Assegnazione della matrice non riuscita. Indice '999' non comprese
nell'intervallo.
In riga:1 car:7
+ $test[ «« 999] = 'hello world
+ Categorylnfo : Invalidoperation: (999:Int32) [],
RuntimeException
+ FullyQualifiedErrorld : IndexOutOfRange

Ogni array, infine, dispone della proprietà Length, tramite la quale è


possibile risalire al numero di elementi memorizzati:
PS C:\Users\ikmju> $test.Length
3

L’Extended Type System di PowerShell aggiunge agli array la proprietà Count,


NO utilizzata tipicamente con maggiore frequenza rispetto alla proprietà nativa perché
TA il nome risulta forse più naturale in un contesto dove si contano degli oggetti.
Questa proprietà, d’altra parte, è semplicemente un alias di Length.

Recupero di elementi multipli


Al di là dei singoli elementi, PowerShell consente di estrarre anche
sezioni di un array, fornendo in sequenza più di un indice all’indexer:
il sistema estrae gli elementi individuati dagli indici forniti e ritorna un
nuovo array. L’espressione fornita all’indexer, infatti, può essere sia
un valore numerico intero sia un array di valori di questo tipo. Il
blocco che segue, a dimostrazione di quanto illustrato, costituisce un
array a partire da caratteri e numeri e ne estrae una sezione in base
a una serie di indici (di cui uno negativo):

PS C:\Users\ikmju> $test = 3, 0, 'h', 1


PS C:\Users\ikmju> $test[2, 0, 3, -1, 1]
h
3
1
1
0

Nonostante il recupero sia supportato da questo costrutto,


l’assegnazione di nuovi valori a una sezione di array (operazione
chiamata, in gergo, slice assignment) non lo è. Tentando
l’operazione, infatti, la shell genera un errore:

PS C:\Users\ikmju> $test[-1, 0] = 5, 'P'


Assegnazione della matrice a [-1,0] non riuscita. L'assegnazione a sezioni
non è supportata.
In riga:1 car:7
+ $test[ <<<< -1, 0] = 5, 'P'
+ CategoryInfo : InvalidOperation: (System.Object[]:Object[])
[], RuntimeException
+ FullyQualifiedErrorId : ArraySliceAssignmentFailed

L’operatore di range numerico


L’operatore di range numerico, noto anche come operatore .., è in
grado di generare autonomamente una sequenza di numeri interi.
Indicando un valore di partenza e un uno di destinazione, infatti,
questo operatore produce un array che contiene tutti gli interi tra i
valori forniti, inclusi questi ultimi.
Se l’intero di partenza è inferiore a quello di destinazione, il sistema
produce una sequenza numerica crescente; in caso contrario,
invece, la sequenza è decrescente. La sintassi dell’operatore è:
<Intero di partenza>..<Intero di destinazione>

Eseguendo il blocco che segue, per esempio, il sistema genera un


nuovo array con tutti gli interi compresi tra 5 e 7:

PS C:\Users\ikmju> 5.-7
5
6
7

Nulla vieta, naturalmente, di impiegare valori negativi:


PS C:\Users\ikmju> -2 - -1
-2
-1
0
1

Se applicato a un indexer, l’operatore di range numerico permette di


recuperare con facilità una particolare sezione di un array.
Nello script seguente, per esempio, grazie a questo operatore sono
recuperati con facilità i primi tre elementi ritornati da Get-Command:

PS C:\Users\ikmju> $commands = Get-Command


PS C:\Users\ikmju> $commands[0..2]

CommandType Name Definition


----------- ---- ----------
Alias % ForEach-Object
Alias ? Where-Object
Function A: Set-Location A:
Unire array e aggiungere elementi
PowerShell consente di unire tra loro due o più array, generandone
un terzo che contiene l’unione degli elementi.
L’operatore di addizione, individuato dal rispettivo simbolo (+),
quando è posto tra due oggetti di questo tipo genera un nuovo array
con gli elementi dell’operando di sinistra e, successivamente, quelli
del destro, mantenendo il rispettivo ordinamento iniziale. Questo
script, infatti, genera un array di sei elementi:

PS C:\Users\ikmju> $a = 3, 4, 5
PS C:\Users\ikmju> $b = 1, 2, 3
PS C:\Users\ikmju>
PS C:\Users\ikmju> $a + $b
3
4
5
1
2
3

Quando l’operando di sinistra è un array ma quello di destra no,


inoltre, l’operatore di addizione si comporta come ci si aspetterebbe,
producendo un nuovo array composto da tutti gli elementi del primo
e dall’oggetto specificato come secondo operando.
Riutilizzando l’esempio precedente per ottenere un nuovo array con
il contenuto di $a, $b e il numero 7, è sufficiente eseguire questo
blocco:

PS C:\Users\ikmju> $a + $b + 7
3
4
5
1
2
3
7

Combinando questo operatore con quello di assegnazione, infine, è


possibile aggiungere con facilità nuovi elementi a un array esistente.
Quando l’operando di sinistra è un oggetto di questo tipo, infatti,
l’operatore += si comporta in maniera analoga a quello di addizione,
ma sovrascrive l’operando di sinistra con il risultato dell’operazione.
Ecco, per esempio, come aggiungere la stringa powershell.it all’array
$a, utilizzato in precedenza:
PS C:\Users\ikmju> $a += 'powershell - it'
PS C:\Users\ikmju> $a
3
4
5
powershell - it

Rimuovere elementi
Non esiste una via diretta, purtroppo, per eliminare gli elementi di un
determinato array; in questo caso è necessario creare un nuovo
array in maniera tale che contenga tutti gli elementi del primo
all’infuori di quello da eliminare. D’altra parte, le tecniche di recupero
delle sezioni di array analizzate in precedenza rendono questo
compito molto semplice. Lo script che segue mostra come usufruire
dell’operatore di range numerico per rimuovere dall’array di esempio
il quarto elemento:

PS C:\Users\ikmju> $test = 'a', 'b', 'e', 'd', 'e', 'f', 'g


PS C:\Users\ikmju> $test = $test[0..2 + 4.-6;
PS C:\Users\ikmju> $test
a
b
e
e
f
g

Lo splitting
All’interno della shell è possibile separare gli elementi di un array
utilizzando una tecnica nota come splitting. Con questo sistema si
utilizza l’operatore di assegnazione, impiegando come operando di
sinistra una serie di variabili separate dal simbolo virgola (, ), e come
operando di destra l’array desiderato: da questo PowerShell estrae
tanti elementi quante sono le variabili della serie di sinistra, meno
uno, e ne utilizza i valori per impostarle. All’ultima variabile
dell’operando di sinistra, infine, vengono associati gli elementi
rimasti: se sono più di uno la variabile risultante è un array, in caso
contrario no.
La sintassi d’uso di questa tecnica è schematizzata di seguito:
$a, $b, ..., $n = $array
L’esempio che segue estrae i primi due elementi dell’array in due
variabili distinte (Sfirst e $second) e la sezione rimanente in un secondo
array ($others):

PS C:\Users\ikmju> $test = 1, 2, 3, 4, 5, 6, 7
PS C:\Users\ikmju> $first, $second, $others = $test
PS C:\Users\ikmju>
PS C:\Users\ikmju> $first
1
PS C:\Users\ikmju> $second
2
PS C:\Users\ikmju> $others
3
4
5
5
7

Utilizzando il valore $null all’interno dell’operando di sinistra la shell


ne salta l’elemento (o gli elementi, se è l’ultimo della serie)
corrispondente:

PS C:\Users\ikmju> $test = 1, 2, 3, 4, 5, 6, 7
PS C:\Users\ikmju> $first, $null, $third, $null = $test
PS C:\Users\ikmju> $first
1
PS C:\Users\ikmju> $third
3

L’operatore di sottoespressione di array


In diverse circostanze risulta molto utile poter considerare il risultato
di qualsiasi espressione come se fosse un array, a prescindere dalla
numerosità degli elementi.
Volendo recuperare il numero di processi ritornati da Get-Process, per
esempio, a prima vista potrebbe sembrare corretto utilizzare la
proprietà Count (o Length) esposta dall’array ritornato da questo cmdlet:
PS C:\Users\ikmju> (Get-Process)-Count
74

Nel caso in cui il cmdlet ritornasse un unico elemento, però, la


proprietà non sarebbe più disponibile poiché non si tratterebbe più di
un array. Anche se questa ipotesi non potrebbe mai verificarsi per lo
script precedente, lo stesso non si può dire nel caso in cui a Get-
Process sia fornito, per esempio, un criterio con cui filtrare i processi.
Specificando il nome System, per esempio, lo script precedente non
produce alcun risultato, nonostante esista esattamente un processo
con questo nome:

PS C:\Users\ikmju> (Get-Process System).Count

Per ovviare a questa incoerenza di risultati, PowerShell mette a


disposizione dell’utente l’operatore di sottoespressione di array, con
cui è possibile racchiudere, utilizzando una coppia di parentesi tonde
e il simbolo di a commerciale (@), una qualsiasi espressione e avere
la garanzia che il sistema ne ritorni in ogni circostanza un array Se
l’espressione è già un oggetto di questo tipo l’operatore viene
ignorato; in tutti gli altri casi agisce creando un nuovo array.
Rivedendo gli esempi precedenti, dunque, entrambi gli script
possono avvalersi dell’operatore di sottoespressione di array ed
essere coerenti:

PS C:\Users\ikmju> ©(Get-Process).Count
75
PS C:\Users\ikmju> ©(Get-Process System).Count
1

Per creare un array vuoto (a cui aggiungere elementi,


successivamente, magari) è sufficiente alimentare l’operatore con
un’espressione vuota:

PS C:\Users\ikmju> ©().Count
0

Filtrare gli elementi


Quando l’operando di sinistra è un array, tutti gli operatori di
confronto (vedere il Capitolo 6), tranne -is e -isnot, agiscono da filtro
rispetto agli elementi che lo costituiscono, ritornando solo gli oggetti
che soddisfano la condizione imposta. La natura di ciò che ne risulta
è duplice e varia a seconda della numerosità degli elementi
recuperati: se vi è un unico elemento, questo viene ritornato così
com’è. In caso contrario viene ritornato un nuovo array, il cui
contenuto sono gli elementi stessi.
La sintassi, per questo tipo di costrutto, è quella già analizzata per gli
operatori di confronto:

<Array> -<Operatore> <Operando di destra>

Nello script che segue, per esempio, l’array $test viene filtrato e sono
ritornati solo gli elementi minori di un particolare valore:

PS C:\Users\ikmju> $test = 1, 2, 3, 4, 5, 6, 7
PS C:\Users\ikmju> $test -lt 3
1
2

Non è consentita, purtroppo, la combinazione di espressioni


mediante operatori logici (non è possibile, dunque, filtrare gli
elementi in base a due o più condizioni contemporaneamente). In
questa circostanza è necessario affidarsi a un ciclo oppure al cmdlet
Where-Object.
Sia il ciclo while (comprese le varianti do..whi1e e do..until) sia il ciclo for
sono valide alternative per iterare all’interno di una collezione di
oggetti (vedere il Capitolo 7): incrementando una variabile a ogni
ciclo e uscendo non appena questa supera il valore massimo
ammesso come indice, è possibile eseguire un blocco di istruzioni
per ciascun elemento di un determinato array.
L’assegnazione della variabile, il suo incremento e la verifica della
condizione d’uscita sono attività tipicamente delegate al ciclo for, per
via della compattezza del codice generato.
Per filtrare i numeri pari dall’array definito nell’esempio precedente è
quindi possibile impiegare il ciclo for in maniera simile a questa:

for ($i = 0; $i -It $test.Length; $i++) {


$element = $test[$i]

if ($element % 2 -eq 0) {
$element
}
}

Non si tratterebbe, in ogni modo, della soluzione più efficiente. Il


cmdlet Where-object, infatti, potrebbe risolvere il medesimo problema in
modo molto più compatto e performante, facendo uso della pipeline:
PS C:\Users\ikmju> $test | Where-Object { $_ %2 -eq 0 }
2
4
5

Gli operatori -contains e -notcontains


Gli array dispongono, infine, di due operatori in grado di ritornare un
valore logico differente a seconda che l’array in questione contenga
un determinato valore o meno.
L’operatore -contains, se posto tra un array - a sinistra - e una
qualsiasi espressione - a destra - ritorna il valore $true quando
quest’ultima è contenuta all’interno del primo. Di riflesso, l’operatore
-notcontains ritorna risultati opposti.
Nello script che segue, per esempio, la prima espressione riporta a
video il valore di verità logica, mentre la seconda no:

PS C:\Users\ikmju> $test = 1, 2, 3, 4, 5, 6, 7
PS C:\Users\ikmju> $test -contains 3
True
PS C:\Users\ikmju> $test -contains 8
False

Poiché $null è un valore come tutti gli altri, infine, questi operatori si
comportano come ci si aspetterebbe:

PS C:\Users\ikmju> $test += $null


PS C:\Users\ikmju> $test -contains $null
True
PS C:\Users\ikmju> $test -notcontains $null
False

Metodi di rilievo
Come anticipato all’inizio del capitolo, ogni array è un’istanza della
classe System. Array (o di una sua derivata) e, come tale, può usufruire
di una serie di metodi e proprietà che rendono più agevole l’impiego
di questi oggetti. Nonostante ne esistano alcuni a livello d’istanza
(come la proprietà Length, già menzionata), i membri più interessanti
di questo tipo sono statici. Per ottenere un elenco dei membri statici
degli array è sufficiente utilizzare lo switch -Static di Get-Member:

PS C:\Users\ikmju> β().GetType() | Get-Member -Static


TypeName: System.Object[]
Name MemberType Definition
---- ---------- ----------
AsReadOnly Method static System.Collections.ObjectModel.
ReadOnlyCollection[T] AsReadOnly[T](T[] array)
BinarySearch Method static int BinarySearch(array array, System.
Object value), static int BinarySearch(array ...
Clear Method static System.Void Clear(array array, int
index,
int length)
ConstrainedCopy Method static System.Void ConstrainedCopy(array
sourceArray, int sourceIndex, array destinationA...
ConvertAll Method static TOutput[] ConvertAll[TInput,
TOutput](TInput[] array, System.Converter[TInput,TOut...
[...]

Anche se la trattazione di tutti i metodi supportati è al di fuori degli


obiettivi di questo libro, vale comunque la pena di soffermarsi sui più
importanti.

Metodo Index0f()
Questo metodo è in grado di vagliare l’array fornito, elemento per
elemento, alla ricerca di un determinato valore. Alla prima
occorrenza trovata il metodo termina l’elaborazione ritornando il
relativo indice; nel caso non ne venga trovata alcuna, il metodo
restituisce il valore -1.
La sintassi di questo metodo è la seguente:

[array]::IndexOf(<Array>, <Valore>)

Nello script che segue, per esempio, sono visualizzati a video


dapprima l’indice del valore richiesto e, in seguito al fallito recupero,
il valore -1:

PS C:\Users\ikmju> $test = 6, 3, 5, 2, 4, 1
PS C:\Users\ikmju> [array]::IndexOf($test, 2)
3
PS C:\Users\ikmju> [array]::IndexOf($test, 7)
-1

Un overload del metodo Indexof() permette inoltre di specificare, in


seguito al valore, l’indice dell’array da cui partire con la ricerca,
secondo questo schema:

[array]::IndexOf(<Array>, <Valore>, <Partenza>]


Nell’esempio che segue si richiede alla shell di visualizzare a video
l’indice del primo elemento pari al valore 6, a partire dal terzo
elemento incluso.

PS C:\Users\ikmju> $test = 6, 3, 5, 6, 5, 6
PS C:\Users\ikmju> [array]::IndexOf($test, 6, 2)
3

Metodo LastlndexOf()
Il metodo LastlndexOf() agisce in maniera speculare a IndexOf(),
effettuando la ricerca degli elementi a ritroso, partendo dalla fine.
Al pari di quest’ultimo, il metodo dispone di un overload tramite il
quale è possibile indicare l’indice di partenza.
Le sintassi d’uso per i due sono queste:

[array]::LastIndexOf(<Array>, <Valore>)
[array]::LastlndexOf(<Array>, <Valore>, <Partenza>)

Lo script che segue illustra l’impiego dei due overload:

PS C:\Users\ikmju> $test = 6, 3, 5, 6, 5, 6
PS C:\Users\ikmju> [array]::LastlndexOf($test, 6)
5
PS C:\Users\ikmju> [array]::LastlndexOf($test, 6, 4)
3

Metodo Reverse()
Il metodo Reverse() inverte la sequenza degli elementi all’interno di un
array. Dispone anche di un overload cui è possibile specificare un
indice di partenza e un numero di elementi da processare, in
maniera tale che questa operazione sia limitata a una porzione
dell’array.
Le sintassi d’uso per i due overload sono:

[array]::Reverse(<Array>)
[array]::Reverse(<Array>, <Partenza>, <NumeroElementi>;

Nel blocco che segue, per esempio, la sequenza dei primi cinque
numeri naturali viene invertita dal comando:

PS C:\Users\ikmju> $test = 1, 2, 3, 4, 5
PS C:\Users\ikmju> [array]::Reverse($test)
PS C:\Users\ikmju> $test
5
4
3
2
1

Quest’altro esempio, invece, utilizza l’overload che consente di


invertirne solo una porzione:

PS C:\Users\ikmju> $test = 1, 2, 3, 4, 5
PS C:\Users\ikmju> [array]::Reverse($test, 2, 3)
PS C:\Users\ikmju> $test
1
2
5
4
3
Gli array associativi

Una panoramica sul supporto offerto da PowerShell per gestire


gliarray associativi e gli oggetti che queste strutture
possono immagazzinare, tramite i metodi del
framework Microsoft .NET e gli elementi sintattici
previsti dal linguaggio di scripting.

Come è stato illustrato nel capitolo precedente, gli array sono valide
strutture di cui avvalersi all’interno del proprio codice per ospitare ed
elaborare informazioni e dati. Il framework Microsoft .NET, così come
molti altri ambienti di sviluppo, consente di utilizzare un tipo simile al
precedente ma dotato di caratteristiche e funzionalità aggiuntive,
chiamato array associativo (oppure tabella hash). La differenza
principale tra questi due tipi consiste nel fatto che negli array ogni
elemento è individuabile unicamente tramite un indice numerico,
mentre negli array associativi l’indice può essere, esso stesso, di un
tipo qualsiasi. Questa maggiore libertà di espressione permette agli
array associativi di essere estremamente utili in tutte quelle
situazioni dove si ha la necessità di creare correlazioni tra alcuni
oggetti e altri e, successivamente, poterle recuperare e gestire
agevolmente.
La creazione
Per creare un array associativo all’interno di PowerShell è sufficiente
utilizzare un costrutto simile a quello impiegato per l’operatore di
sottoespressione di array, con la differenza che, anziché far seguire
al simbolo di a commerciale (@) delle parentesi tonde, sono usate
due parentesi graffe: il risultato di questa operazione è un’istanza del
tipo System.Collections.Hashtable.
La creazione di questi oggetti fa capo, quindi, allo schema sintattico
che segue:

$hash = @{}

Come anticipato, ogni array associativo è in grado di contenere


correlazioni tra oggetti: ciascuna correlazione è composta da un
primo oggetto che funge da chiave e da un secondo che
rappresenta il valore individuato dalla chiave. A differenza di un
ipotetico dizionario, però, limitato a contenere stringhe sia nelle
chiavi sia nei valori, il sistema non pone alcun vincolo sul tipo di dati
da impiegare, in entrambi i casi: l’unica limitazione consiste negli
oggetti impiegati come chiavi, che devono essere univoci
all’interno della struttura.
Sebbene ci siano diversi modi, come analizzato nel resto del
capitolo, per aggiungere correlazioni a un array associativo
esistente, la shell offre la possibilità di includerle anche durante la
creazione di questi oggetti: avvalendosi del costrutto illustrato in
precedenza, infatti, è possibile includere una serie di coppie chiave-
valore direttamente all’interno della coppia di parentesi graffe della
struttura sintattica. Ogni correlazione, separata dall’eventuale
successiva tramite il simbolo punto e virgola (;), è rappresentata da
un’uguaglianza tra l’oggetto che si vuole utilizzare come chiave e
quello che si desidera far diventare il valore associato a quest’ultima.
La sintassi completa del costrutto utilizzato per creare array
associativi, dunque, è rappresentata da questo schema:

$hash = @{ <Chiave> = <Valore>; [<Chiave> = <Valore>;] ... }


Nello script che segue, per esempio, viene creato un array
associativo per correlare il nome di alcuni gas nobili con i rispettivi
numeri atomici:

$nobleGases = @{ 'Elio' = 2; 'Neon' = 10; 'Argon' = 18 }

Le chiavi utilizzate per creare correlazioni sono perlopiù stringhe, ma


nulla vieta di utilizzare altri tipi di oggetto; questo script, per esempio,
crea un array associativo utilizzando chiavi (e valori) di tipo
eterogeneo:

$hash = @{ 100 = 'Cento'; (Get-Date) = 123; 'Processes' = (Get-Process) }

Vista la frequenza con cui le chiavi sono rappresentate da stringhe,


tuttavia, la shell consente in questa circostanza di omettere gli apici
(‘), a patto che la stringa non contenga spazi. Il blocco illustrato
poc’anzi, pertanto, può essere riscritto in modo più “naturale”,
ottenendo il medesimo risultato:

$nobleGases = @{ Elio = 2; Neon = 10; Argon = 18 }

La visualizzazione
La shell rappresenta gli array associativi utilizzando, di default, una
visualizzazione a tabella: una prima colonna riporta l’intestazione Name
e contiene tutte le chiavi utilizzate dall’oggetto. La seconda colonna,
invece, contiene i valori a cui puntano le chiavi e riporta
l’intestazione Value. Per visualizzare il contenuto di un array
associativo è sufficiente digitarne il nome al prompt:

PS C:\Users\ikmju> $nobleGases = @{ Elio = 2; Neon = 10; Argon = 18 }


PS C:\Users\ikmju> $nobleGases

Name Value
---- -----
Argon 18
Neon 10
Elio 2

Nonostante la somiglianza con gli array, d’altra parte, quando l’array


associativo contiene tipi complessi la shell non si avvale
dell’Extended Type System per visualizzarne il contenuto, ma si
limita a mostrare, per ciascuna chiave e ciascun valore, il risultato
della chiamata al metodo ToString():

PS C:\Users\ikmju> $rubbish = @{ 100 = 'Cento'"; (Get-Date) = 123; System =


(Get-Process System) }
PS C:\Users\ikmju> $rubbish

Name Value
---- -----
2/23/2010 11:37:38 AM 123
System System.Diagnostics.Process (System)
100 Cento

Alla stregua di quanto avviene per gli array, anche gli array
associativi dispongono della proprietà Count, tramite la quale è
possibile risalire al numero di correlazioni memorizzate all’interno di
ciascun oggetto. Nello script che segue, infatti, il sistema riporta il
valore desiderato:

PS C:\Users\ikmju> @{ Bill = 56; Warren = 37; Carlos = 35 }.Count


3

Recupero e impostazione degli elementi


Per recuperare un particolare elemento da un array associativo è
sufficiente utilizzare il costrutto indexer, in maniera molto simile a
quanto già illustrato per gli array. A differenza di questi ultimi,
tuttavia, all’interno della coppia di parentesi quadre non va
specificato l’indice numerico bensì la chiave cui è correlato il valore
desiderato, secondo questo schema:

<Array associativo>[<Chiave>]

Nel blocco che segue, per esempio, si definisce un nuovo array


associativo eterogeneo e se ne recuperano i valori in base alle
rispettive chiavi:

PS C:\Users\ikmju> $date = Get-Date


PS C:\Users\ikmju> $rubbish = @{ System = (Get-Process System); 100 = 'Cento';
$date = 123; }
PS C:\Users\ikmju> $rubbish['System']

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ----- -- -----------
628 0 56 4216 7 4 System
PS C:\Users\ikmju> $rubbish[$date]
123
PS C:\Users\ikmju> $rubbish[100]
Cento

Per impostare il valore di un particolare elemento all’interno di un


array associativo esistente, è sufficiente far seguire al risultato
dell’indexer l’operatore di assegnazione (=) e, infine, l’espressione
desiderata.
Nel caso in cui la chiave specificata durante questa operazione
esista già all’interno dell’oggetto, il valore cui la correlazione punta è
aggiornato all’espressione fornita; in caso contrario, invece, l’array
associativo si occupa di creare una nuova relazione. Nel caso si
tratti di stringhe, il confronto tra chiavi avviene in modalità case
insensitive.

L’operatore punto
Poiché gli array associativi sono tra i tipi più impiegati all’interno
della shell, il sistema consente di recuperarne gli elementi mediante
una sintassi alternativa basata sull’operatore punto, molto simile a
quella impiegata per ricavare i valori delle proprietà di un oggetto.
Se le chiavi sono specificate come stringhe prive di spazi, infatti, i
valori a esse associati sono ottenibili seguendo uno schema simile a
questo:

<Array associativo>.<Chiave>

Questa sintassi permette, naturalmente, di ottenere un codice più


leggibile; nello script che segue, per esempio, si sfrutta l’operatore
punto per rendere più “naturale” l’estrazione di due elementi da un
array associativo:

PS C:\Users\ikmju> $nobleGases = @{ Elio = 2; Neon = 10; Argon = 18 }


PS C:\Users\ikmju> $nobleGases.Elio
2
PS C:\Users\ikmju> $nobleGases.Argon
18

Come ci si potrebbe aspettare, inoltre, utilizzando questa tecnica è


anche possibile impostare i valori delle correlazioni, facendo
semplicemente seguire a quanto estratto tramite l’operatore punto
quello di assegnazione e, infine, l’espressione desiderata. Anche in
questo caso, se la chiave specificata esiste già all’interno dell’array
associativo l’operazione porta a un aggiornamento del valore a essa
correlato; in caso contrario porta alla creazione di una nuova
correlazione. Anche qui, nel caso si tratti di stringhe, il confronto tra
chiavi avviene in modalità case insensitive. Per aggiungere un nuovo
elemento all’array associativo dell’esempio precedente, quindi, è
sufficiente eseguire un blocco simile a questo:

PS C:\Users\ikmju> $nobleGases.Kripton = 36

E il nuovo elemento appare nell’output:

PS C:\Users\ikmju> $nobleGases

Name Value
---- -----
Argon 18
Neon 10
Elio 2
Kripton 36

Rimuovere elementi
Per rimuovere una correlazione all’interno di un array associativo è
necessario impiegare il metodo Remove(), cui è necessario fornire la
chiave desiderata. La sintassi di questo metodo è la seguente:

<Array associativo>.Remove(<Chiave>)

Per rimuovere l’elemento aggiunto nell’esempio precedente, dunque,


è necessario operare in maniera simile a questa:

PS C:\Users\ikmju> $nobleGases.Remove('Kripton')

Se la chiave fornita non esiste all’interno dell’array associativo, il


metodo Remove() non genera alcun errore. Anche in questo caso, se le
chiavi sono stringhe, il confronto avviene in modalità case
insensitive.

ConvertFrom-StringData
A partire dalla versione 2.0, PowerShell include il nuovo cmdlet
ConvertFrom-StringData, dedicato alla creazione di array associativi a
partire da una rappresentazione testuale degli stessi.
Per utilizzare questo comando è necessario specificare, per mezzo
del parametro-StringData (unico posizionale), una stringa formata da
tante righe quante sono le correlazioni volute. Ciascuna riga deve
essere composta seguendo uno schema predefinito, che include il
nome della chiave desiderata, il simbolo di assegnazione (=) e la
rappresentazione testuale dell’espressione associata, secondo
questo schema:

<Chiave> = <Valore>
[<Chiave> = <Valore>]
...

Memorizzando, per esempio, un file di testo come questo sotto il


nome di Machines. txt, nella cartella corrente:

SRV01 = 10.0.1.1
SRV02 = 10.0.1.2
SRV03 = 10.0.1.3

e leggendone il contenuto testuale mediante il cmdlet Get-Content


(approfondito nel Capitolo 20), è possibile utilizzare ConvertFrom-
StringData per crearne l’array associativo risultante:

PS C:\Users\ikmju> $machines = Get-Content .\Machines.txt


PS C:\Users\ikmju> $addresses = ConvertFrom-StringData $machines
PS C:\Users\ikmju> $addresses.SRV03
10.0.1.3

Metodi e proprietà di rilievo


Nel capitolo si è già parlato della proprietà Count, che permette di
recuperare il numero di correlazioni presenti all’interno dell’array
associativo, e del metodo Remove(), in grado di eliminare la
correlazione fornita.
L ’oggetto System.Collections.Hashtable dispone di altri metodi e proprietà
utili da ricordare, di cui si parla nel seguito di questo paragrafo.

Proprietà Keys
Questa proprietà consente di recuperare la collezione delle chiavi
utilizzate dall’array associativo.
In questo script, per esempio, sono visualizzate a video le chiavi
delle correlazioni create in fase di inizializzazione di un array
associativo:

PS C:\Users\ikmju> $nobleGases = @{ Elio = 2; Neon = 10; Argon = 18 }


PS C:\Users\ikmju> $nobleGases.Keys
Argon
Neon
Elio

Tramite un ciclo è possibile risalire a tutti i valori di ogni correlazione:

PS C:\Users\ikmju> foreach ($nobleGas in $nobleGases.Keys)


>> {
>> # Visualizza dapprima la chiave
>> $nobleGas
>>
>> $nobleGases[$nobleGas]
>> }
>>
Argon
18
Neon
10
Elio
2

Proprietà Values
Alla stregua della precedente, la proprietà Values permette di
recuperare la collezione dei valori impiegati dall’array associativo:

PS C:\Users\ikmju> $nobleGases.Values
18
10
2

Metodo GetEnumerator()
Questo metodo consente di recuperare la collezione di correlazioni
memorizzate internamente all’array associativo. Nonostante la shell
possa far presupporre il contrario, infatti, un array associativo non è
un array, pertanto non c’è modo di farne fluire automaticamente gli
elementi all’interno della pipeline. Il lettore più smaliziato può aver
provato, probabilmente, a fornire tramite pipeline un array
associativo a un cmdlet, come per esempio Sort-Object. In tal caso il
risultato non è quello atteso e l’ordinamento degli oggetti sembra
non funzionare a dovere:

PS C:\Users\ikmju> $nobleGases | Sort-Object Name

Name Value
---- -----
Argon 18
Neon 10
Elio 2

Dietro le quinte, infatti, il sistema considera l’array associativo un


oggetto unico, nel quale la proprietà Name non esiste nemmeno. Per
ovviare a questa situazione è sufficiente richiamare il metodo
GetEnumerator(), che non accetta parametri, in maniera tale da estrarre
gli elementi dell’array associativo e fornirli alla pipeline. In questo
modo anche l’ordinamento riprende a funzionare correttamente:

PS C:\Users\ikmju> $nobleGases.GetEnumerator() | Sort-Object Name

Name Value
---- -----
Argon 18
Elio 2
Neon 10

Metodo ContainsKey()
Il metodo ContainsKey() verifica se un particolare oggetto è presente
all’interno delle chiavi dell’array associativo e ritorna il valore $true in
caso affermativo, $false in caso contrario. Accetta, pertanto, un unico
parametro che rappresenta la chiave da verificare; ecco la sintassi di
chiamata:

<Array associativo>.ContainsKey(<Chiave>)

Riutilizzando l’esempio precedente, dunque, nello script che segue il


metodo ContainsKey() ritorna $true nel primo caso, $false nel successivo:

PS C:\Users\ikmju> $nobleGases.ContainsKey('Neon')
True
PS C:\Users\ikmju> $nobleGases.ContainsKey('Polonio')
False
Metodo ContaìnsValue()
Il metodo ContainsValue() verifica se un particolare oggetto è
presente tra i valori delle correlazioni di un array associativo. In
maniera identica a ContainsKey(), infatti, ritorna $true se l’oggetto fornito
è tra questi ultimi, $false altrimenti. La sintassi di chiamata è questa:
<Array associativo>.ContainsValue(<Valore>)

Riutilizzando - ancora una volta - l’esempio precedente, nello script


che segue il metodo Containsvalue() ritorna $true nel primo caso, $false
nel successivo:

PS C:\Users\ikmju> $nobleGases.ContainsValue(2)
True
PS C:\Users\ikmju> $nobleGases.ContainsValue(3)
False
Le stringhe

Un’analisi approfondita sui dati di tipo stringa,


sui metodi esposti dalla relativa classe e sugli
elementi sintattici di cui ci si può avvalere
all’interno della shell per gestire e manipolare
con facilità i testi.

La gestione delle stringhe ha sempre giocato un ruolo fondamentale


all’interno di qualsiasi shell testuale; nel mondo *nix, che ha potuto
osservare un’evoluzione molto più costante per questo tipo di
software, l’interazione con il sistema avveniva perlopiù utilizzando
tool a riga di comando, in grado di ritornare del semplice testo.
Questa limitazione ha portato, nel corso del tempo, all’evolversi di
applicazioni e tecniche differenti create per recuperare e manipolare
il testo ritornato all’interno delle piattaforme di scripting, estraendo le
informazioni di interesse. All’interno di PowerShell questa esigenza è
sicuramente molto meno sentita, perché il limite dell’output testuale
è stato superato con l’emissione di oggetti all’interno della pipeline e
tutti i cmdlet sono sottoposti a questo modello.
La gestione del testo, tuttavia, occupa ancora un ruolo di primo
piano tra le attività di gestione e automazione dei sistemi: il seguito
del capitolo (con il capitolo successivo) illustra la risposta di
PowerShell a questa importante esigenza, analizzando le
funzionalità messe a disposizione dal tipo System.String, di cui tutte
le stringhe sono un’istanza, e dedicando ampio spazio sia ai cmdlet
destinati alla manipolazione di questi oggetti sia alla modalità con cui
la shell stessa li gestisce.

La creazione
PowerShell consente di creare nuove stringhe secondo modalità
differenti, in base alle esigenze; quando il concetto di stringa è stato
introdotto, nel Capitolo 3, si è scelto di illustrare il sistema più
semplice, che prevede di racchiudere i caratteri che compongono il
testo desiderato tra apici singoli ('). Questo costrutto prende il nome
di stringa letterale (literal string) e segue dunque questo semplice
schema:

<Testo>

Le stringhe create mediante questa struttura sintattica sono costituite


dal testo presente all’interno della definizione, impiegato così com’è:
la shell non effettua alcuna interpretazione del contenuto delle
stringhe letterali, pertanto è possibile utilizzare liberamente, per
esempio, il simbolo del dollaro ($) oppure il nome completo di una
variabile.
Nei capitoli precedenti sono stati utilizzati script simili a questo:

PS C:\Users\ikmju> $test = 'powershell.it


PS C:\Users\ikmju> '$test => ' + $test
$test => powershell.it

La shell prevede, però, una seconda modalità di creazione delle


stringhe, in grado di interpretare il testo presente nella definizione.
Tale modalità, che si attiva semplicemente impiegando gli apici
doppi al posto dei singoli, impone al sistema di elaborare il testo
originale e di sostituire tutti i riferimenti a variabili con la
rappresentazione testuale delle stesse.
Lo schema d’uso è quindi sostanzialmente identico a quello già
analizzato per le stringhe letterali:
"<Testo>"

La differenza con queste ultime, però, è notevole e porta alla


creazione di script molto più compatti ed efficienti; quello che segue,
per esempio, dimostra quanto sia semplice utilizzare riferimenti a
variabili all’interno delle stringhe di questo tipo:

PS C:\Users\ikmju> $hour = (Get-Date).Hour


PS C:\Users\ikmju> "Sono le $hour e tutto va bene."
Sono le 12 e tutto va bene.

NOT Il cmdlet Get-Date è trattato in modo approfondito nel Capitolo


A 15.

Quando la definizione della stringa tramite doppi apici contiene un


riferimento a un array, PowerShell lo sostituisce con una lista dei
valori che contiene; il comportamento predefinito della shell, in
questo caso, prevede che ogni elemento sia separato dal successivo
con uno spazio. Nello script che segue, per esempio, il sistema
mostra a video il contenuto dell’array $primes.

PS C:\Users\ikmju> $primes = 2, 3, 5, 7, 11, 13


PS C:\Users\ikmju> "$primes"
2 3 5 7 11 13

Agendo sulla variabile globale $OFS (acronimo di Output Field


Separator) è inoltre possibile modificare la stringa impiegata dalla
shell come separatore, consentendo di ottenere un output adatto alle
proprie esigenze:

PS C:\Users\ikmju> $OFS = ', '


PS C:\Users\ikmju> "$primes"
2, 3, 5, 7, 11, 13

I doppi apici consentono, inoltre, di includere intere espressioni


all’interno della definizione delle stringhe, imponendo alla shell di
sostituirne i riferimenti con le rispettive rappresentazioni testuali. Per
ottenere questo risultato è necessario includere le espressioni
desiderate all’interno di una coppia di parentesi tonde, preceduta dal
simbolo del dollaro ($), secondo questo schema:
"... $(<Espressione>) ..."

L’esempio illustrato poc’anzi può dunque essere riscritto in modo


ancora più compatto:

PS C:\Users\ikmju> "Sono le $((Get-Date).Hour) e tutto va bene." Sono le 12 e tutto


va bene.

La shell permette di utilizzare liberamente gli apici singoli all’interno


delle stringhe definite con apici doppi e viceversa; tuttavia, l’impiego
di una tipologia di apice all’interno di una definizione che utilizza la
medesima porta la shell a interrompere l’elaborazione e a generare
un errore. Dei due blocchi che seguono, per esempio, il primo è
corretto mentre il secondo no:

PS C:\Users\ikmju> "un'esperienza fantastica" un'esperienza fantastica

PS C:\Users\ikmju> 'un'esperienza fantastica'


Token 'esperienza' imprevisto nell'espressione o nell'istruzione.
In riga:1 car:15
+ 'un'esperienza <<<< fantastica'
+ Categorylnfo : ParserError: (esperienza:String) [],
ParentContainsErrorRecordException
+ FullyQualifledErrorId : UnexpectedToken

Per risolvere questo tipo di problema è sufficiente raddoppiare il


carattere problematico:

PS C:\Users\ikmju> 'un''esperienza fantastica'


un'esperienza fantastica

PS C:\Users\ikmju> "i ""doppi"" apici"


i "doppi" apici

Tutte le stringhe dispongono, infine, della proprietà Length, in grado di


restituire il numero di caratteri di cui sono composte.
Nello script che segue, per esempio, si mostra a video il numero di
caratteri per un paio di stringhe:

PS C:\Users\ikmju> "powershell.it".Length
13
PS C:\Users\ikmju> "9*7=$( 9*7 (".Length
6

Il carattere di escape
All’interno delle stringhe definite tramite doppi apici, PowerShell
attribuisce al carattere di apice inverso (`), noto anche come
backtick, un significato particolare: quando questo simbolo precede
quello del dollaro ($) il sistema non sostituisce il riferimento
all’eventuale variabile o espressione da questo introdotta,
mantenendo il testo così com’è. Si tratta, in sostanza, di un
“allontanamento” momentaneo rispetto alla normale attività di
elaborazione delle stringhe, un concetto che ha portato a dare
all’apice inverso il nome di carattere di escape.

NO Il carattere di apice inverso non è purtroppo presente nel layout italiano di tastiera
TA ed è pertanto necessario utilizzare la combinazione di tasti ALT+96 per ottenerlo.

Facendo uso del carattere di escape, quindi, è possibile riscrivere lo


script presentato pocanzi così:

PS C:\Users\ikmju> $primes = 2, 3, 5, 7, 11, 13


PS C:\Users\ikmju> "^$primes contiene: $primes"
$primes contiene: 2, 3, 5, 7, 11, 13

Come effetto collaterale dell’impiego di questo simbolo, come si può


notare già dall’esempio precedente, la stringa che viene creata non
contiene l’apice inverso. Nonostante questo sia il comportamento
generalmente atteso, raddoppiando questo carattere si richiede alla
shell di considerarlo come un unico, semplice apice inverso. Nel
blocco che segue, infatti, nell’output l’ultima occorrenza di $x è
correttamente sostituita e al posto del doppio carattere di apice
inverso ne appare solo uno:

PS C:\Users\ikmju> $x = 123
PS C:\Users\ikmju> "$x, `$x, ``$x"
123, $x, `123

Le sequenze di escape
L’apice inverso ha un’ulteriore funzione all’interno delle definizioni di
stringhe effettuate tramite doppi apici: quando incontra questo
simbolo seguito da un particolare carattere tra quelli previsti, la shell
identifica la combinazione come una sequenza di escape e la
sostituisce con i caratteri speciali corrispondenti.
Tabella 12.1 - Le sequenze di escape.
Sequenza Caratteri speciali
`n Nuova riga
`t Tabulazione orizzontale
`r Ritorno a capo
`f Avanzamento di linea
`b Backspace
`o $null (raramente utilizzato)
`a Avviso sonoro (nelle console testuali)
`v Tabulazione verticale (nelle console grafiche, come ISE)

Lo script che segue utilizza la sequenza di escape t per produrre un


testo che contiene valori separati da tabulazioni:

$stars = ©{
AlphaCentauri = 4.3;
Barnard = 5.9;
Wolf359 = 7.8
}

$stars.GetEnumerator()
% { "$($_.Name)^t$($_.Value)" }

Il risultato è questo:

Barnard 5.9
Wolf359 7.S
AlphaCentauri 4.3

Le stringhe here
Le stringhe here rappresentano un costrutto per creare nuove
stringhe, alternativo a quello osservato in precedenza. A differenza
di quest’ultimo, ammette stringhe che contengono qualsiasi
sequenza di apici al loro interno e utilizza una coppia di caratteri
costituita da un apice (singolo o doppio) e il segno di a commerciale
(@) per definire l’inizio e il termine del testo da impiegare.
La sintassi delle stringhe here prevede che la sequenza dei due
caratteri di demarcazione sia invertita tra l’inizio e la fine della
stringa, secondo questo schema:

9 "
<Testo>
<Testo>
[...]
"@

Anche in questo caso, utilizzando apici singoli il sistema interpreta


letteralmente il testo contenuto, mentre usando i doppi la shell
interpreta e sostituisce i riferimenti a variabili e sottoespressioni.
Le stringhe here sono molto utili in tutti gli scenari dove, per praticità,
si desidera avere la massima libertà di espressione durante la
definizione del contenuto di una stringa, per esempio quando si
impiega il cmdlet ConvertFrom-StringData analizzato nel capitolo
precedente.
Riprendendo lo script presentato con questo cmdlet, per esempio, e
utilizzando la tecnica delle here string, è possibile produrne una
variante inline:

PS C:\Users\ikmju> $machines = β"


>> SRV01 = 10-0-1-1
>> SRV02 = 10-0-1-2
>> SRV03 = 10-0-1-3
>> "@
>>
PS C:\Users\ikmju> $addresses = ConvertFrom-StringData $machines
PS C:\Users\ikmju> $addresses -SRV03
10-0-1-3

Stringhe vuote e stringhe nulle


C’è una differenza fondamentale tra una stringa vuota e una nulla.
Nel primo caso, infatti, la stringa è definita ma non contiene niente,
il numero dei caratteri che la costituiscono è pari a zero ma è ancora
possibile chiamarne i metodi di istanza. Quando una stringa è nulla,
invece, non c’è alcun oggetto definito e la chiamata a metodi di
istanza causa un errore. All’interno della shell, tuttavia, il valore $null
è convertito implicitamente in una stringa vuota, al fine di rendere
possibili molti degli scenari di automazione visti finora.
Recuperando il valore della proprietà Length su di una stringa vuota si
ottiene, semplicemente, il numero zero:

PS C:\Users\ikmju> $test = ''


PS C:\Users\ikmju> $test.Length
0
Richiamando questa proprietà su di una stringa nulla, invece, ci si
aspetterebbe la comparsa a video di un messaggio di errore. Ma
così non è: il risultato è ancora una stringa composta da zero
elementi.

>PS C:\Users\ikmju> [string]$test = $null


PS C:\Users\ikmju> $test.Length
0

Poiché i metodi e le proprietà esposte dagli oggetti del framework


Microsoft .NET non sono dotate di questo automatismo, tuttavia,
conviene sempre prestare la massima attenzione quando si
scambiano stringhe con il resto della piattaforma di scripting.

L’ìndexer
Ogni stringa permette di recuperare agevolmente i caratteri da cui è
composta utilizzando il costrutto dell’indexer, alla stregua degli array.
Anche in questo caso gli indici degli elementi (ovvero dei caratteri)
partono da zero. Nei blocchi che seguono sono illustrati diversi
esempi d’uso di questo costrutto:

PS C:\Users\ikmju> $test = 'powershell'


PS C:\Users\ikmju> $test[5]
s
PS C:\Users\ikmju> $test[0..4]
P
o
w
e
r
PS C:\Users\ikmju> $test[-1 - --5]
1
1
e
h
s

L’operatore -join
L’operatore -join permette di combinare tra loro con facilità una serie
di stringhe e restituirne una unica; nel caso siano forniti oggetti che
non sono stringhe il sistema si occupa, automaticamente, di
generarne una rappresentazione testuale. L’operatore utilizza
l’operando di destra per separare un elemento dal successivo
all’interno del valore ritornato.
La sintassi dell’operatore è:

<Stringa1> [, <Stringa2>] ... -join <Separatore>

Ecco come, per esempio, combinare più frammenti di testo tra loro
per generarne uno complessivo, dove l’elemento separatore è uno
spazio:

PS C:\Users\ikmju> 'A', 'change', 'of', 'seasons' -join ' '


A change of seasons

L’operatore -split
Questo operatore ha un obiettivo esattamente opposto a quello di -
join: frapponendolo tra una stringa e un delimitatore, infatti, la shell

analizza la prima e la suddivide in sottostringhe, laddove rilevi la


presenza del delimitatore. La sintassi di base di questo operatore è
la seguente:

<Stringa> -split <Delimitatore>

Conoscendo a priori la struttura della stringa utilizzata, quindi, è


possibile recuperarne facilmente gli eventuali frammenti, come in
questo blocco di esempio:

PS C:\Users\ikmju> '.com;.net;.org;.it' -split ';'


.com
.net
.org
.it

L’operatore -join consente di indicare, inoltre, il numero massimo di


sottostringhe da restituire, specificandolo in seguito al delimitatore.
Lo schema che segue illustra la sintassi di questo costrutto:

<Stringa> -split <Delimitatore>, <NumeroElementi>

Nel caso il numero specificato sia superiore a quello delle


sottostringhe da ritornare il sistema si limita a ignorare l’istruzione e
lo stesso avviene per valori negativi o pari a zero. Se invece il
numero specificato è inferiore a quello delle sottostringhe disponibili,
la shell ritorna le prime e concentra nell’ultima il resto della stringa.
L’esempio che segue illustra quest’ultima evenienza (si noti come la
terza e ultima sottostringa contenga la parte rimanente della stringa
di input):

PS C:\Users\ikmju> '1,3,5,7,11,13' -split ',', 3


1
3
5,7,11,13

Una potente variante dell’operatore -split consente inoltre di


generare le sottostringhe non in base a un delimitatore fisso, ma
impiegando un blocco di script al quale la shell fornisce, per mezzo
della variabile automatica contestuale $_, ciascun carattere della
stringa di partenza. Il blocco viene eseguito per ogni elemento e
l’elaborazione varia a seconda del valore di verità logica che questo
ritorna: se si tratta di $true il carattere corrente è considerato un
delimitatore, altrimenti no. Anche in questo caso, inoltre, è possibile
specificare il numero massimo di sottostringhe desiderate, facendolo
seguire al blocco di script.
Segue la sintassi per questa variante dell’operatore -split:

<Stringa> -split { <Script> } [, <NumeroElementi>]

Nonostante esistano sistemi più efficienti, come l’impiego di wildcard


ed espressioni regolari - argomenti trattati nel prossimo capitolo - nel
blocco che segue, per esempio, sono considerati delimitatori di
stringa tutti i caratteri non compresi nell’intervallo riservato alle cifre:

PS C:\Users\ikmju> '1 ; 3,5 : 7/11|13' -split { $_ -gt '9' -or $_ -lt '0' }
1
3
5
7
11
13

L’operatore -split, infine, non fa distinzione tra minuscole e


maiuscole per quanto concerne l’indicazione del delimitatore: se si
desidera procedere in maniera differente rispetto a questo aspetto è
possibile utilizzare l’operatore -cspiit, sua variante case sensitive.
L’operatore -replace
Questo operatore è in grado di rintracciare le corrispondenze di una
stringa all’interno di un’altra e sostituirle con una terza. Dal punto di
vista sintattico, la stringa di partenza è indicata alla sinistra
dell’operatore, mentre la stringa da recuperare e il suo rimpiazzo
sono specificate, una dopo l’altra, alla sua destra, secondo questo
schema:

<Originale> -replace <Ricerca>, <Rimpiazzo>

In questo script, per esempio, l’operatore sostituisce in una stringa di


partenza una sillaba con un’altra, ritornandone il risultato:

PS C:\Users\ikmju> 'castello' -replace 'ste', 'va'


cavallo

Anche nel caso di -replace la ricerca della stringa avviene in modalità


case insensitive; per forzare la modalità opposta è sufficiente
utilizzare l’operatore gemello -creplace.

La formattazione
In alternativa alla definizione di stringhe basate su doppi apici,
PowerShell permette di formattare un testo avvalendosi del supporto
per la formattazione incluso nel framework Microsoft .NET, che
consente di definire stringhe con punti di inserimento per altri valori e
di variare la rappresentazione testuale di questi ultimi. Il ruolo
centrale di questa tecnologia è assunto dal metodo statico Format() del
tipo System.String, cui è necessario fornire una stringa di formattazione
e i valori da includere nel risultato dell’elaborazione, secondo questo
schema:

[string]::Format("<Formattazione>" [, <Valorel>] [, <Valore2>] ...)

Per aggiungere la rappresentazione testuale dei valori forniti è


necessario includere nel punto desiderato della stringa di
formattazione il numero dell’elemento corrispondente, badando al
fatto che la numerazione - come per gli array - parte da zero. Questo
numero va frapposto a una coppia di parentesi graffe, come in
questo esempio:

[string]::Format("{0} dista -{1} anni luce da qui.", "Proxima Centauri", "4.23";

che ritorna questo testo:

Proxima Centauri dista -4.23 anni luce da qui.

L’operatore -f
Data la frequenza con cui il metodo Format() è utilizzato, tuttavia,
Microsoft ha aggiunto alla shell l’operatore -f, una scorciatoia
sintattica che permette di ottenere il medesimo risultato con un
codice più snello: facendo seguire a una stringa l’operatore -f, infatti,
questa viene passata a Format()come stringa di formattazione, mentre
tutti i valori che lo seguono sono passati come valori aggiuntivi.
La sintassi dell’operatore -f è:

"<Formattazione>" -f [, <Valorel>] [, <Valore2>]

L’esempio precedente, dunque, può essere riscritto in modo più


sintetico così:

PS C:\Users\ikmju> "{0} dista -{1} anni luce da qui." -f "Proxima Centauri", "4.23"
Proxima Centauri dista -4.23 anni luce da qui.

All’interno di ciascun segnaposto, inoltre, è possibile specificare una


stringa di formato caratteristica della tipologia di dato dei valori
aggiuntivi, in modo tale da variare la rappresentazione testuale
risultante. Dietro le quinte, il metodo Format() compone la stringa da
ritornare richiamando, per ogni segnaposto trovato, il metodo
ToString() del relativo oggetto di riferimento, fornendo, se specificata,
anche la stringa di formato: ogni tipo, dunque, è responsabile della
propria formattazione. Per indicare la stringa di formato i punti di
inserimento dei valori aggiuntivi devono rispettare questa sintassi:

{<IndiceValore>:<Formato>}
Come anticipato, esistono stringhe di formato differenti per ciascun
tipo di oggetto presente nel framework Microsoft .NET: di
conseguenza non è possibile elencare tutte le possibili varianti e i
rispettivi obiettivi. Nel seguito del libro, in ogni caso, sono illustrate le
principali stringhe di formato per i tipi primitivi; per ulteriori
approfondimenti si rimanda al sito MSDN
(http://msdn.microsoft.com).

NO Per utilizzare le parentesi graffe all’interno delle stringhe di formattazione è


TA sufficiente raddoppiare il rispettivo carattere.

Metodi di rilievo
In questo paragrafo sono illustrati i metodi d’istanza della classe
System.String più utili per l’utente della shell.

Metodi ToUpper() e ToLower()


Può essere utile, talvolta, agire sul casing delle stringhe,
trasformando tutto il contenuto in lettere maiuscole o minuscole.
Richiamando il metodo d’istanza ToUpper() di una stringa, il sistema ne
ritorna una copia in cui tutte le lettere sono maiuscole. Le cifre e i
simboli vengono lasciati così come sono:

PS C:\Users\ikmju> "aBC'-è%?".ToUpper()
ABC'-È%?

Poiché l’intera piattaforma Microsoft .NET si basa sullo standard


Unicode, il metodo opera correttamente con qualsiasi tipo di stringa
(e lingua) utilizzabile. Il metodo d’istanza ToLower(), invece, opera
esattamente in maniera opposta, ritornando una copia della stringa
in cui tutte le lettere sono minuscole:

PS C:\Users\ikmju> "aBÇ-è%?"-ToLower()
abg-è%?

Metodo ToCharArray()
Ciascuna stringa memorizza internamente il proprio contenuto
all’interno di un array di caratteri, non modificabile dopo la creazione.
Il metodo ToCharArray() richiede al sistema di creare e restituire una
copia di questo array, così da poterlo esaminare o manipolare. Ecco
come, per esempio, ottenere un array costituito dai caratteri
successivi o uguali ad un particolare valore, impiegando questo
metodo e la capacità di PowerShell di filtrare gli elementi degli array:

PS C:\Users\ikmju> "powershell".ToCharArray() -ge p


P
w
r
5

Tra l’altro, la dimensione dell’array restituito da questo metodo


corrisponde al valore della proprietà Length, anche se, per questioni di
performance (se non di leggibilità), è consigliabile usare sempre
quest’ultima.

PS C:\Users\ikmju> "Efran"-Length
5
PS C:\Users\ikmju> "Efran" -ToCharArray() - Length
5

Il metodo ToCharArray(), d’altra parte, è equivalente all’impiego


dell’indexer sulla stringa, dove l’elemento di partenza è zero e
l’elemento di termine corrisponde alla dimensione della stringa,
meno uno:

PS C:\Users\ikmju> $test = 'powershell'


PS C:\Users\ikmju> $test[0..($test.Length - 1)]
p
o
w
[...]

Per aumentare le prestazioni del sistema gli sviluppatori del framework


NO
Microsoft.NET hanno reso il contenuto delle stringhe immutabile. Per tale ragione,
TA
modificando l’array ritornato da ToCharArray() non si altera la stringa originale.

Metodo SubstrìngO
Il metodo substring() consente di ottenere una particolare porzione di
una stringa ed è disponibile in due overload. Il primo ritorna la
porzione di stringa che inizia da una particolare posizione e accetta,
pertanto, un unico parametro:
<Stringa>- Substring(<Partenza>)

Il secondo overload, invece, permette di specificare anche il numero


di caratteri da ritornare al chiamante:

<Stringa>- Substring(<Partenza>, <NumeroElementi>)

Nello script che segue, per esempio, sono estratte due stringhe a
partire da un’originale:

PS C:\Users\ikmju> "powershell".Substring(5)
shell
PS C:\Users\ikmju> "powershell".Substring(0, 5)
power

Metodo Remove()
Questo metodo agisce sulla stringa rimuovendone una porzione e
restituendo il risultato ottenuto. È disponibile in due overload, il primo
dei quali rimuove tutti i caratteri a partire da una particolare
posizione e accetta, pertanto, un unico parametro:

<Stringa>.Remove(<Partenza>)

Il secondo overload, invece, permette di specificare anche il numero


di caratteri da eliminare, a partire dalla posizione fornita:

<Stringa>.Remove(<Partenza>, <NumeroElementi>)

Nello script che segue, per esempio, il metodo Remove() elimina una
porzione centrale della stringa originale:

PS C:\Users\ikmju> "powershell".Remove(3,4)
powell

Metodi Trìm(), TrìmStart() e TrìmEndQ


Il metodo Trim() permette di rimuovere tutti gli spazi vuoti dall’inizio e
dalla fine di una stringa, lasciandovi il resto. Tra i caratteri che il
sistema elimina vi sono lo spazio standard (codice ASCII 32), le
tabulazioni (`t e `v), il carattere di nuova linea (`n), di ritorno a capo
(`r) e di avanzamento di linea (`f); per una lista completa si rimanda
alla documentazione di MSDN.
Si noti che Trim() lascia intatti gli spazi vuoti eventualmente presenti
all’interno del contenuto della stringa, come dimostra questo
esempio:

PS C:\Users\ikmju> " la shell ".Trim() la shell

I metodi TrimStart() e TrimEnd() hanno lo stesso obiettivo di Trim(), ma si


limitano, rispettivamente, a eliminare gli spazi vuoti dall’inizio o dalla
fine della stringa.

Metodi IndexOf() e LastIndexOf


Il metodo IndexOf() consente di cercare una sottostringa all’interno di
un’altra e di ritornare la posizione della prima eventuale occorrenza
trovata. È disponibile in due overload, il primo dei quali accetta un
unico parametro tramite il quale specificare la stringa da cercare.
Ecco lo schema:

<Stringa>.IndexOf(<Ricerca>)

Il secondo overload, invece, consente di specificare anche la


posizione da cui far partire la ricerca:

<Stringa>.IndexOf(<Ricerca>, <Posizione>)

Lo script che segue illustra il funzionamento di entrambe le varianti:

PS C:\Users\ikmju> "la lanterna".IndexOf("la")


0
PS C:\Users\ikmju> "la lanterna".IndexOf("la", 1)
3

Il metodo LastIndexOf() supporta la stessa sintassi di IndexOf(), ma si


differenzia da quest’ultimo perché la ricerca di occorrenze parte dalla
fine della stringa, anziché dall’inizio, come dimostrato da questo
esempio:

PS C:\Users\ikmju> "la lanterna".LastIndexOf("la")


3
Se non viene trovata alcuna occorrenza entrambi i metodi ritornano il
valore -1.
Wildcard ed espressioni regolari

Un’analisi approfondita sulle capacità di ricerca ed


elaborazione del testo fornite da PowerShell, con una
panoramica sul funzionamento dei caratteri wildcard e delle
espressioni regolari.

Il punto di forza più importante e innovativo di PowerShell è


senz’altro rappresentato dalla sua pipeline a oggetti, che rende
sorpassate le shell basate sullo scambio di semplice testo. Se
l’obiettivo è quello di automatizzare quanto più possibile i propri
sistemi Windows, tuttavia, l’elaborazione e la ricerca del testo sono
due attività fondamentali.
In questo capitolo si analizza come PowerShell risponda a queste
esigenze offrendo un supporto completo per due tecnologie standard
per la ricerca del testo: i wildcard e le espressioni regolari.

I wildcard
I wildcard sono caratteri speciali cui la shell attribuisce un significato
particolare durante le operazioni di ricerca all’interno di un testo.
Questa tecnologia, di cui quasi tutte le shell esistenti sono provviste,
consente di costruire in modo molto semplice stringhe di ricerca in
grado di contenere segnaposto per uno o più caratteri, la cui entità e
lunghezza sono descritte dai segnaposto stessi.
Moltissimi cmdlet all’interno della shell consentono di sfruttare
questa tecnologia, specificando caratteri wildcard all’interno delle
stringhe fornite ai propri parametri, mentre gli operatori -like e -
notlike, come anticipato nel Capitolo 6, ne rendono possibile l’impiego

all’interno della piattaforma di scripting.


Nel seguito di questo paragrafo sono illustrati i wildcard utilizzabili
all’interno di PowerShell.

Wildcard *
Quando una stringa di ricerca contiene il carattere asterisco (*), la
shell vi abbina qualsiasi combinazione di testo, di qualsiasi
lunghezza (anche zero), all’interno della stringa da ricercare.
In questo script, per esempio, il wildcard * è utilizzato dal cmdlet Get-
ChildItem per recuperare tutti i file (e le cartelle) la cui estensione è

.txt:

PS C:\Users\ikmju> Get-ChildItem *.txt | Select Name

Name
----
alias.txt
fakepid.txt
test.txt
[...]

Wildcard ?
Il simbolo del punto interrogativo (?) indica a PowerShell di abbinare,
durante l’operazione di confronto, un qualsiasi carattere.
Lo script che segue, quindi, richiede alla shell di ritornare tutti i file il
cui nome sia composto da test e un altro carattere, e un’estensione
qualsiasi:

PS C:\Users\ikmju> Get-ChildItem test?.* | Select Name

Name
--
test1.txt
TestW.zip
Si noti come, in questo caso, utilizzando il wildcard ? la shell abbia
tralasciato il file test.txt, perché questo non dispone del carattere
necessario al match, infatti:

PS C:\Users\ikmju> "test.txt" -like "test?.*"


False

Wildcard []
Come il precedente, questo wildcard è un segnaposto per un unico
carattere e consente di indicare, uno di seguito all’altro, gli elementi
con cui può essere abbinato. Si può specificare qualsiasi carattere,
ma il trattino (-) ha un significato particolare: se è frapposto tra altri
due caratteri, infatti, la shell espande la sequenza con tutti gli
elementi compresi tra il carattere che lo precede e quello che lo
segue. Entrambi questi script, per esempio, sono soddisfatti dalla
stringa di ricerca utilizzata:

PS C:\Users\ikmju> "volpe" -like "[cv]olp[ae]"


True
PS C:\Users\ikmju> "colpa" -like "[cv]olp[ae]"
True

In quest’altro esempio, poi, il wildcard [] è utilizzato per recuperare


tutti i processi la cui ultima lettera del nome è una vocale o una cifra:

PS C:\Users\ikmju> Get-Process *[0-9aeiou] | Select Name

Name
----
conime
conime
Idle
LogonUI
[...]

Le espressioni regolari
Le espressioni regolari rappresentano una sorta di considerevole
evoluzione rispetto ai wildcard e permettono di definire stringhe di
ricerca utilizzando dei segnaposto ricchi di funzionalità e
particolarmente articolati. Questa tecnologia, il cui nome è spesso
abbreviato con l’acronimo regexp o regex, è disponibile all’interno
della piattaforma di scripting di PowerShell grazie agli operatori -match
(e il suo opposto -notmatch) e -replace, introdotti nel Capitolo 6. Alcuni
cmdlet, inoltre, sfruttano le espressioni regolari consentendone l’uso
attraverso i propri parametri; all’interno del framework Microsoft
.NET, infine, è possibile utilizzare la classe
System.Text.RegularExpressions.Regex per compiere grazie a questa
tecnologia ricerche e sostituzioni di testi. È possibile definire
un’espressione regolare utilizzando una stringa composta da
qualsiasi carattere: alcune sequenze, chiamate in gergo pattern,
variano tuttavia la modalità secondo cui la shell effettua i confronti.
Senza scendere nei dettagli del funzionamento delle espressioni
regolari - argomento al di fuori degli obiettivi di questo libro - nei
paragrafi che seguono sono descritti i pattern più utilizzati.

Pattern .
Quando un’espressione regolare contiene il simbolo punto (.) il
sistema lo abbina a qualsiasi carattere, tranne la sequenza di nuova
linea (`n). Questo pattern presenta notevoli somiglianze con il
wildcard ?, tant’è che nel suo impiego di base implica anch’esso la
presenza di un unico carattere nella stringa da ricercare, come
dimostrato da questi esempi:

PS C:\Users\ikmju> "power" -match "po.er"


True
PS C:\Users\ikmju> "poer" -match "po.er"
False
PS C:\Users\ikmju> "poster" -match "po.er"
False

Pattern []
Il pattern [] introduce una serie di caratteri ammessi nel match di un
particolare elemento della stringa da ricercare, in maniera simile a
quanto avviene per il wildcard omonimo. Anche in questo caso il
simbolo di trattino-meno permette di definire sequenze di caratteri
che il sistema espande automaticamente in fase di ricerca; a
differenza del wildcard [], tuttavia, questo pattern permette anche di
definire sequenze di caratteri che non devono essere abbinate alla
stringa da ricercare al fine di generare un’occorrenza. Per definire
questo tipo di sequenza è sufficiente farla precedere dal simbolo
dell’accento circonflesso (^).
Nello script che segue, per esempio, la prima espressione abbina il
pattern [] a qualsiasi carattere dell’alfabeto, mentre la seconda a
qualsiasi carattere che non sia una cifra; la terza espressione, infine,
sostituisce a tutte le cifre un carattere:

PS C:\Users\ikmju> "power" -match "po[a-z]er"


True
PS C:\Users\ikmju> "power" -match "po[^0-9]er"
True
PS C:\Users\ikmju> 'HAL9000' -replace '[0-9]', 'x
HALxxxx

Nel suo impiego di base, anche questo pattern implica la presenza di


un unico carattere nella stringa da ricercare, come dimostrato da
questo esempio:

PS C:\Users\ikmju> "poster" -match "po[a-z]er"


False

I punti dì ancoraggio
Quando il carattere del circonflesso (^) è al primo posto
dell’espressione regolare, al di fuori del pattern [], il sistema procede
con il match solo se la stringa da ricercare comincia con il resto della
sequenza dell’espressione. Se il simbolo del dollaro ($), viceversa, è
all’ultimo posto dell’espressione regolare, la stringa da ricercare
genera un match solo se termina con il resto della sequenza
dell’espressione.
Visto il possibile impiego, entrambi i caratteri in questa
configurazione sono noti come punti di ancoraggio.
Manipolando una delle espressioni proposte in precedenza, d’altra
parte, è facile notare come la mancanza di questi costrutti possa
determinare risultati che ci si potrebbe non aspettare:

PS C:\Users\ikmju> "the power" -match "po[a-z]er"


True
PS C:\Users\ikmju> "the power is here" -match "po[a-z]er"
True
PS C:\Users\ikmju> "power to me" -match "po[a-z]er"
True
I punti di ancoraggio possono dunque alterare questo
funzionamento, vincolando il sistema al recupero di occorrenze che
si verifichino solo all’inizio o al termine della stringa da ricerca,
piuttosto che identificarla nella sua interezza.
Nello script che segue, per esempio, i match possono avvenire solo
all’inizio della stringa:

PS C:\Users\ikmju> "power to me" -match "^po[a-z]er"


True
PS C:\Users\ikmju> "the power" -match "^po[a-z]er"
False

In quest’altro esempio, invece, si utilizza il simbolo del dollaro ($) per


forzare i match al termine della stringa:

PS C:\Users\ikmju> "the power" -match "po[a-z]er$"


True
PS C:\Users\ikmju> "power to me" -match "po[a-z]er$"
False

Utilizzando entrambi i punti di ancoraggio, infine, l’espressione


regolare è confrontata con la stringa di ricerca nella sua interezza,
tralasciando eventuali match parziali; quest’ultimo blocco dimostra
questa possibilità:

PS C:\Users\ikmju> "power" -match "^po[a-z]er$"


True
PS C:\Users\ikmju> "the power" -notmatch "^po[a-z]er$"
True
PS C:\Users\ikmju> "power to me" -notmatch "^po[a-z]er$"
True

I quantificatori
Gli elementi di questa famiglia di pattern permettono di indicare il
numero di volte che una particolare sequenza deve essere ripetuta
all’interno della stringa da ricercare affinché vi sia un match.
Il quantificatore introdotto dal simbolo del punto interrogativo (?)
rende la sequenza che lo precede opzionale.
In questo script, per esempio, si ottengono sia i processi di explorer
sia quelli di iexplore:

PS C:\Users\ikmju> Get-Process | ? { $_ -match '^i?explorer?$' } | select


Name
Name
--
explorer
iexplore

Di base, tutti i quantificatori utilizzano come sequenza di ripetizione il


singolo carattere che li precede all’interno dell’espressione regolare.
Impiegando una coppia di parentesi tonde, tuttavia, è possibile
vincolare il sistema a considerare più di un carattere.
In questo blocco, per esempio, entrambe le espressioni generano un
match per l’espressione fornita:

PS C:\Users\ikmju> "powershell" -match "^po(wer)?sh(ell)?$"


True
PS C:\Users\ikmju> "posh" -match "^po(wer)?sh(ell)?$"
True
PS C:\Users\ikmju> 'posh è un acronimo di Powershell' -replace
pò(wer)?sh(eli)?', 'XYZ'
XYZ è un acronimo di XYZ

Il quantificatore asterisco (*) rende la sequenza che lo precede


ripetibile in modo indefinito, alla stregua del risultato ottenuto dal
wildcard omonimo. Nello script che segue, per esempio, si ottengono
tutti i file il cui nome corrisponde a una determinata sequenza:

PS C:\Users\ikmju> Get-Childltem | { $_ -match '^Test(9x)*.txt$' } |


Select Name

Name
----
Test.txt
Test9x.txt
Test9x9x.txt
Test9x9x9x.txt

Al simbolo più (+) corrisponde un quantificatore simile al precedente,


tranne per il fatto che la sequenza che lo precede deve apparire
almeno una volta nella stringa di ricerca per generare un match.
Sostituendolo al quantificatore asterisco dell’esempio precedente,
infatti, il risultato ottenuto non contiene il primo elemento della serie
che l’asterisco forniva:

PS C:\Users\ikmju> Get-Childltem | { $_ -match '^Test(9x)+.txt$' } |


Select Name

Name
----
Test9x.txt
Test9x9x.txt
Test9x9x9x.txt

Utilizzando una coppia di parentesi graffe, infine, è possibile


richiedere al sistema di generare un match solo al verificarsi di un
determinato numero di ripetizioni della sequenza della stringa da
ricercare.
In questo script, per esempio, solo l’ultima stringa è un match
dell’espressione regolare utilizzata:

PS C:\Users\ikmju> 'abcl23' -match '^[a-z]{5} [ 0-9]{3 } $


False
PS C:\Users\ikmju> 'abcdef123456' -match '^[a-z]{5 } [0-9]{3 } $
False
PS C:\Users\ikmju> 'abcdel23' -match '^[a-z]{5}[0-9]{3 } $
True

Il funzionamento del quantificatore parentesi graffe può essere


ulteriormente personalizzato: se si fa seguire al numero che contiene
il simbolo virgola (,), allora il sistema genera dei match quando il
numero di ripetizioni all’interno della stringa di ricerca è uguale o
maggiore di quello indicato. Se alla virgola, infine, segue un altro
numero, per generare un match il numero di ripetizioni deve essere
compreso tra il primo e il secondo.
L’esempio che segue illustra queste ultime varianti:

PS C:\Users\ikmju> 'abcl234' -match '^[a-z]{3,5 } [0-9]{3,4 } $


True
PS C:\Users\ikmju> 'abcdefgl23456' -match '^[a-z]{3,5 } [0-9]{3,4 } $
False
PS C:\Users\ikmju> 'abcdel23' -match '^[a-z]{3,5 } [0-9]{3,4 } $
True
PS C:\Users\ikmju> 'abcdefgl23456' -replace '[a-z]{3,5}[0-9]{3,4}', 'XXX
abXXX56

Classi di carattere
Le classi di carattere sono sequenze speciali di simboli che il
sistema riconosce automaticamente ed espande in un pattern [] ben
definito. Poiché, generalmente, le classi di carattere sono individuate
da una coppia di simboli, questo sistema consente di risparmiare
tempo nella creazione dell’espressione regolare che, di
conseguenza, risulta anche più leggibile.
Tabella 13.1 - Le principali classi di carattere.
Sequenza Classe risultante
\w Carattere alfanumerico
\W Carattere non alfanumerico
\d Cifra
\D Non cifra
\s Spazio bianco
\s Non spazio bianco

Utilizzando le sequenze illustrate nella Tabella 13.1, per esempio, è


possibile creare un’espressione regolare in grado di generare un
match in presenza di una partita IVA, composta da undici cifre:

PS C:\Users\ikmju> '01234567890' -match '^\d{11}$'


True

Le alternanze
Alla stregua di quanto avviene all’interno del pattern [] per i caratteri,
le espressioni regolari consentono di definire intere sequenze
alternative per la ricerca delle occorrenze. Le alternanze - questo è il
nome del costrutto - sono definite grazie a una coppia di parentesi
tonde che contengono le espressioni regolari desiderate, separate
dal carattere pipe ( |); il sistema considera la stringa di ricerca un
match quando verifica una qualsiasi di queste sequenze alternative.
La sintassi per la definizione delle alternanze è dunque:

;<Alternatival>|<Alternativa2>...)

Mediante questa tecnica è possibile, per esempio, creare una


semplice espressione regolare che genera un match a fronte di un
url il cui protocollo sia uno tra quelli indicati:

PS C:\Users\ikmju> 'http://www.powershell.it' -match '^(http|ftp|svn|ssh)://.*$'


True
PS C:\Users\ikmju> 'Non sono un''url' -match '^(http|ftp|svn|ssh)://.*$'
False
PS C:\Users\ikmju> 'svn://10.0.1.26/xyz/trunk' -match '^(http|ftp|svn|ssh)://.*$'
True
Il carattere di escape
Come si è visto fino a questo punto, l’intera tecnologia delle
espressioni regolari delega ad alcuni caratteri speciali il compito di
definire come il sistema debba interpretare il testo fornito; nel caso
sia necessario utilizzare proprio questi caratteri all’interno delle
espressioni è sufficiente farli seguire al carattere di escape
backslash (\). Nello script che segue, per esempio, si recuperano i
documenti di Microsoft Word e Microsoft Excel dalla cartella
corrente:

PS C:\Users\ikmju > Get-ChildItem | ? { $_ -match '\.(doc|docx|xls|xlsx)$' }


| select name

Name
----
Vacanze.xlsx
Visio for Enterprise Architectes e VS.NET.docx
VPS Hosting.xls

Si noti come, facendo seguire il simbolo del punto al carattere di


escape, l’espressione ne abbia considerato il rispettivo valore
letterale e non il pattern omonimo, che avrebbe invece effettuato il
match con qualsiasi carattere.

Le sequenze di escape
Il simbolo backslash permette di introdurre all’interno delle
espressioni regolari anche sequenze standard di caratteri che il
sistema espande, in maniera pressoché identica a quanto avviene
con l’apice inverso per le sequenze di escape della shell.

Tabella 13.2 - Le sequenze di escape.


Sequenza Caratteri speciali
\n Nuova riga
\t Tabulazione orizzontale
\r Ritorno a capo
\f Avanzamento di linea
\b Backspace
\o $null
\a Avviso sonoro
\v Tabulazione verticale

Sfruttando la sequenza di escape \n, per esempio, è possibile


generare un match quando la stringa da ricercare contiene una
nuova linea, come in questo caso:

PS C:\Users\ikmju> β"
>> 1234567
>> abede
>> "β -match "^Xdi7}\n\w*$"
>>
True

I gruppi
Gli insiemi di caratteri racchiusi tra parentesi tonde, nel gergo delle
espressioni regolari, prendono il nome di gruppi. Quando si verifica
un match tra una stringa da ricercare e un’espressione regolare che
contiene dei gruppi, il sistema estrae dalla prima il testo di
pertinenza di ciascun gruppo della seconda, assegnando a ognuno
un numero in base alla posizione del gruppo all’interno
dell’espressione. Ogni coppia costituita dal numero del gruppo e dal
relativo testo di pertinenza, infine, è aggiunta all’array associativo
$matches, consultabile e manipolabile dall’utente; il gruppo numero

zero corrisponde sempre all’intera stringa di ricerca. Nel prossimo


esempio è utilizzato l’operatore -match a fronte di un’espressione
regolare che contiene due gruppi (uno di questi è un’alternanza), in
grado generare un match per degli url:

PS C:\Users\ikmju> 'http://www.powershell.it/Forum.aspx' -match


'^(http|ftp)://(.*)$'
True

In seguito a quest’operazione, l’array associativo $matches contiene tre


elementi:

PS C:\Users\ikmju> $matches

Name Value
---- -----
2 www.powershell.it/Forum.aspx
1 http
0 http://www.powershell.it/Forum.aspx
Come anticipato, infine, è possibile operare su $matches per recuperare
i dati del gruppo di interesse:

PS C:\Users\ikmju> $matches[l]
http
PS C:\Users\ikmju> $matches[2]
www.powershell.it/Forum.aspx

Un’importante caratteristica dei gruppi consiste nella possibilità di


utilizzare i valori recuperati dalla stringa da ricercare direttamente
all’interno del testo di rimpiazzo fornito all’operatore -replace: ogni
riferimento a questi valori è sintatticamente composto dal simbolo
del dollaro ($) e dal numero del gruppo desiderato.
In quest’ultimo script, quindi, si dimostra come utilizzare questa
funzionalità per manipolare una lista di url http, in maniera tale da
ottenerne una seconda in cui ciascun elemento faccia capo alla
porta 8080:

PS C:\Users\ikmju> $urls = 'http://example.com/x',


>> 'http://example.com:9876',
>> 'http://www.example.com/x/y.z'
>>
PS C:\Users\ikmju> $urls -replace '^http://([^/:]*)(:\d*)?(/.*)?$',
'http://$1:8080$3'
http://example.com:8080/x
http://example.com:8080
http://www.example.com:8080/x/y.z

Select-String
In aggiunta agli operatori dedicati al trattamento delle espressioni
regolari, la shell mette a disposizione degli utenti un cmdlet chiamato
Select-String, in grado di impiegare questa tecnologia per ricercare del

testo in una collezione di stringhe oppure all’interno di uno o più file.


Il lettore che abbia esperienza con le shell Microsoft precedenti
potrebbe riscontrare in questo comando alcune somiglianze con
findstr, almeno per quanto riguarda alcuni degli obiettivi, mentre chi
proviene da precedenti esperienze con le shell *nix potrebbe trovare
in Select-String un valido e potente sostituto del comando grep.
Tramite il parametro -Pattern (primo posizionale) questo cmdlet
accetta il testo ricercato, che la shell interpreta automaticamente
come un’espressione regolare a meno che lo switch -SimpleMatch non
sia specificato. Il contenuto da esaminare può essere fornito in due
modi distinti:
• tramite una stringa (o un array di queste) da passare via pipeline, se si
desidera trovare le occorrenze all’interno degli oggetti stessi;
• per esaminare il contenuto di uno o più file, invece, è possibile usufruire
del parametro -path, indicandone il nome e avvalendosi, eventualmente,
di caratteri wildcard.

La sintassi di base di questo comando è rappresentata dal seguente


schema:

Select-String [<Pattern>] [<Path>] [-SimpleMatch]

Nell’impiego di base di , il comando ritorna un oggetto


select-string

Microsoft.POW-ersheii.commands.Matchinfo per ogni match recuperato, da cui è

possibile estrarre l’eventuale riferimento al file e al numero della riga


(oltre che al contenuto di questa) che lo ha generato.
L’esempio che segue utilizza la lista dei documenti RFC in formato
testo semplice rilasciati dall’IETF, scaricabili liberamente dal sito
http://www.ietf.org/rfc; nello script si utilizza il cmdlet Select-String per
ricercare un termine letterale all’interno di questi file di testo:

PS C:\Users\ikmju> Select-String 'kerberos' .\RFC-all\*.txt -SimpleMatch

RFC-all\rfcll76.txt:319: other means, e.g. Kerberos. Until identity and


access authorization
RFC-all\rfcl244.txt: 1479 : systems (such as Kerberos) require that the
machine be physically
RFC-all\rfcl244.txt:2247: 3.9.6.1 Kerberos
RFC-all\rfcl244.txt:2249: Kerberos, named after the dog who in
mythology is said to stand
[...]
RFC-all\rfcl258.txt:239: Secure authentication extensions to the rlogin
protocol (Kerberos,
RFC-all\rfcl282.txt:240: Secure authentication extensions to the rlogin
protocol (Kerberos,
RFC-all\rfcl305.txt:2127: ticket-management system such as Kerberos [STE88],
In this model only
[...]

L’output prodotto segue una formattazione custom, che evidenzia in


modo sintetico il nome del file, il numero della riga trovata e il suo
contenuto.
La modalità di funzionamento predefinita, tuttavia, impone al cmdlet
di emettere un oggetto Microsoft.Powershell.Commands.Matchinfo per ogni riga
in cui si verifichi un match, generandone quindi anche più di uno per
ciascun file: specificando lo switch -List si richiede a Select-String di
limitarsi a emettere l’eventuale primo match trovato per ciascun file,
tralasciando i successivi.
In questo script, quindi, si richiede al cmdlet di ritornare una riga di
output per ogni file che contiene un match con l’espressione regolare
fornita:

PS C:\Users\ikmju> Select-String '\s53/(tep|udp)' .\RFC-all\*.txt -List

RFC-all\rfcl060.txt:702: nameserver 53/tcp domain


RFC-all\rfcl340.txt: 602 : domain 53/udp Domain Name Server
[81,95,PMi;
RFC-all\rfcl700.txt:1032: domain 53/tcp Domain Name Server
RFC-all\rfc2307.txt:762: domain 53/tcp nameserver

Specificando lo switch -Quiet, poi, il cmdlet non emette alcun match


nella pipeline, ma restituisce un valore logico: se è $true è stato
trovato almeno un match, altrimenti no.
Lo switch -NotMatch, infine, consente di invertire la logica di Select-String,
facendogli considerare solo le righe (o le stringhe) che non hanno
alcun match con l’espressione fornita.
In quest’ultimo esempio, infatti, il cmdlet emette nella pipeline solo le
stringhe che non presentano alcuna cifra al loro interno:

PS C:\Users\ikmju> '98abcde', 'test5', 'powershell.it' I Select-String "\d+" -


NotMatch

powershell - it

Di default, i confronti effettuati internamente da Select-Command non sono


sensibili alle maiuscole. Per forzare l’opposto è necessario
specificare lo switch -Casesensitive.
I numeri

Una panoramica completa sui tipi numerici supportati


dalla shell, sui principali metodi esposti dalle relative
classi del framework Microsoft .NET e sugli operatori
dedicati a questi oggetti.

Al pari delle stringhe, i numeri ricoprono un ruolo fondamentale tra i


tipi gestiti da PowerShell.
Questo capitolo analizza approfonditamente il supporto offerto dalla
shell e dal framework Microsoft .NET per questo tipo di oggetti.

Tipi numerici
Il framework Microsoft .NET supporta nativamente la
rappresentazione e la manipolazione dei numeri attraverso ben 11
tipi differenti, a seconda del fatto che si desideri operare con valori
interi o decimali e della scala da utilizzare. Nonostante l’ampia
offerta, PowerShell è sempre in grado di determinare per ogni
occasione quale sia il tipo più adatto per la memorizzazione dei
valori, così che non sia necessario esplicitarne i tipi: conoscerne le
peculiarità, tuttavia, può facilitare la stesura e la comprensione del
codice e rendere possibili scenari di maggiore complessità.
Byte e SByte
Il tipo System.Byte occupa 8 bit di memoria e permette di gestire valori
interi naturali nell’intervallo che va da 0 a 255, per un totale di 28
valori possibili; il tipo System. SByte (acronimo di Signed Byte) è identico
al precedente ma riserva un bit per memorizzare l’informazione del
segno, ammettendo valori interi da un minimo di -128 ad un
massimo di 127. All’interno della shell la frequenza d’uso per
entrambi i tipi è generalmente limitata al supporto per l’elaborazione
di file binari e all’impiego di strutture definite all’interno del framework
stesso.

Int16 e Ulnt16
Il tipo System.Int16 (chiamato short, nella maggior parte dei linguaggi di
sviluppo di derivazione C) gestisce tramite 16 bit i valori interi che
vanno da -32768 a 32767, per un totale di 216 valori possibili;
System.uInt16 (acronimo di Unsigned Integer) è la versione senza

segno, che supporta una scala di valori da 0 a 65535. Anche in


questo caso, l’uso è abbastanza infrequente all’interno della shell.

Int32 e Ulnt32
Salendo la scala dei tipi numerici si trova System.Int32, che consente di
gestire valori interi a 32 bit con un intervallo che spazia da
-2147483648 a 2147483647, per un totale di 232 possibilità. La
versione priva di segno è chiamata System.Int32 e si distingue dalla
precedente per una scala che ammette valori da 0 a 4294967295. Il
tipo System.Int32 è utilizzato automaticamente dalla shell quando il
risultato di un’espressione è un intero che rientra nell’intervallo
ammesso da questo tipo, come dimostra questo script:

PS C:\Users\ikmju> (9).GetType().Name
Int32
PS C:\Users\ikmju> (9783).GetType().Name
Int32
PS C:\Users\ikmju> (-299721).GetType().Name
Int32

Si noti, in particolare, come la shell prediliga l’impiego del tipo


System.Int32 anche quando potrebbe bastare un byte o uno short.
Int64 e Ulnt64
Il tipo System.Int64 (chiamato long, nei principali linguaggi di sviluppo di
derivazione C) permette di gestire i valori interi nell’intervallo che va
da -9223372036854775808 a 9223372036854775807: occupa 64 bit
di memoria per un totale di 264 possibilità. Il tipo System.UInt64, di
rimando, è privo di segno e supporta una scala di valori da 0 a
18446744073709551615.
Il tipo System.Int64 è utilizzato automaticamente dalla shell quando il
risultato di un’espressione è un intero che rientra nell’intervallo
ammesso da questo tipo, ma non in quello di System.Int32.

Single
Il tipo System.Single gestisce approssimazioni di numeri reali,
utilizzando uno standard definito dall’IEEE (Institute of Electrical and
Electronic Engineers). I valori ammessi spaziano da -3,402823·1038
a 3,402823·1038 con una precisione di sette cifre decimali, in 32 bit
di spazio occupato. Dei tipi numerici non interi, questo è quello
utilizzato meno frequentemente all’interno della shell.

Double
Il tipo System-Double è molto simile al precedente, ma gestisce
approssimazioni di numeri reali il cui valore può spaziare da
-1,79769313486232·10308 a 1,79769313486232·10308 in 64 bit di
spazio occupato, con una precisione di 15-16 cifre decimali.
Quando il risultato di un’espressione è un numero non intero, la shell
utilizza in maniera trasparente System.Double per immagazzinarne il
valore, come dimostra questo breve script:

PS C:\Users\ikmju> (7.83).GetType().Name
Double

Decimal
Questo tipo permette di definire numeri decimali con una precisione
molto più elevata dei due precedenti, ma con una scala di valori
minore. System.Decimal occupa 128 bit di memoria e ammette valori a
partire da -7,9·1028 fino ad arrivare a 7,9·1028, con una precisione di
28-29 cifre decimali. Si tratta senz’altro del tipo da utilizzare quando
si desidera portare a termine operazioni matematiche con numeri
decimali ottenendo il massimo della precisione possibile all’interno di
PowerShell.

Conversioni tra tipi


Per convertire un valore da un tipo numerico a un altro è
generalmente sufficiente effettuare un cast dal primo al secondo,
come in questo esempio:

[decimal]123.456

Nell’eventualità in cui il tipo di destinazione non abbia una capienza


sufficiente per contenere il dato, il sistema non effettua la
conversione e genera un errore simile a questo:

PS C:\Users\ikmju> [byte]999
Impossibile convertire il valore "999" nel tipo "System.Byte". Errore:
"Valore troppo grande o troppo piccolo per un Unsigned Byte."
In riga:1 car:7
+ [byte] << 999
+ Categorylnfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorld : RuntimeException

Operazioni fondamentali
Tutti i tipi numerici possono essere manipolati grazie agli operatori
aritmetici, introdotti nel libro a partire dal Capitolo 3. Per ogni
espressione in cui sono coinvolti valori numerici, la shell determina
automaticamente quale tipo utilizzare per ospitarne il risultato, in
maniera tale da non richiederne all’utente un’esplicitazione: nel caso
in cui un’espressione contenga termini numerici di tipo differente,
inoltre, il sistema è in grado di convertirne automaticamente i valori
prima di procedere a eventuali operazioni.

Gli operatori aritmetici


L’operatore di addizione esegue la somma tra due espressioni
numeriche ed è rappresentato dal simbolo più (+), che va frapposto
tra i due operandi, seguendo questo semplice schema:

<Espressione> + <Espressione>

La differenza tra due espressioni numeriche, viceversa, è portata a


termine dall’operatore omonimo, individuato dal simbolo meno (-)
frapposto tra i valori desiderati. Ecco lo schema:

<Espressione> - <Espressione>

Il simbolo asterisco (*) individua l’operatore prodotto, che compie il


calcolo omonimo sui due operandi tra cui è frapposto. Segue,
anch’esso, uno schema simile ai precedenti:

<Espressione> * <Espressione>

Il quoziente tra due espressioni numeriche, invece, è individuato


dall’operatore omonimo utilizzando il simbolo slash (/), in base a
questo schema:

<Espressione> / <Espressione>

Oltre alle quattro operazioni classiche, la shell dispone anche


dell’operatore modulo, che calcola il resto della divisione tra
l’operando di sinistra e quello di destra. Utilizza il simbolo percento
(%) e segue lo stesso schema degli altri operatori aritmetici:

<Espressione> % <Espressione>

Poiché si tratta di costrutti molto semplici, si è scelto di raggrupparne


le dimostrazioni in quest’unico script di esempio, che contiene un
ciclo dove sono chiesti due valori numerici all’utente, cui sono
applicati i diversi operatori fin qui illustrati:

PS C:\Users\ikmju> while ($true)


>> {
>> $x = [int](Read-Host Valore di x)
>> $y = [int](Read-Host Valore di y)
>>
>> "Somma: {0}" -f ($x + $y)
>> "Differenza: {0}" -f ($x - $y)
>> "Prodotto: {0}" -f ($x * $y)
>> "Quoziente: {0}" -f ($x / $y)
>> "Resto: {0}" -f ($x % $y)
>> }
>>
Valore di x: 5
Valore di y: 3
Somma: 8
Differenza: 2
Prodotto: 15
Quoziente: 1.66666666666667
Resto: 2

NOT Per terminare un ciclo come questo è sufficiente premere


A Ctrl+C.

Tabella 14.1 - Operatori aritmetici.


Operatore Funzione
+ Addizione
- Differenza
* Prodotto
/ Quoziente
% Modulo

Oltre a quelli di base, la shell supporta operatori aritmetici che sono


frutto della combinazione tra i primi e l’operatore di assegnazione
semplice, ottenendo per ogni elemento del primo gruppo un nuovo
costrutto in grado di rendere più sintetico e leggibile il codice. Ogni
costrutto di questo tipo è a sua volta un operatore di assegnazione,
individuato dall’unione del simbolo dell’operatore aritmetico
corrispondente (+, -, *, /, %) con quello di assegnazione semplice (=).
Quando è frapposto tra un’istanza di un oggetto (come una variabile
o una proprietà, per esempio) e un’espressione, la shell reimposta
l’operando di sinistra con il risultato dell’operazione aritmetica di
riferimento applicata tra i due operandi.

Tabella 14.2 - Operatori di assegnazione.


Operatore Funzione
+= Assegnazione di addizione
—= Assegnazione di differenza
*= Assegnazione di prodotto
/= Assegnazione di quoziente
%= Assegnazione di modulo (rara)

L’operatore assegnazione di addizione (+=) applicato tra due


operandi, per esempio, è equivalente ad assegnare all’operando di
sinistra la somma tra questo e l’operando di destra. Questa
espressione, per esempio:

$x = $x + $y

equivale a questa, più concisa della precedente:

$x += $y

Infine, quando si desidera aggiungere o sottrarre il valore 1 a una


determinata istanza di un oggetto (come una variabile o una
proprietà, per esempio), la shell permette di utilizzare altri due
operatori unari, mutuati dal linguaggio C: rispettivamente, l’operatore
di incremento, individuato dal doppio simbolo più (++), e l’operatore di
decremento, individuato dal doppio simbolo meno (--).
La particolarità di questi operatori consiste nel fatto che possono
essere postfissi oppure prefissi al valore da alterare: nel primo caso
la shell ritorna preventivamente il valore considerato al chiamante (o
alla pipeline) e, solo in seguito, lo modifica (incrementandolo o
decrementandolo). Quando questi operatori sono prefissi, invece, la
shell ne modifica il valore di riferimento prima di ritornarlo. Entrambe
queste espressioni, quindi, aggiungono il valore 1 alla variabile $x:

$x++
++$x

Utilizzandole all’interno di un’ulteriore espressione, tuttavia, nel


primo caso la shell utilizza il valore originale di $x, per poi, in un
secondo tempo, incrementarlo, mentre nel secondo caso utilizza
direttamente il valore già incrementato, producendo risultati
completamente diversi:

PS C:\Users\ikmju> $x = 2
PS C:\Users\ikmju> 7 * ($x++)
14

PS C:\Users\ikmju> $x = 2
PS C:\Users\ikmju> 7 * (++$x)
21

Le notazioni numeriche
PowerShell riconosce automaticamente le notazioni numeriche più
utilizzate. La notazione decimale è quella standard, impiegata fino
a questo punto del libro per definire i numeri e usata di default da
PowerShell per visualizzarli a video. Il simbolo di separazione tra la
parte intera e quella decimale è il punto (.), a prescindere dalle
impostazioni internazionali della macchina su cui si sta operando.
Poiché, come riportato in precedenza, il tipo utilizzato di default per i
numeri non interi è Double, nel caso si desideri forzare il tipo Decimal è
possibile far seguire all’indicazione del valore la lettera d. Lo script
che segue dimostra le due alternative:

PS C:\Users\ikmju> (123.45).GetType().Name
Double
PS C:\Users\ikmju> (123.45d).GetType().Name
Decimal

Per impiegare la notazione esadecimale è sufficiente far precedere


al valore di interesse il testo standard 0x. Nell’espressione che
segue, per esempio, sono mischiate la notazione esadecimale e
quella decimale:

PS C:\Users\ikmju> Oxff * 1.5


382.5

Di rimando, per ottenere la rappresentazione testuale esadecimale


di un valore è sufficiente utilizzare l’operatore -f specificando la
stringa di formato x, come illustrato da questo esempio:

PS C:\Users\ikmju> "{0:x}" -f 14031879


d61c07

La notazione scientifica, infine, è rappresentata alla stregua degli


altri linguaggi di derivazione C e impiega una prima parte, decimale,
che individua la mantissa del numero e una seconda, intera, che
rappresenta l’esponente: tra i due valori è sempre presente la lettera
e, che sta per esponente. In questo breve script, per esempio, si può
notare come la shell comprenda automaticamente questo valore,
inserito tramite notazione scientifica, e ne ritorni un Double:

PS C:\Users\ikmju> 2.997925e+S
29979250C

Per rappresentare a video un numero in notazione scientifica è


possibile utilizzare l’operatore -f e specificare la stringa di formato e,
come in questo esempio:

PS C:\Users\ikmju> "{0:e}" -f 14031879


1.403188e+007

PowerShell non offre un supporto diretto né per la rappresentazione


ottale né per quella binaria: impiegando il tipo System.Convert, tuttavia, è
possibile convertire in numero una rappresentazione testuale definita
utilizzando la base binaria, ottale, decimale o esadecimale. I metodi
statici ToByte(), ToInt16(),ToInt32() e ToInt64() , infatti, consentono di
recuperare, rispettivamente, un intero a 8, i6,32 o64 bit, a partire da
una stringa e da un numero che specifica la base con cui la prima è
definita.
La sintassi di questi metodi, dunque, è identica:

[Convert]::ToInt32(<Stringa>, <Base>)
[convert]::ToInt64(<Stringa>, <Base>)
[... ]

Specificando la base 2, per esempio, si converte una


rappresentazione binaria di un numero nell’intero corrispondente:

PS C:\Users\ikmju> [Convert]::ToInt32("1001010001", 2)
593

L’operazione inversa, invece, è portata a termine dal metodo statico


To String(), al quale è possibile fornire il valore numerico desiderato e
una delle basi valide per i metodi di conversione esposti poc’anzi. La
sintassi di chiamata, dunque, è questa:

[Convert]::ToString(<Numero>, <Base>)

Specificando la base 8, per esempio, si converte un numero nella


sua rappresentazione ottale:
PS C:\Users\ikmju> [Convert]::ToString(9783, 8;
23067

I suffissi per i multipli binari


All’interno di PowerShell è possibile fare riferimento con estrema
facilità a quantità multiple delle potenze di 2, comunemente
impiegate nella maggior parte delle attività di automazione. Non solo
questa caratteristica consente a chi utilizza la piattaforma di scripting
di risparmiare tempo e fatica nel recuperare le potenze di due, ma
rende molto più conciso e leggibile il codice che si produce.
La shell permette di fare riferimento a queste potenze utilizzando le
rispettive sigle mutuate dagli standard definiti dal SI (il Sistema
Internazionale di unità di misura) e poi entrate nell’uso comune.
Quando queste sigle sono postfisse a un numero, il sistema
considera automaticamente quest’ultimo un multiplo della quantità
individuata dalla sigla, in base a questa semplice sintassi:

<NumeroxSuffisso per multiplo binario>

Utilizzando il cmdlet Get-Process e il suffisso MB, per esempio, è


possibile individuare con facilità i processi che occupano più di un
determinato quantitativo di memoria fisica:

PS C:\Users\ikmju> Get-Process | ? { $_.WorkingSet -gt 100MB }

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
921 31 148716 151256 400 16.47 3296 iexplore
[...]

Tabella 14.3 - Suffissi per multipli binari.


Suffisso Potenza
KB 2^10 - KiloByte
MB 2^20 - MegaByte
GB 2^30 - GigaByte
TB 2^40 - TeraByte
PB 2^50 - PetaByte
Nel 1998 la Commissione Elettrotecnica Internazionale (IEC) ha
messo a punto un nuovo standard per codificare i multipli binari, che
prevede l’impiego di nuovi simboli. Poiché questo standard non ha
ancora preso piede, tuttavia, PowerShell supporta solo quello
illustrato nella Tabella 14.3.

Gli operatori binari


Gli operatori binari agiscono sulle rappresentazioni binarie interne
dei numeri, manipolandole come se fossero una sequenza di verità
logiche, il cui valore è determinato dai singoli bit che compongono i
numeri: il valore 0 equivale a $false, mentre 1 a $true. Ciascun
operatore binario, quindi, interagisce con numeri e ritorna numeri
ma, al suo interno, opera con le tabelle di verità logica analizzate nel
Capitolo 6.
Questo tipo di operatori è spesso utilizzato per manipolare
direttamente i file binari (come documenti e immagini) oppure per
intervenire nei dati ritornati da alcuni costrutti del framework
Microsoft .NET.

L’operatore -band
Dati due operandi interi (o convertibili in intero), l’operatore -band
(acronimo di binary and) ritorna un terzo valore i cui bit sono uguali a
uno dove i rispettivi bit nei due operandi sono pari a tale valore.
Questo operatore, quindi, può essere considerato a tutti gli effetti
l’equivalente binario di -and.

Tabella 14.4 - Tabella della verità per l’operatore -band.


Bit di sinistra Bit di destra Risultato
0 0 0
1 0 0
0 1 0
1 1 1

Nello script che segue, per esempio, l’operatore -band è utilizzato per
calcolare l’and binario tra due valori:
PS C:\Users\ikmju> 123 -band 456
72

L’operatore -bor
L’operatore -bor (acronimo di binary or) ritorna un intero i cui bit sono
uguali a uno quando almeno uno dei rispettivi bit nei due operandi è
pari a tale valore. Questo operatore, quindi, può essere considerato
a tutti gli effetti l’equivalente binario di -or.

Tabella 14.5 - Tabella della verità per l’operatore -bor.


Bit di sinistra Bit di destra Risultato
0 0 0
1 0 1
0 1 1
1 1 1

Nello script che segue, per esempio, l’operatore -bor è utilizzato per
calcolare l’or binario tra due valori:

PS C:\Users\ikmju> 123 -bor 456


507

L’operatore -bxor
Quando è frapposto tra due valori numerici, l’operatore -bxor
(acronimo di binary exclusive or) ne ritorna un terzo i cui bit sono
uguali a uno quando solo uno dei rispettivi bit nei due operandi è pari
a tale valore. Questo operatore, quindi, può essere considerato a
tutti gli effetti l’equivalente binario di -xor.

Tabella 14.6 - Tabella della verità per l’operatore -bxor.


Bit di sinistra Bit di destra Risultato
0 0 0
1 0 1
0 1 1
1 1 0
Nello script che segue, per esempio, l’operatore -bxor è utilizzato per
calcolare l’or esclusivo binario tra due valori:

PS C:\Users\ikmju> 123 -bxor 456


435

L’operatore -bnot
Mentre tutti gli operatori analizzati in precedenza utilizzano due
espressioni distinte per generarne una terza, l’operatore -
bnot(acronimo di binary not) ammette come unico operando quello di

destra e genera un valore numerico i cui bit sono l’opposto di quelli


dell’espressione di riferimento.
Nello script che segue, quindi, il valore generato dall’operatore è
l’inverso binario di quello fornito:

PS C:\Users\ikmju> -bnot 123


-124

Il tipo System.Math
Il tipo System.Math fornisce un ampio supporto per l’impiego delle
funzioni matematiche più comuni, incluse quelle esponenziali,
logaritmiche e trigonometriche, ed espone una serie di proprietà e
metodi statici con cui è possibile interagire. In questo paragrafo sono
discusse le funzionalità più comuni tra gli utenti di PowerShell.

Costanti matematiche
Le proprietà statiche PI ed E consentono di ottenere, rispettivamente,
il valore di π e quello di e (la costante di Nepero). Entrambi i valori
sono ritornati sotto forma di Double.
Nello script che segue, per esempio, sono recuperati e visualizzati
entrambi:

PS C:\Users\ikmju> [Math]::PI
3.14159265358979
PS C:\Users\ikmju> [Math]::E
2.71828182845905

Metodo Pow()
Il metodo statico POW() eleva un numero alla potenza specificata,
ritornando il risultato. La sintassi è questa:

[Math]::Pow(<Base>, <Esponente>]

In questo esempio, quindi, si chiede al sistema di calcolare il valore


corrispondente a 97:

PS C:\Users\ikmju> [Math] : :Pow(9,7)


4782969

Metodo Sqrt()
Di rimando, il metodo statico sqrto - acronimo di square root - calcola
la radice quadrata del valore specificato, in base a questa semplice
sintassi:

[Math]::Sqrt(<Numero>]

Nell’esempio che segue, la shell visualizza a video il valore


corrispondente a √7983 in notazione scientifica:

PS C:\Users\ikmju> "{0:e}" -f [Math]::Sqrt(7983)


3.934764e+001

Metodo Truncate()
Questo metodo statico accetta un unico valore numerico come
parametro e ne ritorna la sola parte intera. La sintassi - banale - è
questa:

[Math]::Truncate(<Numero>]

In questo script, per esempio, si richiede al metodo di restituire la


parte intera del valore fornito:

PS C:\Users\ikmju> [Math]::Truncate(567.89)
567

Metodi Floor(), Ceìlìng() e Round()


Il tipo system.Math permette di arrotondare i numeri secondo tecniche
differenti, in base al risultato che si desidera ottenere.
Il metodo statico Floor o accetta un unico valore numerico e ritorna il
più grande intero minore o uguale a quest’ultimo; per i numeri
positivi questo comportamento è identico a quello di Truncate(), ma per
quelli negativi i due metodi differiscono, come dimostra questo breve
script:

PS C:\Users\ikmju> [Math] ::Truncate(567.89 ;


567
PS C:\Users\ikmju> [Math] ::Floor(567.89 ;
567
PS C:\Users\ikmju> [Math] ::Truncate(-567.89 ;
-567
PS C:\Users\ikmju> [Math] ::Floor(-567.89 ;
-568

Il metodo statico Ceiling() d’altra parte, agisce in maniera opposta a


Floor() e ritorna il più grande intero maggiore o uguale al numero

fornito come parametro. Completando lo script precedente, dunque,


si ottiene questo risultato:

PS C:\Users\ikmju> [Math] ::Ceiling(567.89 ;


568
PS C:\Users\ikmju> [Math] ::Ceiling(-567.89 ;
-567

Il metodo statico Round(), infine, permette di ottenere il numero intero


più vicino al valore decimale fornito; il comportamento predefinito di
questo metodo è conforme allo standard IEEE 754 e, quando un
numero si trova a metà tra due interi (come 9.5, per esempio), ritorna
il numero pari più vicino a quello specificato, come illustrato da
questo esempio:

PS C:\Users\ikmju> [Math]::Round(1.5)
2
PS C:\Users\ikmju> [Math]::Round(2.5)
2
PS C:\Users\ikmju> [Math]::Round(3.5)
4

Poiché, tipicamente, l’arrotondamento desiderato non corrisponde a


quello standard, è possibile sfruttare un overload di Round() al fine di
specificare la modalità di arrotondamento desiderata. Le sintassi,
dunque, sono queste:

[Math]::Round(<Numero>)
[Math]::Round(<Numero>, <Arrotondamento>)

Il valore specificato come arrotondamento è uno degli elementi


dell’enumerazione System-MidpointRounding:
• ToEven ritorna il numero pari più vicino; rappresenta il
comportamento standard;
• AwayFromZero ritorna l’intero più vicino a quello indicato e più
distante dallo zero.
Per non generare errori a causa della moltitudine di overload
supportati da Round(), è necessario effettuare il cast esplicito della
modalità di arrotondamento al tipo System.MidpointRounding, come indicato
in questo esempio:

PS C:\Users\ikmju> [Math]::Round(2.5, [MidpointRounding]'AwayFromZero')


3

Il tipo System.Random
Nonostante non faccia letteralmente parte dei tipi
numerici,System.Random espone una funzionalità molto utile in tutti quei
contesti dove si desidera ottenere rapidamente un valore numerico
casuale.

Metodo Next()
Tutto il funzionamento di questo tipo ruota attorno a questo metodo
di istanza, che dispone di tre overload distinti. Il primo non accetta
parametri e restituisce un valore Int32 positivo casuale; il secondo
accetta un unico intero e ritorna un valore Int32 positivo casuale
minore del numero specificato. Il terzo overload, infine, permette di
specificare un valore minimo e uno massimo e ritorna un Int32
casuale all’interno dell’intervallo descritto.
Le sintassi per questi overload sono:

<Random>.Next()
<Random>.Next(<Massimo>)
<Random>.Next(<Minimo>, <Massimo>)

In questo script, per esempio, si utilizza il cmdlet Start-Sleep per


sospendere l’attività della shell per un numero casuale di
millisecondi, nell’intervallo 100–3000:

PS C:\Users\ikmju> $rnd = New-Object Random


PS C:\Users\ikmju> Start-Sleep -Milliseconds ($rnd.Next(100, 3000))

I numeri ritornati dalla classe System.Random non sono completamente casuali,


perché sono ottenuti dal risultato di un semplice algoritmo matematico. Si tratta di
NO valori eccellenti per un utilizzo pratico, come quello che tipicamente svolge chi
TA utilizza la piattaforma di scripting della shell, ma non sufficienti, per esempio, per
generare valori casuali da un punto di vista crittografico. In tale circostanza è
consigliabile affidarsi alle classi del namespace System.Security.Cryptography.
Le date e gli intervalli temporali

Un’introduzione alle date e agli intervalli temporali,


con un’analisi sulle tecniche di manipolazione e di
rappresentazione all’interno di PowerShell.

L’impiego di date e intervalli temporali all’interno della shell non


avviene con la stessa frequenza con cui si opera sugli altri tipi di
dato; conoscere il supporto offerto dal sistema, tuttavia, rende
possibili alcune soluzioni difficilmente realizzabili con le classiche
shell testuali a riga di comando.
In questo capitolo si analizzano le funzionalità esposte da
PowerShell per interagire con questi oggetti e le classi del
framework Microsoft .NET che rappresentano sia le date sia gli
intervalli temporali.

Get-Date
Questo cmdlet è il più importante dei comandi dedicati alla gestione
delle date e ricopre una duplice funzione. Richiamato senza
specificare alcun parametro, infatti, Get-Date ritorna la data e l’ora
correnti, alla stregua di quanto è possibile fare con il comando date,
presente sia nei sistemi *nix sia in CMD e COMMAND.COM. Gli sviluppatori di
PowerShell, d’altra parte, hanno aggiunto date agli alias predefiniti di
, in maniera tale da rendere più facile l’adozione del nuovo
Get-Date

comando agli utenti che provengono dai sistemi menzionati.

PS C:\Users\ikmju> Get-Date

giovedì 4 marzo 2010 15.28.41

A differenza del semplice testo ritornato da queste piattaforme,


tuttavia, Get-Date restituisce un oggetto di tipo System.DateTime, che è
possibile interrogare ulteriormente e manipolare a piacere. Ogni
istanza di questa classe, infatti, espone una serie di utili proprietà in
sola lettura, riportate nella Tabella 15.1.

Tabella 15.1 - Alcune proprietà esposte da System.DateTime.


Proprietà Descrizione
Date Ritorna la data, omettendo le informazioni sull’ora
Day Ritorna il giorno del mese
DayOfWeek Ritorna il giorno della settimana
DayOfYear Ritorna il giorno dell’anno
Hour Ritorna l’ora
Millisecond Ritorna i millisecondi
Minute Ritorna i minuti
Month Ritorna il mese
Second Ritorna i secondi
Year Ritorna l’anno

Per recuperare il mese corrente, per esempio, è sufficiente utilizzare


la proprietà Month, come illustrato nello script che segue:

PS C:\Users\ikmju> (Get-Date).Month
3

Fornendo al cmdlet Get-Date i parametri -Year, -Month, -Day, -Hour, -Minute e


-Second

è possibile creare un nuovo oggetto System.DateTime, inizializzato


secondo quanto specificato.
In questo script, per esempio, si crea una nuova data in base ai
valori forniti:
PS C:\Users\ikmju> Get-Date -Year 1983 -Month 7 -Day 9 -Hour 12

sabato 9 luglio 1983 12.23.08

Gli elementi non specificati direttamente (nell’esempio precedente


minuti, secondi e millisecondi) vengono impostati a partire dalla data
e ora correnti.
La visualizzazione di oggetti System.DateTime può essere alterata
direttamente tramite questo cmdlet; utilizzando il parametro -
DisplayHint, infatti, è possibile richiedere a Get-Date di mostrare a video

solo la porzione di data dell’oggetto (valore Date) oppure solo la


porzione di ora (valore Time), come dimostrato da questo esempio:

PS C:\Users\ikmju> Get-Date -DisplayHint Date

giovedì 4 marzo 2010

PS C:\Users\ikmju> Get-Date -DisplayHint Time

16.32.48

Sebbene l’output visualizzato possa far pensare il contrario, in


entrambi i casi Get-Date continua a restituire nella pipeline un oggetto
di tipo system.DateTime.

Formattare le date
Il cmdlet consente, inoltre, di ritornare direttamente delle stringhe
come risultato della formattazione personalizzata della data e
dell’ora di riferimento: agendo sul parametro -Format, infatti, si può
specificare una stringa di formattazione che indica come
rappresentare l’oggetto.
La stringa di formattazione yyyy-MM-dd, per esempio, induce Get-Date a
produrre una stringa composta, rispettivamente, dall’anno espresso
in quattro cifre, dal mese espresso in due e dal giorno espresso,
anch’esso, in due cifre:

PS C:\Users\ikmju> Get-Date -Format "yyyy-MM-dd"


2010-03-04

Tabella 15.2 - Principali stringhe di formattazione per -Format.


Stringa Output
yyyy Anno a quattro cifre
yy Anno a due cifre
MMMM Nome completo del mese
MMM Nome abbreviato del mese
MM Mese a due cifre
dddd Nome completo del giorno della settimana
ddd Nome abbreviato del giorno della settimana
dd Giorno a due cifre
HH Ora a due cifre, basata sulle 24 ore
hh Ora a due cifre, basata sulle 12 ore
mm Minuti a due cifre
ss Secondi a due cifre
r Formato RFC 1123
z Fuso orario di riferimento

Inoltre, per rendere l’adozione di Get-Date semplice anche agli utenti


che provengono dal mondo *nix, Microsoft ha dotato questo cmdlet
di un ulteriore parametro di indicazione del formato, chiamato -UFormat
(acronimo di Unix Format). Fornendo una stringa a questo
parametro, Get-Date ne interpreta il contenuto secondo le opzioni di
formattazione supportate dal comando date di *nix.
In questo script, per esempio, si impone al cmdlet di utilizzare le
stringhe di formattazione %d, %m e %Y per mezzo del parametro -
UFormat, per generare lo stesso output che restituirebbe il comando

suddetto:

PS C:\Users\ikmju> Get-Date -UFormat "%d-%m-%Y"


04-03-2010

Tabella 15.3 - Principali stringhe di formattazione per -UFormat.


Stringa Stringa
%Y Anno a quattro cifre
%y Anno a due cifre
%B Nome completo del mese
%b Nome abbreviato del mese
%m Mese a due cifre
%A Nome completo del giorno della settimana
%a Nome abbreviato del giorno della settimana
%d Giorno a due cifre
%H Ora a due cifre, basata sulle 24 ore
%I Ora a due cifre, basata sulle 12 ore
%M Minuti a due cifre
%S Secondi a due cifre

Manipolare le date
Gli oggetti di tipo System.DateTime sono dotati di alcuni utili metodi che
consentono di effettuare operazioni sulle date; poiché questi oggetti,
una volta creati, non si possono modificare, i metodi in questione
non alterano il valore contenuto dall’istanza cui sono sottesi, ma ne
creano una nuova, provvista del valore desiderato, e la ritornano al
chiamante.
Alcuni metodi d’istanza di questa classe sono utilizzati per
aggiungere uno specifico intervallo temporale a una data: il metodo
AddYears() ritorna una data che differisce da quella corrente per il

numero di anni indicato e segue questa sintassi di chiamata:

<DateTime>.AddYears(<Anni>)

Il metodo AddMonths(), invece, aggiunge il numero di mesi indicato,


ritornandone il risultato. La sintassi di chiamata è:

<DateTime>-AddMonths(<Mesi>)

In modo analogo, il metodo AddDays() aggiunge il numero di giorno


fornito, con questo sintassi:

<DateTime>-AddDays(<Giorni>)

Per aggiungere un intervallo orario, inoltre, è possibile impiegare


AddHours(), Ad-dMinutes() e Addseconds() per aggiungere, rispettivamente,

ore, minuti e secondi, in base a queste sintassi:

<DateTime>-AddHours(<Ore>)
<DateTime>-AddMinutes(<Minuti>)
<DateTime>-Addseconds(<Secondi>)

Nello script che segue, per esempio, si sottraggono 3 anni dalla data
corrente:

PS C:\Users\ikmju> (date)-AddYears(-3)
Monday, March 05, 2007 4:32:22 PM

Mentre con questo si aggiungono 9 ore e 7 minuti, in sequenza:

PS C:\Users\ikmju> (Get-Date) .AddHours(9) .AddMinutes(7)


Saturday, March 06, 2010 1:41:03 AM

Gli anni bisestili


Il tipo System.DateTime è in grado di ritornare alcune utili informazioni sul
calendario corrente, inclusa la ciclicità degli anni bisestili. Utilizzando
il metodo statico Daysln-Month(), infatti, è possibile richiede al sistema
l’esatto numero di giorni di un particolare mese, per un anno
specifico, seguendo questa sintassi:

[DateTime]::DaysInMonth(<Anno>, <Mese>)

In questo script, per esempio, si interroga il sistema sul numero di


giorni di febbraio, in tre anni differenti:

PS C:\Users\ikmju> [DateTime]::DaysInMonth(2010, 2)
28
PS C:\Users\ikmju> [DateTime]::DaysInMonth(2010, 2)
29
PS C:\Users\ikmju> [DateTime]::DaysInMonth(2010, 2)
28

Il metodo statico IsLeapYear(), infine, ritorna un valore logico pari a $true


se l’anno indicato è bisestile, $false altrimenti. La sintassi è:

[DateTime]::IsLeapYear(<Anno>)

Riprendendo l’esempio precedente, dunque, il risultato ritornato da


questo metodo è quello che ci si aspetterebbe:

PS C:\Users\ikmju> [DateTime]::IsLeapYear(2010)
False
PS C:\Users\ikmju> [DateTime]::IsLeapYear(2012)
True
PS C:\Users\ikmju> [DateTime]::IsLeapYear(2100)
False

Ora solare e ora legale


Il framework Microsoft .NET consente di risalire con facilità alle
informazioni relative alle impostazione del fuso orario gestite da
Windows; ogni istanza del tipo System. DateTime, infatti, espone il
metodo IsDaylightSavingTime(), che ritorna un valore logico a seconda
che la data di riferimento si trovi o meno all’interno dell’intervallo
dell’ora legale.
La sintassi di questo semplice metodo è:

<DateTime>.IsDaylightSavingTime()

Questo blocco dimostra come il metodo ritorni risultati differenti per


le date specificate:

PS C:\Users\ikmju> (Get-Date -Year 2010 -Month 7 -Day


9).IsDaylightvingTime()
True
PS C:\Users\ikmju> (Get-Date -Year 2012 -Month 12 -Day 21).
IsDaylightSavingTime()
False

Il ruolo centrale di mediatore per le informazioni sull’ora del fuso di


riferimento, però, è svolto da una particolare istanza della classe
System.TimeZone, ritornata dalla proprietà statica CurrentTimeZone del tipo

stesso al fine di recuperare le impostazioni del sistema corrente.


Il metodo GetDaylightChanges() di System.TimeZone permette di conoscere con
semplicità le date di inizio e di fine dell’applicazione dell’ora legale
per un particolare anno ed è richiamabile con questa sintassi:

<TimeZone>.GetDaylightChanges(<Anno>)

In questo script, per esempio, si mostra a video l’intervallo di


applicazione dell’ora legale per l’anno 2010 in Italia:

PS C:\Users\ikmju> [TimeZone]::CurrentTimeZone.GetDaylightChanges(2010)

Start End Delta


----- --- -----
28/03/2010 2.00.00 31/10/2010 3.00.00 01:00:00
Come si può notare, l’output comprende anche la proprietà Delta, che
indica la differenza temporale osservata durante l’applicazione
dell’ora legale (in alcuni Paesi questo dato è differente).
Due proprietà di System.Timezone, infine, restituiscono il nome standard
del fuso orario corrente e il nome dello stesso quando vi è applicata
l’ora legale, nella lingua del sistema operativo: si tratta,
rispettivamente, di StandardName e DaylightName.
Nel blocco che segue sono visualizzate a video, a titolo di esempio,
le due informazioni per un sistema in lingua italiana, configurato sul
fuso orario di Roma (quello italiano); come si può notare, la
visualizzazione predefinita delle istanze di TimeZone riporta ambedue i
testi, in forma tabellare:

PS C:\Users\ikmju> [TimeZone]::CurrentTimeZone

StandardName DaylightName
------------ ------------
ora solare Europa occidentale ora legale Europa occidentale

Gli intervalli temporali


All’interno del framework Microsoft .NET il tipo System.TimeSpan consente
di memorizzare degli intervalli temporali, definiti con un numero di
giorni, ore, minuti, secondi e frazioni di secondo. La manipolazione
delle date può trarre vantaggio dall’impiego di questa classe perché
alcuni metodi di modifica ritornano intervalli temporali e le variazioni
che si applicano alle date possono essere definite sinteticamente
con un’istanza di System.TimeSpan.
L’operatore differenza - individuato dal simbolo meno (-) - quando è
applicato a due date ritorna l’intervallo temporale corrispondente alla
differenza tra gli operandi; risalire al numero esatto di giorni, ore,
minuti e secondi che separano una data da un’altra è quindi
semplice come usufruire delle proprietà dell’istanza dell’oggetto
System.Timespan, ritornato da questo operatore.

Nello script che segue, per esempio, si sfrutta la proprietà Days di


questa classe per recuperare il numero di giorni di differenza tra
l’istante in cui si esegue il codice e un particolare evento, fissato nel
tempo:
PS C:\Users\ikmju> $today = Get-Date
PS C:\Users\ikmju> $apocalypse = Get-Date -Year 2012 -Month 12 -Day 21 -Hour
0 -Minute 0
PS C:\Users\ikmju> ($apocalypse - $today).Days
1021

Tabella 15.4 - Le principali proprietà di System.TimeSpan.


Proprietà Descrizione
Days Numero di giorni interi
Hours Numero di ore intere
Minutes Numero di minuti interi
Seconds Numero di secondi interi
TotalDays Numero di giorni interi e frazionari
TotalHours Numero di ore intere e frazionarie
TotalMinutes Numero di minuti interi e frazionari
TotalSeconds Numero di secondi interi e frazionari

È possibile creare una nuova istanza di un oggetto System.Timespan


impiegando il cmdlet New-TimeSpan e fornendo ai parametri -Days, -Hours, -
Minutes e -Seconds, rispettivamente, il numero di giorni, ore, minuti e
secondi con cui definire l’intervallo temporale. La sintassi d’uso
segue questo schema:

New-TimeSpan [-Days <Giorni>] [-Hours <Ore>] [-Minutes <Minuti>] [-Seconds


<Secondi>]

Ecco, dunque, come definire un intervallo temporale di 9 minuti e 7

secondi:

PS C:\Users\ikmju> New-TimeSpan -Minutes 9 -Seconds 7

Days : 0
Hours : 0
Minutes : 9
Seconds : 7
[...]

La shell consente di sommare e sottrarre tra loro gli oggetti di tipo


System.Timespan utilizzando, rispettivamente, l’operatore di addizione
(simbolo +) e quello di differenza (simbolo -): il risultato, ancora una
volta, è un intervallo temporale il cui valore è determinato dalla
somma o dalla differenza dei valori degli operandi.
Riutilizzando lo script precedente, quindi, è possibile adoperare
l’operatore di addizione per aggiungere all’intervallo 83 secondi e
verificare come il sistema calcoli automaticamente le diverse
componenti dell’istanza System.TimeSpan restituita:

PS C:\Users\ikmju> $x = New-TimeSpan -Minutes 9 -Seconds 7


PS C:\Users\ikmju> $y = New-TimeSpan -Seconds 83
PS C:\Users\ikmju>
PS C:\Users\ikmju> $x + $y

Days : 0
Hours : 0
Minutes : 10
Seconds : 30
[...]

Il metodo distanza Add( ; della classe System.DateTime, infine, permette di


aggiungere un intervallo temporale a una data, restituendo il
risultato. Segue lo schema di chiamata:

<DateTime>-Add(<TimeSpan>)

L’esempio seguente, quindi, aggiunge un intervallo di 7 giorni e 6 ore


alla data di esecuzione dello script:

PS C:\Users\ikmju> (Get-Date).Add((New-TimeSpan -Days 7 -Hours 6))

sabato 13 marzo 2010 6.46.25

Set-Date
Il cmdlet Set-Date è in grado di reimpostare le informazioni sulla data e
l’ora di sistema. Fornendo un oggetto System.DateTime al parametro -Date
il comando utilizza direttamente questo valore, mentre impiegando il
parametro -Adjust e un intervallo temporale la shell aggiunge
quest’ultimo al valore della data corrente, variandola. Le sintassi di
chiamata diSet-Date, dunque, sono rappresentate da questo schema:

Set-Date -Date <DateTime>


Set-Date -Adjust <TimeSpan>
Nello script che segue, per esempio, si reimpostano le informazioni
sulla data e l’ora di sistema portandole indietro di 10 minuti:

PS C:\Windows\system32> Set-Date -Adjust (New-TimeSpan -Minutes -10)

sabato 6 marzo 2010 0.47.44

Per poter funzionare correttamente, questo comando deve essere


lanciato da una shell con i privilegi di amministrazione (utilizzando
l’opzione Esegui come amministratore); in caso contrario, set-Date genera
un messaggio di errore simile a questo:

PS C:\Users\ikmju> Set-Date -Adjust (New-TimeSpan -Minutes -10)


Set-Date : Il privilegio richiesto non appartiene al client
In riga:l car : 9
+ Set-Date <<<< -Adjust (New-TimeSpan -Minutes -10)
+ Categorylnfo : NotSpecifìed: (:) [Set-Date], Win32Exception
+ FullyQualifìedErrorld : System.ComponentModel -Win32Exception,Microsoft.
PowerShell- Commands -SetDateCommand
Funzioni e filtri

Una discussione approfondita sulle tecniche di


organizzazione dei blocchi di script all’interno di
funzioni e filtri, in modo tale da favorire il riutilizzo del
codice e ottimizzare la propria attività all’interno della
shell.

Come si è visto nel corso del libro, il sistema di scripting di


PowerShell è stato studiato per favorire quanto più possibile
l’organizzazione e il controllo del codice utilizzato dagli utenti.
Tra le strutture sintattiche finalizzate a questi obiettivi il sistema
espone le funzioni e i filtri, che permettono di raggruppare facilmente
blocchi di istruzioni in maniera tale da poterli riutilizzare. In questo
capitolo sono descritte in modo dettagliato ambedue le strutture con
le principali tecniche di impiego.

Le funzioni
Una funzione consiste in una o più istruzioni raggruppate all’interno
di un unico blocco, cui è stato assegnato un nome; in seguito alla
definizione, questo nome può essere impiegato come un nuovo
comando e la sua digitazione porta a termine le istruzioni contenute
nel rispettivo blocco. Generalmente, quindi, si tende a creare
funzioni laddove sia conveniente riutilizzare particolari istruzioni
all’interno dei propri script.
La sintassi di definizione delle funzioni è molto simile a quella
presente in altri linguaggi di sviluppo: le istruzioni che ne formano il
corpo sono raggruppate utilizzando una coppia di parentesi graffe,
mentre l’intero blocco deve essere preceduto dalla keyword function
seguita dal nome desiderato. Ecco lo schema sintattico:

function <Nome>
{
<Istruzione1>
<Istruzione2>
<Istruzione3>
[...]
}

Nonostante PowerShell non definisca regole di nomenclatura per le


funzioni, generalmente si tende a rispettare la convezione prevista
per i nomi dei cmdlet, utilizzando stringhe composte dalla coppia
<Verbo>-<Sostantivo>.

A scopo di esempio, nello script che segue si definisce la funzione


Is-Administrator per raggruppare alcuni comandi che ritornano un

valore di verità logica a seconda del fatto che l’utente che esegue lo
script faccia parte o meno del gruppo Administrators della macchina
locale. Nonostante il codice contenga alcuni riferimenti a due utili
classi del framework Microsoft .NET, il seguito di questo paragrafo si
limita a illustrare i concetti legati direttamente alle funzioni,
tralasciando invece i dettagli legati al codice nel corpo di queste
ultime.

function Is-Administrator
{
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal $identity

$principal.IsInRole('BUILTIN\Administrators')
}

Una volta definita, la funzione fa parte dei comandi disponibili


all’interno della shell e, digitandone il nome, si esegue l’insieme delle
istruzioni che ne compongono il corpo:

PS C:\Users\ikmju> Is-Administrator
True

Gli oggetti emessi nella pipeline dalla funzione sono disponibili per
ulteriori elaborazioni in un blocco di pipeline successivo; le funzioni,
quindi, non sono limitate alla restituzione di un unico oggetto al
chiamante – paradigma molto diffuso in quasi tutti i linguaggi di
sviluppo – ma partecipano attivamente all’elaborazione della pipeline
e sono in grado di erogare una sequenza di valori.
Questa funzione, per esempio, ritorna la sequenza dei moduli
principali per tutti i processi della macchina locale:

function Get-MainModule
{
Get-Process
Select-Object -Expand MainModule
}

Utilizzando la funzione all’interno della pipeline, quindi, è possibile


elaborare ulteriormente il risultato, come in questo esempio:

PS C:\Users\ikmju> Get-MainModule | Select-Object FileName -Unique

FileName
--------
C:\Windows\system32\conime.exe
C:\Windows\system32\Dwm.exe
C:\Windows\Explorer.EXE
[...]

PowerShell, inoltre, consente di utilizzare la keyword return all’interno


di una funzione per emulare il comportamento dell’istruzione
omonima nei linguaggi di sviluppo di derivazione C: se la si pone
prima di un oggetto, infatti, la shell emette quest’ultimo - se indicato -
nella pipeline e termina immediatamente l’esecuzione della funzione.
La funzione che segue, per esempio, emette nella pipeline i numeri
1, 2 e 3, evitando di portare a termine i comandi delle righe

successive alla keyword return:

function Test-Return
{
1
2
return 3
4
5
}
E genera un output simile a questo:

PS C:\Users\ikmju> (Test-Return) -join ', '


1, 2, 3

Lo scope
Le variabili definite all’interno di una funzione sono soggette alla
stessa protezione prevista per gli script esterni, analizzata nel
Capitolo 4. L’esecuzione di una funzione, infatti, porta alla creazione
di un nuovo scope, che consente di proteggere e incapsulare
funzionalità indipendenti dall’esterno del corpo, facilitando ancora
una volta, quindi, il riutilizzo del codice.
Grazie a una sintassi analoga a quella impiegata dalle variabili,
inoltre, è possibile indicare a PowerShell in quale scope definire
un’intera funzione, con benefici simili a quanto descritto in
precedenza. È sufficiente far precedere al nome della funzione lo
scope desiderato, come illustrato in questo schema:

function [<Scope:>]<Nome>
{
[...]
}

Avendo, dunque, a disposizione uno script esterno chiamato


GlobalScopeFunction. PS1 con questo contenuto:

function global:Do-Testl
{
"Test 1"
}

function Do-Test2
{
"Test 2"
}

ed eseguendo, al prompt, il seguente blocco:

PS C:\Users\ikmju> .\GlobalScopeFunction.PS1

è possibile notare come la shell, in seguito, riconosca solo la


funzione dichiarata nello scope global:
PS C:\Users\ikmju> Do-Testl
Test 1

generando un errore mentre si tenta di richiamare quella definita


nello scope local (il predefinito):

PS C:\Users\ikmju> Do-Test2
Termine 'Do-Test2' non riconosciuto come nome di cmdlet, funzione, programma
eseguibile o file script- Controllare l'ortografìa del nome o verificare che il
percorso sia incluso e corretto, quindi riprovare.
In riga:l car : 9
+ Do-Test2 <<<<
+ Categorylnfo : ObjectNotFound: (Do-Test2:String) [],
CommandNotFoundException
+ FullyQualifìedErrorld : CommanclNotFoundException

NOT Per ulteriori dettagli sugli scope disponibili si rimanda al Capitolo


A 4.

Recuperare le funzioni disponibili


Per ottenere la lista delle funzioni disponibili nello scope corrente si
può utilizzare il cmdlet Get-Command, fornendo il valore Function al
parametro -CommandType in maniera tale da restringere la tipologia dei
comandi ritornati, come in questo esempio:

PS C:\Users\ikmju> Get-Command -CommandType Function


CommandType Name Definition
----------- ---- ----------
Function A: Set-Location A:
Function B: Set-Location B:
Function C: Set-Location C:
[...]

NO In questa circostanzaGet-command ritorna sia funzioni sia filtri, trattati nel seguito del
TA capitolo.

Per ogni funzione recuperata, ritorna un oggetto di tipo


Get-Command

System.Mana-gement.Automation.FunctionInfo, che consente anche di risalire,


come si può intuire dall’output dell’esempio precedente, al testo
originale della definizione della funzione di interesse.
Clear-Host, per esempio, è una funzione standard della shell che

permette di cancellare il contenuto della finestra della console;


dispone addirittura di due alias, il primo – cls – concepito per gli utenti
che provengono dalle precedenti shell Microsoft, l’altro – clear – per
chi ha esperienza con le shell *nix. Il fatto che questo comando sia
una funzione permette di utilizzare Get-Command per esplorarne con
estrema facilità la definizione:

PS C:\Users\ikmju> (Get-Command Clear-Host).Definition


$space = New-Object System.Management.Automation.Host.BufferCell
$space.Character = ' '
$space.ForegroundColor = $host.ui.rawui.ForegroundColor
$space.BackgroundColor = $host.ui.rawui.BackgroundColor
$rect = New-Object System.Management.Automation.Host.Rectangle
$rect.Top = $rect.Bottom = $rect.Right = $rect.Left = -1
$origin = New-Object System.Management.Automation.Host.Coordinates
$Host.UI.RawUI.CursorPosition = $origin
$Host.UI.RawUI.SetBufferContents($rect, $space)

Come si può notare dall’output di Get-Command, lo stesso comando C: –


contrariamente a quanto, forse, ci si potrebbe aspettare – non è
nient’altro che una funzione che richiama il cmdlet Set-Location,
approfondito nel Capitolo 17:

PS C:\Users\ikmju> (Get-Command C:).Definition


Set-Location C:

I parametri
In modo simile a quanto avviene per i cmdlet e per gli script esterni,
anche le funzioni supportano il passaggio di valori tramite parametri,
che possono essere definiti utilizzando due sintassi diverse a
seconda della preferenza dell’utente. La prima impiega la keyword
param, alla stregua di quanto già visto per gli script esterni nel Capitolo
4; in questo caso il blocco param deve essere il primo elemento del
corpo della funzione, come in questo esempio:

function Is-Even
{
param ($value)

($value % 2) -eq 0
}

La sintassi alternativa, invece, è più in linea con i linguaggi di


sviluppo di derivazione C e prevede di specificare la lista dei
parametri attesi direttamente in seguito al nome della funzione, tra
parentesi tonde. Ecco la funzione Is-Even riscritta in modo più sintetico
grazie a quest’altra sintassi:

function Is-Even($value)
{
($value % 2) -eq 0
}

Le due sintassi sono schematizzate di seguito:

function <Nome>
{
param (<Parametro1> [, <Parametro2>] [, ...])
[...]
}

function <Nome>(<Parametro1> [, <Parametro2>] [, ...])


{
[...]
}

In entrambi i casi, per eseguire la funzione e specificare i valori dei


parametri, è sufficiente digitare il nome della prima - come di
consueto - e farlo seguire dai valori che si desidera abbinare ai
parametri. La funzione definita nell’esempio precedente, quindi, può
essere utilizzata così:

PS C:\Users\ikmju> Is-Even 3
False
PS C:\Users\ikmju> Is-Even 100
True

A meno che non si adoperi una delle tecniche esposte nel seguito di
questo capitolo, ciascun parametro è opzionale e, nel caso in cui
non sia fornito alcun valore, la shell lo considera pari a $null. La
funzione precedente, per esempio, ritorna $true quando viene
richiamata senza specificare parametri perché la variabile $value
assume di conseguenza il valore $null, che convertito a intero è pari
a 0:

PS C:\Users\ikmju> Is-Even
True

Per rendere più leggibile il codice e facilitare il passaggio di valori a


funzioni che accettano molti parametri, inoltre, PowerShell consente
di specificare il nome dei parametri denominati a cui si desidera
fornire il valore: la sintassi è simile a quella del passaggio di valori a
cmdlet e script esterni e prevede l’indicazione del nome di ogni
parametro (escluso il carattere dollaro) preceduto dal simbolo trattino
(-), cui far seguire il valore desiderato. La funzione dichiarata in
precedenza, per esempio, è richiamabile con questo script:

PS C:\Users\ikmju> Is-Even -Value 907


False

Alla stregua di quanto avviene per gli script esterni, inoltre, anche i
parametri delle funzioni possono essere fortemente tipizzati: è
sufficiente, anche in questo caso, far precedere al nome del
parametro il tipo atteso, tra parentesi quadre. La funzione
dell’esempio, quindi, può essere riscritta così, in maniera tale da
rendere esplicito il tipo atteso:

function Is-Even([Int32]$value)
{
($value % 2) -eq 0
}

Gli switch, inoltre, godono di un trattamento speciale perché sono gli


unici tipi che non necessitano di un’indicazione esplicita del valore
fornito ($true o $false). Come per i cmdlet, è sufficiente indicarne il
nome (preceduto, anche qui, dal trattino) per causarne l’attivazione.
La funzione che segue, per esempio, utilizza uno switch per
determinare se ritornare un oggetto Timespan o un valore numerico:

function Get-TimeToApocalypse([switch]$asMinutes)
{
$timeToApocalypse = ([datetime]'2012-12-21') - (date)

if ($asMinutes)
{
$timeToApocalypse-TotalMinutes
}
else
{
$timeToApocalypse
}
}

La chiamata, quindi, non prevede il passaggio di alcun valore allo


switch:
PS C:\Users\ikmju> Get-TimeToApocalypse

Days : 1015
Hours : 7
[...]

PS C:\Users\ikmju> Get-TimeToApocalypse -asMinutes


1462036 - 95181522

A ciascun parametro, infine, può essere abbinato un valore


predefinito, assunto nel caso in cui la funzione sia stata richiamata
senza fornire al parametro alcun valore. Per indicare un valore
predefinito è sufficiente far seguire al nome del parametro
l’operatore di assegnazione - individuato dal simbolo uguale (=) - e il
valore desiderato. La sintassi di definizione di ciascun parametro, a
questo punto, è schematizzabile così:

[<Tipo>]<Parametrol> [ = <ValorePredefinito>]

La funzione Is-Administrator, definita in precedenza, può dunque


essere rinominata in Is-InRole ed estendere la propria verifica
all’appartenenza di qualsiasi gruppo di utenti: tale gruppo -
identificato nell’esempio con la variabile $role - assume di default il
valore di Administrators.

function Is-InRole($role = 'BUILTIN\Administrators')


{
$identity = [Security - Principal-WindowsIdentity]::GetCurrent()
$principal = New-Object Security - Principal-WindowsPrincipal $identity

$principal-IsInRole($role)
}

Richiamando la funzione senza specificare parametri, dunque, si


ottiene lo stesso risultato illustrato in precedenza:

PS C:\Users\ikmju> Is-InRole
True

Ma specificando il valore di $role la funzione verifica l’appartenenza


dell’utente al gruppo specificato:

PS C:\Users\ikmju> Is-InRole 'TEST'


False
I parametri posizionali
L’associazione tra i valori forniti e i parametri delle funzioni avviene
in tre fasi distinte. Nella prima fase PowerShell estrae dalla riga di
comando tutti i valori forniti alla funzione come parametro e li divide
in due gruppi: un gruppo che contiene i valori per i quali sono stati
indicati esplicitamente i nomi dei parametri di destinazione e un
gruppo che contiene i valori rimanenti.
Nella seconda fase il sistema analizza il primo gruppo e abbina ogni
valore al parametro cui è riferito.
Nella terza fase, infine, viene analizzato il secondo gruppo e,
seguendo l’ordine di definizione dei parametri all’interno della
funzione, la shell associa ai parametri non ancora abbinati il valore
corrispondente.
Tutti i valori che “avanzano” dalla terza fase sono forniti alla funzione
come elementi dell’array $args, una variabile automatica: per
contraddistinguerli dai parametri denominati, illustrati nel paragrafo
precedente, questo tipo di parametro è chiamato posizionale.
La funzione che segue, per esempio, permette di visualizzare a
video sia i valori dei parametri denominati sia il valore di ogni
parametro posizionale:

function Do-Test($x, $y)


{
"`$x -> $x"
"`$y -> $y"

foreach ($arg in $args)


{
"Posizionale: $arg"
}
}

Lo script che segue mostra tutte le casistiche previste dal


meccanismo di abbinamento dei parametri, discusso poc’anzi:

PS C:\Users\ikmju> Do-Test 3
$x -> 3
$y ->
PS C:\Users\ikmju> Do-Test -y 5
$x ->
$y -> 5
PS C:\Users\ikmju> Do-Test 3 5
$x -> 3
$y -> 5
PS C:\Users\ikmju> Do-Test -y 3 5
$x -> 5
$y -> 3
PS C:\Users\ikmju> Do-Test 2 3 5
$x -> 2
$y -> 3
Posizionale: 5
PS C:\Users\ikmju> Do-Test 2 3 -x 5 7
$x -> 5
$y -> 2
Posizionale: 3
Posizionale: 7

La validazione dei parametri


A partire dalla versione 2.0, PowerShell consente di definire delle
regole di convalida per ciascun parametro e di bloccare l’esecuzione
della relativa funzione qualora queste regole non dovessero essere
soddisfatte. Questa tecnica consente di risparmiare il codice
necessario alla verifica della bontà dei dati forniti dal chiamante e
permette, di conseguenza, di ottenere script più snelli e leggibili. Per
definire l’obbligatorietà di un parametro è possibile utilizzare questa
sintassi:

[Parameter(Mandatory=$true)]
<Parametro>

Se un parametro obbligatorio non è specificato, la relativa funzione


non viene eseguita e genera un errore. Nello script che segue, per
esempio, il parametro $value della funzione Is-Even diventa obbligatorio:

function Is-Even(
[Parameter(Mandatory=$true)]
[Int32]
$value
)
{
($value % 2) -eq 0
}

NO Per migliorare la leggibilità del codice è possibile suddividere la dichiarazione di


TA ciascun parametro in più righe.

Se si richiama al prompt la funzione senza specificare parametri,


infatti, la shell richiede di specificarne il valore:

PS C:\Users\ikmju> Is-Even
Cmdlet Is-Even nella posizione 1 della pipeline dei comandi
Specificare i valori per i seguenti parametri:
value:

Fornendo il valore $null, invece, viene generato un errore:

PS C:\Users\ikmju> Is-Even $null


Is-Even : Impossibile associare l'argomento al parametro 'value' perché è null.

Decorando in modo opportuno il parametro, d’altra parte, è possibile


fargli accettare valori nulli, fermo restando il vincolo di obbligatorietà.
È sufficiente seguire questa sintassi:

[Parameter(Mandatory=$true)]
[AllowNull()]
<Parametro>

La funzione di esempio può dunque essere ridefinita per trarre


vantaggio dalla nuova impostazione:

function Is-Even(
[Parameter(Mandatory=$true)]
[AllowNull()]
[Int32]
$value
)
{
($value % 2) -eq 0
}

e continuare, di conseguenza, ad accettare valori nulli:

PS C:\Users\ikmju> Is-Even $null


True

In modo analogo al precedente, è possibile chiedere alla shell di


accettare il valore stringa vuota ( '' ), altrimenti non consentito:

[Parameter(Mandatory=$true)]
[AllowEmptyString()]
<Parametro>

Un altro costrutto, poi, permette di convalidare il numero di elementi


ammessi da un parametro che accetta array di oggetti. La sintassi
permette di definire sia un numero minimo sia un numero massimo
di oggetti:
[Parameter(Mandatory=$true)]
[ValidateCount(<Minimo>, <Massimo>)]
<Parametro>

Ecco, per esempio, come limitare il numero di elementi accettati dal


parametro $value della funzione Do-Test:

function Do-Test(
[Parameter(Mandatory=$true)]
[ValidateCount(3, 5)]
[Int32[]]
$value
)
{
[...]
}

Se si richiama la funzione di esempio con un numero di elementi


minore di 3 o maggiore di 5 viene generato un errore:

PS C:\Users\ikmju> Do-Test 1, 2
Do-Test : Impossibile convalidare l'argomento sul parametro 'value'. Il numero
di argomenti forniti (2) è inferiore al numero minimo di argomenti consentiti
(3). Specificare più di 3 argomenti ed eseguire di nuovo il comando.
[...]

Utilizzando la sintassi che segue, inoltre, è possibile chiedere alla


shell di accettare il valore di un parametro solo quando questo
rientra in un elenco prefissato di valori possibili:

[ValidateSet(<Valore1>, <Valore2>, ...)]


<Parametro>

La funzione che segue, per esempio, genera un errore quando al


parametro $value viene fornito un valore diverso da Staging o Production
(e $null, poiché nella definizione del parametro non c’è
obbligatorietà):

function Do-Test(
[ValidateSet('Staging', 'Production')]
$value
)
{
[...]
}

PS C:\Users\ikmju> Do-Test 'oopssss..'


Do-Test : Impossibile convalidare l'argomento sul parametro 'value'.
L'argomento "oopssss.." non appartiene al set "Staging,Production"
specificato dall'attributo ValidateSet. Fornire un argomento incluso nel set
ed eseguire di nuovo il comando.
[...]

Due costrutti sono dedicati alla gestione dei parametri di tipo stringa.
Il primo serve per convalidare la lunghezza del testo fornito:

[ValidateLength(<Minimo>, <Massimo>)]
<Parametro>

In questo caso, per esempio, il parametro della funzione deve avere


una rappresentazione testuale di lunghezza compresa tra 2 e 7:

function Do-Test(
[ValidateLength(2, 7)]
$value
)
{
[...]
}

In caso contrario, il sistema genera un errore:

PS C:\Users\ikmju> Do-Test 'x'


Do-Test : Impossibile convalidare l'argomento sul parametro 'value'. Il
numero di caratteri (1) dell'argomento è insufficiente. Specificare un
argomento la cui lunghezza sia maggiore o uguale a "2" ed eseguire di nuovo
il comando.
[...]

Il secondo costrutto dedicato alle stringhe, invece, verifica che il


valore fornito generi un match con una particolare espressione
regolare. Segue questo schema:

[ValidatePattern('<EspressioneRegolare>')]
<Parametro>

In questo script, per esempio, il parametro della funzione deve avere


il formato di una partita IVA italiana (undici cifre):

function Do-Test(
[ValidatePattern('^\d{11}$')]
$value
)
{
[...]
}

Fornendo un valore che non rispetta questo formato la shell non


permette l’esecuzione della funzione:
PS C:\Users\ikmju> Do-Test 'xyz'
Do-Test : Impossibile convalidare l'argomento sul parametro 'value'.
L'argomento "xyz" non corrisponde al modello "^\d{
11}$". Fornire un argomento che corrisponda a "^\d{11}$" ed eseguire di
nuovo il comando.
[...]

Questo costrutto, invece, permette di definire il valore minimo e


massimo accettati per un particolare parametro:

[ValidateRange(<Minimo>, <Massimo>)]
<Parametro>

In questo esempio, quindi, la funzione non accetta valori per il


parametro a meno che questi non siano compresi tra 10 e 100:

function Do-Test(
[ValidateRange(10, 100)]
$value
)
{
[...]
}

Producendo, in caso contrario, un errore:

PS C:\Users\ikmju> Do-Test 5
Do-Test : Impossibile convalidare l'argomento sul parametro 'value'.
L'argomento 5 è minore dell'intervallo minimo consentito di (10). Fornire un
argomento maggiore di 10 ed eseguire di nuovo il comando.
[...]

L’ultimo costrutto trattato in questo paragrafo, infine, permette di


convalidare un parametro utilizzando uno script definito dall’utente,
cui la shell fornisce il valore da esaminare tramite la variabile
automatica $_: se lo script ritorna il valore $true l’operazione è
andata a buon fine; in caso contrario il sistema annulla l’esecuzione
della funzione. La sintassi d’uso è schematizzata come segue:

[ValidateScript(<Script>)]
<Parametro>

In questo esempio, quindi, il parametro della funzione accetta valori


solo quando questi sono numerici pari:

function Do-Test(
[ValidateScript({ ($_ % 2) -eq 0 })]
$value
)
{
[...]
}

Richiamando la funzione con un valore non valido, quindi, la shell


genera un messaggio di errore simile a questo:

PS C:\Users\ikmju> Do-Test 3
Do-Test : Impossibile convalidare l'argomento sul parametro 'value'. Lo
script di convalida " ($_ % 2) -eq 0 " per l'argomento con valore "3" non ha
restituito true. Determinare il motivo per cui lo script di convalida non è
riuscito ed eseguire di nuovo il comando.
[...]

Interagire con la pipeline


Nel primo paragrafo di questo capitolo si è visto come le funzioni
siano in grado di ritornare sequenze di oggetti emettendoli all’interno
della pipeline; il processo inverso è più complesso e nel seguito del
capitolo sono illustrate le diverse tecniche che lo rendono possibile.
Una funzione inserita all’interno della pipeline non viene eseguita
fino a quando la sequenza di oggetti ritornata dagli eventuali blocchi
precedenti non è stata completata: gli eventuali oggetti presenti nella
pipeline, dunque, sono raggruppati in una collezione (un oggetto
ArrayList) fornita alla funzione desiderata per mezzo della variabile

automatica $input.
Per interagire con gli oggetti provenienti dalla pipeline, dunque, è
sufficiente iterare la collezione $input e portare a termine l’attività
desiderata per ciascun elemento trovato. Supponendo di voler
creare una funzione che ritorni la radice quadrata dei valori presenti
nella pipeline, per esempio, è possibile servirsi di uno script simile a
questo:

function Get-SquareRoot()
{
foreach ($x in $input)
{
[Math]::Sqrt($x)
}
}

Richiamando questa funzione tramite pipeline, infatti, si ottiene il


risultato desiderato:
PS C:\Users\ikmju> 67, 81, 15 | Get-SquareRoot
8.18535277187245
9
3.87298334620742

Questa tecnica, d’altra parte, non sfrutta pienamente la capacità di


elaborazione della pipeline: la presenza della funzione, infatti, causa
l’interruzione della lavorazione ordinaria degli oggetti, che non sono
più passati di blocco in blocco, uno di seguito all’altro. Nell’esempio
precedente questa lacuna non si nota, ma quando la pipeline diventa
più complessa possono emergere problemi di performance. Per
ottenere un livello di integrazione completo con la pipeline,
PowerShell consente di impiegare una sintassi di definizione delle
funzioni più evoluta, che permette, tra l’altro, di superare la carenza
di performance menzionata precedentemente. Questa sintassi
prevede che il corpo della funzione sia suddiviso in tre blocchi e che
ciascuno contenga le istruzioni da eseguire in una determinata fase
di elaborazione della pipeline:
• il blocco begin contiene i comandi della funzione da eseguire una
sola volta all’avvio dell’elaborazione dell’intera pipeline, prima
che sia emesso o elaborato qualsiasi oggetto;
• il blocco process contiene i comandi da eseguire per ogni oggetto
emesso nella pipeline dall’eventuale blocco che precede quello
della funzione. Anche in questo caso, l’oggetto da elaborare è
fornito tramite la variabile automatica $_;
• il blocco end, infine, contiene le istruzioni della funzione da
eseguire una sola volta al termine dell’elaborazione degli
oggetti emessi dall’eventuale blocco precedente quello
corrente.
La sintassi che regola l’impiego di questi blocchi può essere
schematizzata così:

function <Nome>
{
begin
{
<Comandi>
}

process
{
<Comandi>
}

end
{
<Comandi>
}
}

In questa sintassi ciascuno dei tre blocchi è opzionale, ma l’assenza


di process comporta comunque la consumazione degli oggetti nella
pipeline.
La funzione definita in precedenza, dunque, può essere riscritta
avvalendosi di questa sintassi e usufruire pienamente del supporto
per l’elaborazione sequenziale offerto dalla pipeline:

function Get-SquareRoot()
{
process
{
[Math]::Sqrt($_)
}
}

Gli oggetti ritornati, in ogni modo, sono gli stessi dell’esempio


precedente:

PS C:\Users\ikmju> 67, 81, 15 | Get-SquareRoot


8.18535277187245
9
3.87298334620742

I blocchi begin e end sono sicuramente impiegati con minore frequenza


rispetto a process, ma consentono di gestire in modo ottimale
l’allocazione di eventuali oggetti impiegati dalla funzione e di
emettere, eventualmente, oggetti di riepilogo rispetto alla sequenza
elaborata.
Per dimostrare quest’ultima evenienza, dunque, lo script che segue
illustra una funzione in grado di restituire la somma degli oggetti
provenienti dalla pipeline:

function Sum-Object
{
begin
{
$sum = $null
}
process
{
if ($sum -eq $null)
{
$sum = $_
}
else
{
$sum += $_
}
}
end
{
$sum
}
}

L’esecuzione della funzione porta al risultato atteso:

PS C:\Users\ikmju> 9, 83, 7 | Sum-Object


99
PS C:\Users\ikmju> "Ef", "ra", "n" | Sum-Object
Efran

Si noti come, in questo caso, il blocco process non emetta alcun


oggetto, mentre il blocco end emetta il risultato dell’operazione.

NO Il cmdlet Measure-Object è in grado di calcolare la somma tra due valori numerici,


TA ma è limitato a questa tipologia di oggetti.

I filtri
Alla luce di quanto illustrato in precedenza, dunque, definire una
funzione utilizzando la sintassi di base è equivalente a impiegare la
sintassi che permette l’interazione con la pipeline, inserendo il
codice desiderato nel blocco end.
I filtri sono funzioni che si distinguono dalle precedenti per il solo
fatto che i comandi contenuti nel loro corpo sono eseguiti, quando si
impiega la sintassi di base, per ogni elemento emesso
precedentemente nella pipeline. Si tratta, dunque, di un
comportamento analogo all’inserire il codice desiderato in un blocco
process di una normale funzione. Per tale ragione, i filtri costituiscono

una sorta di scorciatoia sintattica rispetto alla sintassi che permette


l’interazione tra le funzioni e la pipeline.
La definizione dei filtri avviene in maniera identica a quella delle
funzioni, a parte il fatto che la keyword introduttiva non è function, ma
.
filter

filter <Nome>
{
<Istruzionel>
<Istruzione2>
<Istruzione3>
[...]
}

Benché sia scarsamente usata in questa circostanza, anche i filtri


supportano la sintassi che permette l’interazione completa con la
pipeline:

filter <Nome>
{
begin
{
<Comandi>
}
process
{
<Comandi>
}
end
{
<Comandi>
}
}

Poiché la funzione Get-SquareRoot, definita in precedenza, utilizza il solo


blocco process, potrebbe essere più conveniente definirla tramite un
filtro, come illustrato da questo esempio:

filter Get-SquareRoot()
{
[Math]::Sqrt($_)
}

Il risultato, naturalmente, dimostra la capacità di interazione con la


sequenza di oggetti provenienti dalla pipeline:

PS C:\Users\ikmju> 81, 89.3 | Get-SquareRoot


9
9.44986772394196
Parte IV
Amministrazione del sistema
I provider

Una panoramica sull’architettura dei provider di


PowerShell e un approfondimento sulle principali
tecniche di manipolazione dei file, delle chiavi del
registry di Windows, delle variabili, degli alias e
delle funzioni, con un’introduzione ai parametri di
attenuazione dei rischi.

Nei sistemi operativi Microsoft, la notazione che identifica con una


lettera i drive disponibili risale alle prime versioni di MS-DOS,
quando ogni lettera rappresentava un volume fisico diverso e non
esisteva il supporto per i volumi logici e le partizioni. Al contrario dei
sistemi *nix, dunque, dove il concetto di mount point ha garantito fin
dalle origini la possibilità di dare un nome arbitrario ai volumi
all’interno del namespace gerarchico (/), i sistemi Microsoft sono nati
con questa limitazione – oggi ampiamente superata – frutto
dell’eredità ricevuta da CP/CMS.
Il concetto di drive all’interno di PowerShell prende spunto da quello
del sistema operativo, ma ne espande le potenzialità in maniera tale
da consentire la gestione e la fruizione non solo dei drive classici
ordinari (come i volumi fisici e quelli logici) ma anche di strutture
gerarchiche di tipo diverso, come il registry di Windows. Nel seguito
di questo capitolo sono analizzati i diversi tipi di drive disponibili
nativamente all’interno di PowerShell e i cmdlet da utilizzare per
gestirne gli elementi.

L’architettura
All’interno di PowerShell i drive non sono limitati a una sola lettera,
ma possono essere stringhe di qualsiasi lunghezza, anche se di
solito non superano i 4-5 caratteri. In ogni percorso assoluto il drive
è semplicemente la porzione di stringa che va dal primo carattere al
simbolo dei due punti (:), che funge da separatore. Genericamente,
dunque, la sintassi d’uso dei drive è simile a quella di CMD e COM-
MAND.COM:

<Drive>:<Percorso>

Nell’architettura scelta da Microsoft, la gestione dei drive di


PowerShell è completamente aperta ed espandibile: il codice di
manipolazione dei percorsi, infatti, non risiede nella shell stessa ma
è contenuto in alcune classi particolari del framework Microsoft .NET
esposte dagli snap-in, chiamate genericamente provider. Così, alla
stregua di quanto avviene per i cmdlet, al caricamento degli snap-in
il sistema rileva l’eventuale presenza di provider all’interno di tali
assembly e, grazie alle informazioni rilasciate da questi, compila la
lista dei drive disponibili nel sistema. A ogni drive, dunque,
corrisponde un provider che è in grado di interagire con i percorsi
che gli fanno capo.
La caratteristica che rende così interessante questa architettura è il
fatto che la shell dispone di diversi cmdlet in grado di interagire con
qualsiasi drive e qualsiasi provider, in modo indipendente dalla
natura di questi ultimi: i comandi necessari a manipolare il
filesystem, per esempio, sono gli stessi che servono a gestire il
registry di Windows e qualsiasi altro provider installato.
Per recuperare la lista dei provider disponibili nella sessione corrente
è possibile fare uso del cmdlet Get-PSProvider, che ritorna un oggetto di
tipo System.Management. Automation.ProviderInfo per ogni elemento
recuperato:

PS C:\Users\ikmju> Get-PSProvider

Name Capabilities Drives


---- ------------ ------
WSMan Credentials {WSMan}
Alias ShouldProcess {Alias}
Environment ShouldProcess {Env}
FileSystem Filter, ShouldProcess {C, A, D}
Function ShouldProcess {Function}
Registry ShouldProcess, Transactions {HKLM, HKCU}
Variable ShouldProcess {Variable}
Certificate ShouldProcess {cert}

L’output di Get-PSProvider include la proprietà Name, che indica il nome


del provider, e la proprietà Drives, che contiene una collezione di drive
gestiti da ciascun provider.
La proprietà PSSnapIn, inoltre, nonostante non sia inclusa nella vista
predefinita del tipo System.Management.Automation.ProviderInfo, permette di
recuperare lo snap-in associato a ogni provider, come dimostra
questo esempio:

PS C:\Users\ikmju> Get-PSProvider | Select Name, PSSnapIn

Name PSSnapIn
---- --------
WSMan Microsoft.WSMan.Management
Alias Microsoft.PowerShell.Core
Environment Microsoft.PowerShell.Core
FileSystem Microsoft.PowerShell.Core
Function Microsoft.PowerShell.Core
Registry Microsoft.PowerShell.Core
Variable Microsoft.PowerShell.Core
Certificate Microsoft.PowerShell.Security

Quando PowerShell gestisce un percorso assoluto, dunque, ne


determina il drive e da questo il provider da impiegare; per
recuperare la lista dei drive disponibili nel sistema si può usare il
cmdlet Get-PSDrive, come illustrato in questo esempio:

Name Used (GB) Free (GB) Provider Root CurrentLocation


---- --------- --------- -------- ---- ---------------
A FileSystem A:\
Alias Alias
C 10,65 116,35 FileSystem C:\ Users\ikmju
cert Certificate \
D FileSystem D:\
Env Environment
Function Function
HKCU Registry HKEY_CURRENT_USER
HKLM Registry HKEY_LOCAL_MACHINE
Variable Variable
WSMan WSMan

La tabella che segue elenca tutti i drive nativi di PowerShell 2.0, a


esclusione di quelli relativi al file system, che variano in base alle
unità disponibili nel sistema:

Tabella 17.1 - Drive nativi di PowerShell 2.0.


Drive Provider Descrizione
Alias: Alias Catalogo degli alias
Catalogo dei certificati X.509 installati nel
cert: Certificate
sistema
Env: Environmen Catalogo delle variabili d’ambiente
t
Function: Function Catalogo delle funzioni
HKCU: Registry Registry di Windows, hive
HKEY_CURRENT_USER
Registry di Windows, hive
HKLM: Registry
HKEY_LOCAL_MACHINE
Variable: Variable Catalogo delle variabili
WSMan: WSMan Catalogo di configurazione di WS-Management
*: (A:, C:, FileSystem Unità corrispondente del file system
...)

Ogni drive dispone di un percorso corrente, memorizzato all’interno


della proprietà CurrentLocation; durante l’eventuale passaggio tra un
drive e l’altro questa informazione risulta così facilmente
recuperabile, anche tramite script.

I provider
Prima di analizzare i comandi dedicati alla gestione dei drive e dei
file conviene esaminare le possibilità offerte dai differenti tipi di
provider supportati nativamente da PowerShell.

II provider FileSystem
Il provider FileSystem gestisce le unità, le cartelle e i file del file system
e, all’avvio di PowerShell, crea un drive per ogni unità rilevata nella
macchina (incluse le unità di rete). Nello script che segue, per
esempio, si può notare come questo provider abbia creato un drive
per ogni unità disponibile nel file system di una macchina di test,
incluse l’unità del disco floppy (A:), quella del lettore DVD (D:) e
un’unità di rete (z:):

PS C:\Users\ikmju> Get-PSDrive | ? { $_.Provider-Name -eq 'FileSystem' }

Name Used (GB) Free (GB) Provider Root CurrentLocation


---- -------- -------- -------- ---- ---------------
A FileSystem A:\
C 10,65 116,35 FileSystem C:\ Users\ikmju
D FileSystem D:\
Z 285,52 12,57 FileSystem Z:\

Il provider FileSystem gestisce, inoltre, l’accesso diretto ai percorsi di


rete definiti utilizzando la sintassi UNC (Universal Naming
Convention), una possibilità non ancora offerta né da CMD né da
COMMAND.COM.

Il provider Registry
Il provider Registry permette di gestire il registry di Windows,
recuperandone e impostandone la struttura, le chiavi e i valori.
Nonostante questo provider supporti l’accesso a tutti gli hive del
registry, all’avvio di PowerShell si limita alla creazione di un drive per
HKEY_CURRENT_USER e uno per HKEY_LOCAL_MACHINE, rispettivamente HKCU: e HKLM:.

Il provider Registry non supporta l’accesso a un registry di Windows appartenente


NO
a una macchina remota. Per ovviare a questo limite è possibile utilizzare il metodo
TA
statico OpenRemoteBaseKey() della classe Microsoft.Win32-RegistryKey.

Il provider Variable
Il provider Variable consente di gestire le variabili presenti nella
sessione corrente. A ogni variabile, infatti, corrisponde un elemento
presente all’interno del drive Variable:.

Il provider Alias
In modo simile al precedente, questo provider consente il recupero e
la modifica degli alias presenti nella sessione corrente. A ogni alias,
quindi, corrisponde un elemento presente all’interno del drive Alias:.

Il provider Function
Analogamente ai due provider precedenti, il provider Function gestisce
tutte le funzioni presenti nella sessione corrente e a ognuna fa
corrispondere un elemento del drive omonimo.

Il provider Environment
L’utilissimo provider Environment consente di accedere rapidamente alle
variabili di ambiente per la sessione corrente, rendendole disponibili
attraverso gli elementi del drive Env:.

Il provider Certificate
Il provider Certificate gestisce i certificati X.509 installati nella
macchina locale e permette il recupero immediato di tutti i dettagli,
come gli enti emettitori e le date di validità. Il drive cert:, governato
da questo provider, espone una struttura gerarchica simile a quella
esibita nella console MMC (Microsoft Management Console) dallo
snap-in Certificati.

Il provider WSMan
Il provider WSMan è presente a partire dalla versione 2.0 di
PowerShell e consente di recuperare e impostare la configurazione
di WinRM (Capitolo 1). Ogni singola impostazione di questo sistema è
gestita attraverso gli elementi del drive WSMan:.

Gestire il percorso di lavoro corrente


Tutte le shell a riga di comando consentono di recuperare e
impostare con facilità il percorso su cui si sta operando. All’interno di
PowerShell si possono utilizzare due semplici cmdlet per portare a
termine queste attività, a prescindere dal provider utilizzato.

Get-Location
Nel suo impiego più semplice, questo cmdlet ritorna un oggetto di
tipo System.Mana-gement.Automation.PathInfo che descrive il percorso di lavoro
corrente. Questa classe contiene i riferimenti al provider, al drive e
alla posizione di riferimento all’interno dello stesso, così come ci si
potrebbe aspettare.
Nello script che segue, per esempio, si utilizza Get-Location dapprima
per rappresentare a video la posizione corrente, poi per recuperare il
provider associato:

PS C:\Users\ikmju> Get-Location

Path
----
C:\Users\ikmju

PS C:\Users\ikmju> (Get-Location).Provider

Name Capabilities Drives


---- ------------ ------
FileSystem Filter, ShouldProcess {C, A, D}

A questo cmdlet sono associati sia l’alias gl, nativo di PowerShell,


sia pwd, omonimo del comando presente nelle shell di derivazione
*nix.

Set-Location
Il comando Set-Location agisce in maniera speculare rispetto a Get-
Location e imposta il percorso di lavoro corrente. Mediante il
parametro posizionale -Path di questo cmdlet, infatti, è possibile
specificare una stringa che identifica univocamente un percorso
all’interno di uno dei drive supportati dalla shell.
Nello script che segue, per esempio, si utilizza questo cmdlet per
impostare il percorso di lavoro corrente su di una particolare cartella
del drive D:

PS C:\Users\ikmju> Set-Location D:\Folderl\Folder2


PS D:\Folderl\Folder2>

In quest’altro esempio, invece, la posizione di lavoro corrente è una


chiave del registry di Windows:

PS C:\Users\ikmju> Set-Location HKLM:\SYSTEM\CurrentControlSet


PS HKLM:\SYSTEM\CurrentControlSet>

NO Il prompt di PowerShell riflette la posizione corrente anche per le posizioni gestite


TA da provider diversi da FileSystem, come nel caso di Registry.

Se il percorso fornito a Set-Location è relativo e non include, pertanto,


l’indicazione del drive, allora il comando interpreta il valore in base
alla posizione di lavoro corrente, alla stregua di quanto avviene
all’interno di Windows e in qualsiasi altra shell a riga di comando.
L’interpretazione dei percorsi relativi avviene in base ad alcuni
caratteri speciali da porre all’inizio della stringa del percorso, illustrati
nella Tabella 17.2.

Tabella 17.2 - Caratteri speciali per l’indicazione dei percorsi


relativi.
Sequenze Destinazione
. Percorso di lavoro corrente
.. Elemento padre del percorso di lavoro corrente
\ Elemento radice del drive del percorso di lavoro corrente

Eseguendo questo script, per esempio, il cmdlet Set-Location nel primo


caso cambia il percorso di lavoro corrente portandolo al livello
immediatamente superiore, mentre nel secondo non sortisce alcun
effetto poiché il percorso è una radice del drive:

PS C:\Users\ikmju> Set-Location ..
PS C:\Users>
PS C:\Users> Set-Location Function:
PS Function:\> Set-Location ..
PS Function:\>

Se si fornisce un percorso inesistente, il comando genera un errore


appropriato, come ci si potrebbe aspettare:

PS C:\Users\ikmju> Set-Location HKLM:


PS HKLM:\> Set-Location . \SOFTWARE\ChiaveInesistente
Set-Location : Impossibile trovare il percorso 'HKLM:\SOFTWARE\
Chiavelnesistente' perché non esiste.
In riga:l car:13
+ Set-Location <<<< . \SOFTWARE\ChiaveInesistente
+ Categorylnfo : ObjectNotFound: (HKLM:\SOFTWARE\
Chiavelnesistente:String) [Set-Location], ItemNotFoundEx
ception
+ FullyQualifiedErrorld : PathNotFound,Microsoft -PowerShell- Commands.
SetLocationCommand

Per fornire un percorso a Set-Location ci si può avvalere del


completamento automatico disponibile all’interno della shell e valido
per qualsiasi provider: premendo il tasto Tab, infatti, il sistema
esamina il testo digitato e propone una lista di possibili elementi il cui
nome inizia (o corrisponde) a quanto immesso. Alla stregua di
quanto avviene per i comandi, inoltre, laddove esista più di un
risultato possibile, la shell consente di scorrere gli elementi trovati -
ordinati alfabeticamente - con successive pressioni del tasto Tab
(oppure Maiusc + Tab per scorrere all’indietro). Al cmdlet Set-Location
è abbinato l’alias standard cd, omonimo del comando presente nelle
precedenti shell Microsoft e in quelle di derivazione *nix, dotato di
obiettivi analoghi a quello di PowerShell.

Recuperare gli elementi


Il recupero degli elementi di un drive o di una cartella è
un’operazione fondamentale in qualsiasi shell. Nel seguito di questa
sezione si analizza il supporto che la shell offre per portare a termine
questa attività, in maniera indipendente dal provider impiegato.

Get-ChildItem
Il cmdlet Get-ChildItem ritorna la sequenza di elementi contenuti in un
percorso specifico, gestito da un provider caricato nella sessione. È
possibile indicare a questo comando il percorso desiderato
impiegando il parametro posizionale -path; nel caso in cui tale valore
sia omesso, Get-chiiditem recupera gli elementi figlio presenti nel
percorso di lavoro corrente. Questo cmdlet ritorna tipi di oggetto
diversi a seconda del provider utilizzato e dell’elemento ritornato.
• il provider FileSystem ritorna oggetti di tipo Fileinfo per indicare un
elemento di tipo file e oggetti di tipo Directoryinfo per le cartelle.
Entrambe le classi risiedono nel namespace System.IO;
• il provider Registry ritorna oggetti di tipo Microsoft.Win32.RegistryKey;
• la maggior parte degli oggetti ritornati dal provider variable è
un’istanza della classe System.Management.Automation.PSVariable. Alcuni
elementi, inoltre, sono ritornati utilizzando tipi specifici differenti,
appartenenti al namespace System.Management.Automation;
• il provider Alias ritorna elementi di tipo System-
Management.Automation.AliasInfo;

• il provider Function ritorna oggetti di tipo FunctionInfo per le funzioni


e istanze del tipo Fiiterinfo per i filtri. Entrambe le classi
appartengono al namespace System-Management-Automation;
• il provider Environment ritorna oggetti di tipo
System.Collections.DictionaryEntry, costituti da una coppia nome-

valore;
• il provider Certificate ritorna elementi di tipo X509certificate2 per i
certificati e X509store per i contenitori, entrambi appartenenti al
namespace System. Security.Cryptography.X509Certificates;
• il provider WSMan ritorna oggetti di tipo diverso, in base
all’elemento di confi gurazione corrispondente. Tutte le classi,
tuttavia, risiedono nel namespace Microsoft.WSMan.Management.
In questo script, per esempio, il cmdlet Get-ChildItem è utilizzato per
recuperare gli elementi figlio di un percorso di un drive del file
system:

PS C:\Users\ikmju> Get-ChildItem C:\Windows

Directory: C:\Windows

Mode LastWriteTime Length Name


---- ------------- ------ ----
d-- 15/01/2010 11.12 AppPatch
d-r-s 22/01/2010 11.52 assembly
d---- 19/01/2008 10.40 Boot
d---- 19/01/2008 12.35 Branding
d---- 19/01/2008 10.40 Cursors
[...]

In modo analogo, è possibile recuperare con facilità gli elementi figlio


di un percorso di un drive del registry di Windows:

PS C:\Users\ikmju> Get-ChildItem HKLM:\SYSTEM\CurrentControlSet\Control

Hive: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control

SKC VC Name Property


--- -- ---- --------
0 7 AGP {102B0520, 102B0521, 102B0525, 10DE0100...}
3 0 Arbiters {}
3 0 BackupRestore {}
56 0 Class {}
4 1 CMF {CompressedSegments}
[...]

Per i provider non gerarchici, come Variable, Alias, Function e Environment,


è sufficiente specificare il percorso radice del drive desiderato:

PS C:\Users\ikmju> Get-Childltem Env:

Name Value
---- -----
ALLUSERSPROFILE C:\ProgramData
APPDATA C:\Users\ikmju\AppData\Roaming
CLIENTNAME ALKAID
CommonProgramFiles C:\Program Files\Common Files
COMPUTERNAME WIN-TT6M1MHLDN1

PS C:\Users\ikmju> Get-Childltem Function:

CommandType Name Definition


----------- ---- ----------
Function A: Set-Location A:
Function B: Set-Location B:
Function C: Set-Location C:
Function cd.. Set-Location ..
[...]

Il cmdlet Get-Childltem non visualizza, di default, gli elementi come i file


nascosti e i file di sistema. Utilizzando lo switch -Force, d’altra parte, è
possibile imporre al provider coinvolto di ritornare anche questo tipo
di dati.
Lo switch -Recurse, infine, permette di effettuare un recupero ricorsivo
degli elementi, ritornando tutti gli elementi figlio del percorso
specificato a tutti i livelli di profondità. In questo script, per esempio,
si recuperano tutti i certificati e gli store X.509 della macchina locale:

PS C:\Users\ikmju> Get-ChildItem cert:\LocalMachine -Recurse

Name : SmartCardRoot
Name : AuthRoot
Subject : CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE
Issuer : CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE
Thumbprint : B1BC968BD4F49D622AA89A81F2150152A41D829C
FriendlyName : GlobalSign
NotBefore : 01/09/1998 14.00.00
NotAfter : 28/01/2028 13.00.00
Extensions : {System.Security.Cryptography.Oid, System.Security.
Cryptography.Oid, System.Security.Cryptography.Oid}

Subject : CN=GTE CyberTrust Global Root, OU="GTE CyberTrust Solutions,


Inc.", O=GTE Corporation, C=US
Issuer : CN=GTE CyberTrust Global Root, OU="GTE CyberTrust Solutions,
Inc.", O=GTE Corporation, C=US
Thumbprint : 97817950D81C9670CC34D809CF794431367EF474
FriendlyName : GTE CyberTrust Global Root
NotBefore : 13/08/1998 2.29.00
NotAfter : 14/08/2018 1.59.00
Extensions : {}
Subject : OU=Class 3 Public Primary Certification Authority,
O="VeriSign, Inc.", C=US
Issuer : OU=Class 3 Public Primary Certification Authority,
O="VeriSign, Inc.", C=US
Thumbprint : 742C3192E607E424EB4549542BE1BBC53E6174E2
FriendlyName : VeriSign Class 3 Public Primary CA
NotBefore : 29/01/1996 1.00.00
NotAfter : 02/08/2028 1.59.59
Extensions : {}
[...]

La sintassi d’uso di questo comando, dunque, è riassumibile nel


seguente schema:

Get-Childltem <Percorso> [-Force] [-Recurse]

Il cmdlet Get-ChildItem è usato spesso per mezzo dell’alias dir,


omonimo del relativo comando delle precedenti shell Microsoft, e
tramite l’alias ls, omonimo del relativo comando *nix. Un terzo alias,
gci, è nativo di PowerShell.

Get-Item
Questo cmdlet ritorna la sequenza di item specificati utilizzando il
parametro -path e, tramite lo switch -Force, consente di recuperare
anche gli elementi nascosti, come i file di sistema. La sintassi è
molto semplice:

Get-Item <Percorso> [-Forced

L’esempio che segue illustra come sia possibile utilizzare Get-item per
recuperare una particolare cartella del file system:

PS C:\Users\ikmju> Get-Item C:\

Directory:

Mode LastWriteTime Length Nam


---- ------------- ------ ---
d--hs 18/03/2010 11.58 C:\
Manipolare gli elementi
La shell mette a disposizione dell’utente diversi cmdlet in grado di
interagire con i provider installati nella sessione, per manipolarne gli
elementi. Analogamente a quelli illustrati in precedenza, tutti i cmdlet
riportati nel seguito della sezione sono generalmente utilizzabili con
qualsiasi provider.

New-Item
Questo cmdlet crea un nuovo elemento all’interno di un percorso
gestito da un provider di PowerShell. Accetta il parametro
posizionale -path, tramite il quale è possibile specificare il percorso e
il nome di destinazione dell’elemento e, nel caso in cui il provider
supporti più di un tipo di elemento, accetta il parametro -ItemType, con
cui si può esplicitare una stringa specifica del provider di riferimento.
Con il parametro -Value, inoltre, è possibile associare un oggetto che
rappresenta il valore dell’elemento da creare, che eventualmente
può anche essere recuperato in automatico dalla pipeline.
Lo switch -Force, infine, permette di creare un elemento anche
quando questo ne sovrascrive uno esistente, marcato per l’accesso
in sola lettura: in tal caso, infatti, il comportamento predefinito di
questo comando consiste nel generare un errore appropriato. La
sintassi d’uso di New-Item, dunque, può essere schematizzata così:

New-ltem <Percorso> [-ItemType <Tipo>] [-Value <Valore>] [-Force]

Nello script che segue, per esempio, si utilizza questo cmdlet per
creare dapprima una nuova cartella e poi un nuovo file di testo
all’interno di questa:

PS C:\Users\ikmju> New-Item .\TestDir -ItemType Directory

Directory: C:\Users\ikmju

Mode LastWriteTime Length Name


---- ------------- ------ ----
d---- 19/03/2010 14.16 TestDir

PS C:\Users\ikmju> New-Item .\TestDir\Test.txt -ItemType File -Value "hello, world"


Directory: C:\Users\ikmju\TestDir

Mode LastWriteTime Length Name


---- ------------- ------ ----
-a--- 19/03/2010 14.16 12 Test.txt

In quest’altro script, invece, si crea una nuova chiave all’interno del


registry, dotata di un valore di default di tipo stringa:

PS C:\Users\ikmju> New-Item HKCU:\TestKey -Value "hello, world" Hive:

HKEY_CURRENT_USER

SKC VC Name Property


--- -- ---- --------
0 1 TestKey {(default)}

Come si può notare dall’output dell’esempio, in tutti i casi il cmdlet


emette l’oggetto creato nella pipeline.

Remove-Item
Questo cmdlet elimina gli elementi specificati tramite il parametro
posizionale -Path, che accetta anche i caratteri wildcard. Anche in
questo caso è possibile utilizzare lo switch -Recurse per eliminare tutti
gli elementi figlio di un percorso specifico e lo switch -Force per
imporre al cmdlet di eliminare anche gli elementi che altrimenti non
sarebbero presi in considerazione, come i file nascosti, di sistema o
in sola lettura. La sintassi di base di Remove-Item è:

Remove-Item <Percorso> [-Force] [-Recurse]

Nello script che segue, per esempio, sono eliminati gli elementi
creati nel paragrafo precedente:

PS C:\Users\ikmju> Remove-Item HKCU:\TestKey


PS C:\Users\ikmju> Remove-Item .\TestDir -Recurse

Questo cmdlet è richiamabile utilizzando uno dei diversi alias


predefiniti: del, erase, rd, ri, rm, rmdir.

Rename-Item
Questo cmdlet si occupa di rinominare un elemento gestito da un
provider di PowerShell. Accetta il parametro posizionale -Path, tramite
il quale è possibile specificare l’elemento su cui si intende agire, e il
parametro -NewName, con cui se ne può indicare il nuovo nome. Lo
switch -Force, infine, consente di rinominare anche quegli elementi
che altrimenti non potrebbero essere modificati, come i file nascosti,
di sistema oppure in sola lettura. La sintassi di base necessaria a
eseguire Rename-Item è:

Rename-Item <Percorso> -NewName <Nome> [-Force]

Lo script che segue dimostra quanto sia semplice, per esempio,


rinominare un file e una cartella di un drive del file system:

PS C:\Users\ikmju> New-Item .\Test.txt -ItemType File -Value "hello, world"

Directory: C:\Users\ikmju

Mode LastWriteTime Length Name


---- ------------- ------- ----
-a--- 19/03/2010 15.01 12 Test.txt

PS C:\Users\ikmju> Rename-Item .\Test.txt -NewName HelloWorld.txt

In quest’altro script, invece, si rinomina una variabile:

PS C:\Users\ikmju> $x = 123
PS C:\Users\ikmju> "`$x -> $x, `$y -> $y"
$x -> 123, $y ->
PS C:\Users\ikmju> Rename-Item Variable:\x -NewName "y"
PS C:\Users\ikmju> "`$x -> $x, `$y -> $y"
$x -> , $y -> 123

Il cmdlet Rename-item è utilizzato spesso per mezzo dell’alias predefinito


ren

Copy-Item
Il cmdlet copy-item consente di copiare un elemento da un percorso a
un altro, utilizzando lo stesso provider. Con il consueto parametro
posizionale -path si può indicare l’elemento oggetto della copia, con
l’eventuale supporto dei caratteri wildcard e la destinazione si
specifica per mezzo del parametro posizionale -Destination. L’impiego
dello switch -Recurse, inoltre, indica che la copia degli elementi deve
essere ricorsiva, prendendo in considerazione tutti gli elementi figlio
del percorso indicato (nel caso si tratti di un contenitore, come una
cartella del file system); lo switch -Force, infine, permette di
sovrascrivere gli elementi di destinazione in sola lettura, che
altrimenti genererebbero un errore appropriato. La sintassi di base di
Copy-Item è:

Copy-Item <Sorgente> <Destinazione> [-Recurse] [-Force]

In questo script, per esempio, si copia un file di testo dalla posizione


di lavoro corrente in una sotto-cartella:

PS C:\Users\ikmju> New-Item .\Test.txt -ItemType File -Value "hello, world"

Directory: C:\Users\ikmju

Mode LastWriteTime Length Name


---- ------------- ------ ----
-a--- 19/03/2010 15.16 12 Test.txt

PS C:\Users\ikmju> New-Item .\TestDir -ItemType Directory

Directory: C:\Users\ikmju

Mode LastWriteTime Length Name


---- ------------- ------ ----
d---- 19/03/2010 15.16 TestDir

PS C:\Users\ikmju> Copy-Item .\Test.txt .\TestDir

Gli alias predefiniti per Copy-item ricalcano gli omonimi comandi delle
precedenti shell Microsoft e di quelle di derivazione *nix: copy, cp.

Move-Item
Questo cmdlet sposta un elemento da un percorso a un altro, fermo
restando che questi siano entrambi gestiti dal medesimo provider.
Nel caso di directory, tuttavia, Move-Item è limitato allo spostamento
nell’ambito dello stesso drive di origine. Alla stregua di quanto
avviene per il comando precedente, con il parametro posizionale -
path si può indicare l’elemento oggetto dello spostamento, con

l’eventuale supporto dei caratteri wildcard, mentre la destinazione si


specifica mediante il parametro posizionale -Destination. Lo switch -
, infine, permette di sovrascrivere gli elementi di destinazione in
Force

sola lettura, che altrimenti genererebbero un errore appropriato. La


sintassi di base di Move-Item è molto simile a quella di Copy-Item:

Move-Item <Sorgente> <Destinazione> [-Force]

In questo script, per esempio, si spostano tutte le chiavi e i valori


contenuti in una chiave del registry da un percorso a un altro:

PS C:\Users\ikmju> New-Item HKCU:\Software\TestKeyl\Xyz -Value "test" -Force

Hive: HKEY_CURRENT_USER\Software\TestKeyl

SKC VC Name Property


--- -- ---- --------
0 1 Xyz {(default)}

PS C:\Users\ikmju> New-Item HKCU:\Software\TestKey2 -Type Directory

Hive: HKEY_CURRENT_USER\Software

SKC VC Name Property


--- -- ---- --------
0 0 TestKey2 {}

PS C:\Users\ikmju> Move-Item HKCU:\Software\TestKey1\* HKCU:\Software\TestKey2

Set-Item
Il cmdlet Set-Item è speculare rispetto a Get-item e permette di
impostare un particolare elemento individuato dal percorso fornito,
grazie al parametro posizionale -Path, rispetto al valore specificato
mediante il parametro posizionale -Value. Anche Set-item accetta lo
switch -Force per sovrascrivere gli elementi in sola lettura, operazione
che altrimenti genererebbe un errore.
La sintassi di base del comando è schematizzabile così:

Set-Item <Percorso> <Valore> [-Forced

In questo script, per esempio, Set-item è impiegato per reimpostare il


corpo della funzione c : :

PS C:\Users\ikmju> Set-Item Function:C: { Write-Host "hello, world" }


PS C:\Users\ikmju> C:
hello, world

Mentre in questo script si altera il valore di una variabile:


PS C:\Users\ikmju> $x = 9783
PS C:\Users\ikmju> Set-Item Variable:x 123
PS C:\Users\ikmju> $x
123

Nonostante il cmdlet Set-Item offra un supporto di modifica degli elementi


NO
agnostico rispetto al provider, in alcuni casi (come per le variabili e le funzioni)
TA
potrebbe essere più semplice utilizzare direttamente l’operatore di assegnazione.

Invoke-Item
Il cmdlet Invoke-Item esegue l’operazione predefinita associata al
percorso specificato tramite il parametro posizionale -Path. Nella
versione 2.0 di PowerShell questo comando è limitato al provider
Filesystem e recupera l’eventuale eseguibile associato al file indicato in

base alle impostazioni presenti nel registry di Windows, alla stregua


di quanto è possibile ottenere con il comando Start di CMD.
La sintassi di Invoke-Item è semplice:

Invoke-Item <Percorso>

Nello script che segue, per esempio, si richiede al sistema di aprire


un file di testo utilizzando l’eseguibile associato ai documenti di
questo tipo (di default il Blocco note di Windows):

PS C:\Users\ikmju> Invoke-Item .\Test.txt

Manipolare le proprietà degli elementi


Il supporto offerto da PowerShell non sarebbe completo se non ci
fossero dei comandi in grado di recuperare e modificare le proprietà
degli elementi forniti dai provider. Alcuni di questi, infatti, permettono
di accedere a una quota parte delle informazioni che gestiscono
proprio attraverso le proprietà. Nel caso del provider Registry, per
esempio, i valori memorizzati nelle chiavi sono disponibili come
proprietà degli oggetti che rappresentano queste ultime.
Il seguito di questo paragrafo illustra tutti i cmdlet che la shell mette
a disposizione per gestire le proprietà degli elementi.

Get-ItemProperty
Grazie a questo cmdlet è possibile recuperare facilmente le proprietà
di un elemento in base al percorso indicato tramite il parametro
posizionale -path. Il parametro -Name, anche questo posizionale,
permette inoltre di limitare le proprietà ritornate dal comando per
mezzo di un array di stringhe. La sintassi di base di Get-ltemProperty è:

Get-ltemProperty <Percorso> [<Proprietàl> [, <Proprietà2>.- -]]

In questo script, per esempio, si recupera l’edizione di Windows


installata nella macchina facendo riferimento a una specifica voce
del registry:

PS C:\Users\ikmju> Get-ltemProperty 'HKLM:\SOFTWARE\Mierosoft\Windows NT\


OurrentVersion' ProductName

PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\
SOFTWARE\Microsoft\Windows NT\CurrentVersion
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\
SOFTWARE\Microsoft\Windows NT
PSChildName : CurrentVersion
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
ProductName : Windows Server (R) 2008 Standard without Hyper-V

Si noti come, oltre alla proprietà richiesta (ProductName), il cmdlet abbia


restituito un oggetto con altre proprietà (individuate dall’iniziale PS): si
tratta di informazioni aggiuntive inserite dalla shell per rappresentare
l’oggetto ritornato.
In quest’altro script, inoltre, Get-ItemProperty è impiegato per
recuperare la data di ultima modifica del file di paging del sistema:

PS C:\Users\ikmju> Get-ItemProperty C:\pagefile.sys LastWriteTime

PSPath : Microsoft.PowerShell.Core\FileSystem::C:\pagefile.sys
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\
PSChildName : pagefile.sys
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
LastWriteTime : 12/03/2010 18.12.18

Il cmdlet Get-ItemProperty è frequentemente utilizzato per mezzo


dell’alias prede-finito gp.

Set-ltemProperty
Set-ItemProperty agisce in maniera speculare al comando
precedente e permette di impostare il valore di una proprietà di un
elemento, individuata specificando il percorso di quest’ultimo grazie
al parametro posizionale -Path e il nome della prima mediante il
parametro posizionale -Name. Il valore da impiegare, inoltre, è fornito
tramite il parametro -Value, anch’esso posizionale.
Il cmdlet, infine, ammette l’impiego dello switch -Force per
sovrascrivere le proprietà degli elementi in sola lettura. La sintassi di
base di Set-ItemProperty è:

Set-ItemProperty <Percorso> <Proprietà> <Valore> [-Forced]

Lo script che segue, per esempio, aggiunge alla barra del titolo di
Internet Explorer un testo (tipicamente utilizzato per fare branding)
sfruttando una particolare voce del registry di Windows:

PS C:\Users\ikmju> Set-ItemProperty 'HKCU:\Software\Microsoft\Internet


Explorer\Main' 'Window Title' 'powershell.it

New-ltemProperty
Il cmdlet New-itemProperty permette di creare una nuova proprietà di un
elemento, in maniera molto simile a quanto avviene con il comando
precedente. Il percorso dell’elemento si specifica tramite il parametro
posizionale -Path e il nome della proprietà per mezzo del parametro
posizionale -Name. Il valore da impiegare, inoltre, è fornito tramite il
parametro -Value. Anche questo cmdlet, infine, ammette l’impiego
dello switch -Force per creare proprietà negli elementi in sola lettura.
La sintassi di base di New-ItemProperty è riassumibile così:

New-ItemProperty <Percorso> <Proprietà> -Value <Valore> [-Force]

Nell’esempio che segue si sfrutta il cmdlet per abilitare il log del


caricamento degli assembly di Microsoft .NET, aggiungendo una
specifica voce al registry di Windows:

PS C:\Users\ikmju> New-ItemProperty HKLM:\Software\Microsoft\Fusion


EnableLog -Value 1

PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\
Software\Microsoft\Fusion
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\
Software\Microsoft
PSChildName : Fusion
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
EnableLog : 1

Remove-ItemProperty
Il cmdlet Remove-ItemProperty elimina una proprietà associata a un
elemento, in maniera speculare a quanto effettuato dal comando
precedente. L’elemento su cui si desidera intervenire si specifica
tramite il parametro posizionale -Path e il nome della proprietà da
eliminare per mezzo del parametro posizionale -Name. Utilizzando lo
switch -Force, infine, è possibile eliminare le proprietà degli elementi
in sola lettura, operazione che altrimenti genererebbe un errore.
Il cmdlet Remove-ItemProperty ha una sintassi di base riassumibile
mediante questo schema:

Remove-ItemProperty <Percorso> <Proprietà> [-Force]

Nel blocco che segue, quindi, questo comando viene utilizzato per
rimuovere il valore aggiunto alla chiave del registry nell’esempio
precedente:

PS C:\Users\ikmju> Remove-ItemProperty HKLM:\Software\Microsoft\Fusion EnableLog

Rename-ItemProperty
Questo cmdlet reimposta il nome di una proprietà di un elemento
gestito da un provider di PowerShell. Utilizzando i parametri
posizionali -Path, -Name e -NewName è possibile specificare,
rispettivamente, il percorso dell’elemento di interesse, il nome della
proprietà da modificare e il nuovo nome da utilizzare. Lo switch -
Force, infine, può essere impiegato per rinominare una proprietà di un
elemento in sola lettura.
La sintassi di base di Rename-ItemProperty è:

Rename-ItemProperty <Percorso> <Proprietà> <NuovoNome> [-Force]

In questo script, per esempio, il cmdlet è utilizzato per rinominare un


valore del registry di Windows:
PS C:\Users\ikmju> Rename-ItemProperty HKCU:\TestKey TestValue NewTestValue

Copy-ItemProperty
Questo cmdlet è in grado di copiare una proprietà da un elemento a
un altro. Il percorso dell’elemento di origine è specificato tramite il
parametro posizionale -Path, mentre quello di destinazione con il
parametro -Destination, anch’esso posizionale. Il nome della proprietà,
infine, è indicato grazie al parametro posizionale -Name, mentre con lo
switch -Force si permette al comando di sovrascrivere l’eventuale
proprietà nell’elemento di destinazione. La sintassi del cmdlet è
schematizzabile così:

Copy-ItemProperty <Origine> <Destinazione> <Proprietà> [-Force]

In questo blocco, per esempio, il comando è utilizzato per copiare il


nome del driver della porta seriale di Windows dalla sua chiave del
registry a un’altra:

PS C:\Users\ikmju> Copy-ItemProperty HKLM:\SYSTEM\CurrentControlSet\


Services\Serial HKCU:\TestKey DisplayName

Move-ItemProperty
si occupa di spostare una proprietà da un elemento a
Move-ItemProperty

un altro. Sia i parametri del cmdlet sia il suo funzionamento sono


dunque molto simili al comando precedente, da cui si
contraddistingue per il fatto che la proprietà è eliminata dall’oggetto
sorgente non appena la copia dei valori termina. La sintassi di
questo comando, dunque, è schematizzabile così:

Move-ItemProperty <Origine> <Destinazione> <Proprietà> [-Force]

Nell’esempio che segue il cmdlet è utilizzato per spostare un paio di


elementi della lista dei comandi avviati di recente (MRU) tramite la
finestra di dialogo Esegui di Windows dalla loro posizione nel
registry a un’altra, custom:

PS C:\Users\ikmju> Move-ItemProperty HKCU:\Software\Microsoft\Windows\


CurrentVersion\Explorer\RunMRU HKCU:\TestKey a,b
I parametri di attenuazione dei rischi
Tutti i cmdlet esaminati in questo capitolo possono accettare due
switch aggiuntivi, in grado di mitigare i possibili rischi derivati dal
potenziale utilizzo dei comandi e tra l’altro comuni a molti altri
cmdlet: in questo paragrafo se ne illustra l’impiego.

Switch -WhatIf
Quando è specificato, questo utilissimo switch non esegue il cmdlet
attivamente, ma si limita a mostrare a video un messaggio che
descrive l’operazione che il comando avrebbe altrimenti effettuato.
Aggiungendo lo switch -WhatIf a un’operazione di spostamento, per
esempio, la shell visualizza un messaggio simile a questo senza
sortire alcun effetto sul file specificato:

PS C:\Users\ikmju> Move-Item C:\pagefile.sys D:\Temp -WhatIf


WhatIf: Esecuzione dell'operazione "Sposta file" sulla destinazione
"Elemento: C:\pagefile.sys Destinazione: D:\Temp".

Switch -Confirm
Lo switch -Confirm, infine, prima di eseguire il comando cui è
applicato mostra a video lo stesso messaggio descrittivo visualizzato
da -WhatIf e successivamente chiede conferma all’utente prima di
eseguire il cmdlet desiderato. La modalità con cui -Confirm richiede
conferma all’utente varia a seconda dell’interfaccia di PowerShell
utilizzata; le interfacce grafiche, solitamente, impiegano una finestra
modale a parte. Specificando lo switch -Confirm per l’eliminazione di
una chiave del registry di Windows, per esempio, l’interfaccia a
console testuale di PowerShell chiede conferma all’utente in un
modo simile a questo:

PS HKLM:\SOFTWARE\Microsoft> Remove-Item Windows -Confirm

Conferma
Per l'elemento in HKLM:\SOFTWARE\Microsoft\Windows sono disponibili elementi
figlio e non è stato specificato il parametro Recurse. Se si continua, assieme
all'elemento verranno rimossi anche tutti gli elementi figlio. Continuare?
[S] Sì [T] Sì a tutti [N] No [U] No a tutti [O] Sospendi [?] Guida (il
valore predefinito è "S"):
Sia -WhatIf sia -Confirm, quindi, sono validi strumenti che permettono di
evitare possibili danni durante le normali attività di utilizzo di
PowerShell.
WMI

L’architettura di WMI comandi


e un’analisi dettagliata dei
che la shell mette a disposizione per manipolare e
recuperare informazioni sulle diverse componenti
hardware e software gestibili da Windows tramite
questa tecnologia.

Windows Management Instrumentation (WMI) è un componente


software creato per consentire la gestione e l’automazione dei
sistemi Windows attraverso un’interfaccia standard e un unico punto
di accesso, in modo indipendente dall’hardware installato e dalla
versione del sistema operativo in uso. Questo componente è
l’implementazione Microsoft di Web-based Enterprise Management
(WBEM), uno standard creato da Distributed Management Task
Force (DMTF) con lo scopo di definire un set di tecnologie sviluppate
per unificare la gestione dei sistemi informatici. PowerShell offre un
supporto eccellente per interagire con WMI, sia attraverso le classi
esposte dal framework Microsoft .NET sia grazie ad alcune
funzionalità native: il seguito del capitolo analizza questa tecnologia
e propone diversi spunti di applicazione.
L’architettura
Il modello WBEM raggruppa diversi standard con l’obiettivo di
rendere fruibile un intero catalogo di informazioni relative alla
configurazione di un determinato sistema, alla stregua di un
database. Avvalendosi del modello CIM (Common Information
Model), WBEM è in grado di rappresentare, tramite documenti in
formato XML, uno o più elementi di configurazione, includendo dati
relativi a oggetti fisici, come gli hard disk e le schede di rete, oppure
relativi a oggetti virtuali, come i processi, i servizi o le cartelle.
Nonostante il modello sia condiviso tra i produttori di sistemi
operativi e quelli di hardware, lo standard CIM è comunque aperto e
consente a ciascuno di aggiungere funzionalità specifiche, relative al
proprio prodotto.
Per analogia con il mondo dello sviluppo del software, il modello CIM
che definisce le funzionalità esposte da un elemento della
configurazione è chiamato genericamente classe, in
contrapposizione con il termine oggetto che ne identifica un’istanza.
L’architettura di WMI prevede che all’interno di Windows sia attivo il
servizio winMgmt, in grado di rispondere alle richieste di
approvvigionamento dati tramite protocollo WBEM, sia per le
invocazioni locali sia per quelle remote. Il dialogo tra client e server
avviene utilizzando sia DCOM sia RPC, quindi per far funzionare
WMI è indispensabile che questi componenti siano attivi e che le
porte utilizzate per la comunicazione non siano bloccate da un
firewall.
Nel caso in cui si desideri utilizzare WMI da remoto, inoltre, è
necessario prestare attenzione al fatto che l’utente che effettua
l’interrogazione tramite WBEM deve essere amministratore locale
nella macchina di destinazione, pena l’esito negativo dell’attività.

NO Per ulteriori dettagli su DCOM, RPC e l’eventuale gestione delle porte coinvolte
TA nella comunicazione con questi componenti si rimanda al sito Microsoft TechNet.

Come anticipato, il database di oggetti gestito da WMI è estendibile:


l’insieme delle classi gestite dal servizio WinMgmt, infatti, è il risultato
dell’unione delle classi esposte dai moduli installati nel sistema. Ogni
modulo in grado di supportare una o più tipologie di classi WMI
prende il nome di provider WMI e fa capo a una libreria (.dll)
memorizzata all’interno della cartella %SystemRoot%\System32\wbem.
Il numero di classi e oggetti supportati da WBEM potrebbe diventare
difficilmente gestibile se lo standard non avesse previsto il concetto
di namespace: alla stregua di quanto avviene per le classi del
framework Microsoft .NET, anche all’interno di WBEM ogni classe
appartiene a un determinato namespace, in maniera tale da
renderne più semplice il recupero e l’individuazione. Tipicamente
ogni provider WMI aggiunge al database di classi gestite da questa
tecnologia un nuovo namespace, dove raggruppa le proprie classi
WMI.

Recuperare le informazioni
Per recuperare e, eventualmente, interagire con la configurazione di
un determinato apparato (hardware o software) del sistema è
necessario conoscere il namespace e la classe su cui si desidera
operare all’interno di WMI.
Prima di analizzare gli strumenti messi a disposizione da
PowerShell, può essere utile dare una prima occhiata all’alberatura
dei namespace WMI all’interno di un sistema Windows utilizzando lo
snap-in MMC di WMI, seguendo questi passaggi:
1. lanciare il file wmimgmt.msc dalla finestra di dialogo Esegui di
Windows;
2. fare clic destro sull’elemento Controllo WMI (computer
locale) e selezionare la voce Proprietà (la voce Connetti a un
altro computer consente di verificare i namespace WMI
supportati da un’altra macchina, in remoto);
Figura 18.1 - Selezione della voce Proprietà dal menu contestuale dell’elemento Controllo
WMI.

3. cliccare sulla scheda Protezione. Il risultato è una vista ad


albero dei namespace WMI presenti nella macchina.
Figura 18.2 - La vista ad albero dei namespace WMI, all’interno della scheda Protezione.

PowerShell WMI Explorer


Un’alternativa decisamente più completa e funzionale rispetto allo
snap-in MMC è rappresentata da PowerShell WMI Explorer, uno
script di PowerShell creato da Marc Van Orsouw, conosciuto in rete
con lo pseudonimo di /\/\o\/\/ (o PowerShell Guy). Lanciando questo
script, scaricabile gratuitamente dal sito dell’autore, è possibile
esplorare la gerarchia dei namespace WMI presenti in una macchina
e interagire con le istanze esposte da ciascuna classe; l’intero script,
dietro le quinte, impiega i cmdlet di interazione con WMI analizzati
nel seguito del capitolo.

PowerShell WMI Explorer è scaricabile da questo indirizzo:


NO
http://thepowershellguy.com/blogs/posh/archive/2007/03/22/powershell-wmi-
TA
explorer-part-1.aspx

Una volta scaricato lo script, è possibile avviare PowerShell WMI


Explorer dal prompt dei comandi di PowerShell, in questo modo:
PS C:\Users\ikmju> .\WmiExplorer.ps1

L’interfaccia del prodotto è divisa in diversi riquadri, dove sono


visualizzate le informazioni di pertinenza per il namespace e la
classe selezionati.

Figura 18.3 - L’interfaccia di PowerShell WMI Explorer.

Utilizzando il primo riquadro, in alto a sinistra, è possibile connettersi


alla macchina desiderata, digitandone il nome nel box Computer (di
default la macchina locale) e facendo clic sul pulsante Connect. A
connessione avvenuta, il riquadro sottostante contiene la lista dei
namespace disponibili (Figura 18.4).
Figura 18.4 - Il riquadro dei namespace disponibili in PowerShell WMI Explorer.

Facendo doppio clic su di un namespace qualsiasi tra quelli


visualizzati, PowerShell WMI Explorer carica la lista delle classi WMI
che gli fanno capo e la mostra nel riquadro in basso a sinistra. In
Figura 18.5 si è scelto di visualizzare le classi che appartengono al
namespace ROOT\CIMV2 (uno dei namespace più ricchi di classi e
funzionalità).
Figura 18.5 - Il riquadro delle classi del namespace ROOT\CIMV2.

Il doppio clic su di una classe WMI tra quelle elencate porta al


caricamento dei riquadri di destra, con le informazioni sulle proprietà
e i metodi che questa espone. In Figura 18.6 si è scelta la classe
Win32_PhysicalMemory, che permette di recuperare informazioni sui banchi

di memoria RAM presenti nella macchina.


Figura 18.6- I dettagli della classe WMI Win32_PhysicalMemory.

Il pulsante Get instances, infine, carica la lista delle istanze per la


classe WMI selezionata all’interno di una griglia nel riquadro in
basso a destra. La Figura 18.7, in questo caso, mostra la griglia con
i banchi di memoria della macchina locale.

Figura 18.7 - Il recupero delle istanze della classe WMI Win32_PhysicalMemory

Get-WmiObject
Quando si tratta di WMI, Get-WmiObject è il cmdlet principale di
PowerShell: grazie a una sintassi semplice ma ricca di funzionalità,
questo comando è in grado sia di recuperare le istanze di una
particolare classe WMI sia di fornire informazioni sulle classi e sui
namespace disponibili all’interno del sistema.
Mediante i parametri -Class (posizionale) e -Namespace è possibile
recuperare la lista delle istanze associate a una determinata classe
WMI, secondo questa sintassi:
Get-WmiObject [-Namespace <Namespace>] -Class <Classe>

Quando il namespace non è specificato, il comando utilizza il


namespace di default

ROOT\CIMV2.

Per recuperare le istanze della classe WMI Win32_PhysicalMemory della


macchina corrente, dunque, è sufficiente eseguire questo script:

PS C:\Users\ikmju> Get-WmiObject Win32_PhysicalMemory

__GENUS : 2
__CLASS : Win32_PhysicalMemory
__SUPERCLASS : CIM_PhysicalMemory
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : Win32_PhysicalMemory.Tag="Physical Memory 0"
__PROPERTY_COUNT : 30
[...]

Le informazioni ritornate dal comando sono variabili, in base alla


classe richiesta: per esplorare i membri di ciascuna classe è
sufficiente utilizzare il cmdlet Get-Member, oppure usufruire
dell’eccellente supporto offerto da PowerShell WMI Explorer.
Riformulando l’esempio precedente dopo aver analizzato le proprietà
di interesse per la classe Win32_PhysicalMemory, dunque, si potrebbe
utilizzare questo script:

PS C:\Users\ikmju> Get-WmiObject Win32_PhysicalMemory | Select BankLabel,


9{Expression={$_.Capacity / 1GB}}

BankLabel $_.Capacity / 1GB


--------- -----------------
BANK0 2
BANK1 2
BANK2 2
BANK4 2

Utilizzando lo switch -List, il cmdlet elenca le classi disponibili


all’interno del namespace specificato.
In questo script, per esempio, si recuperano tutte le classi WMI
residenti all’interno del namespace ROOT\CIMV2:

PS C:\Users\ikmju> Get-WmiObject -Namespace ROOT\CIMV2 -List

NameSpace: ROOT\CIMV2
Name Methods Properties
---- ------- ----------
[...]
Win32_PrivilegesStatus {} {Description, Operation, ParameterInfo,
PrivilegesNotHeld...}
Win32_JobObjectStatus {} {AdditionalDescription, Description, Operation,
ParameterInfo...}
Win32_Trustee {} {Domain, Name, SID, SidLength...}
Win32_ACE {} {AccessMask, AceFlags, AceType,
GuidInheritedObjectType...}
Win32_SecurityDescriptor {} {ControlFlags, DACL, Group, Owner...}
Win32_ComputerSystemEvent {} {MachineName, SECURITY_DESCRIPTOR, TIME_CREATED}
Win32_ComputerShutdownEvent {} {MachineName, SECURITY_DESCRIPTOR, TIME_CREATED,
Type}
Win32_IP4RouteTableEvent {} {SECURITY_DESCRIPTOR, TIME_CREATED}
Win32_SystemTrace {} {SECURITY_DESCRIPTOR, TIME_CREATED}
Win32_ProcessTrace {} {PageDirectoryBase, ParentProcessID, ProcessID,
ProcessName...}
[...]

Il parametro -ComputerName indica a Get-WmiObject che l’attività di recupero


delle informazioni deve essere portata a termine nella macchina
desiderata: in tal caso è possibile indicarne l’indirizzo IP, il nome
NetBIOS o il nome di dominio completo (FQDN). Quando il
parametro -ComputerName è specificato, il cmdlet interroga il sistema di
destinazione utilizzando DCOM/RPC.
Nello script che segue, per esempio, si recuperano le partizioni dei
dischi di un PC Windows presente nella rete, attraverso la classe
WMI Win32_DiskPartition:

PS C:\Users\ikmju> Get-WmiObject Win32_DiskPartition -ComputerName


192 -168 -178 -25

NumberOfBlocks : 266330112
BootPartition : True
Name : Disco #0, partizione #0
PrimaryPartition : True
Size : 136361017344
Index : 0

Tutti gli oggetti ritornati da Get-WmiObject contengono alcune proprietà


che consentono di risalire alla macchina, al namespace e alla classe
da cui sono stati recuperati:
• la proprietà__SERVER ritorna il nome della macchina di
provenienza, anche se il recupero è locale;
• la proprietà__NAMESPACE ritorna il namespace di origine degli
oggetti;
• la proprietà __CLASS ritorna il nome della classe WMI a cui
l’oggetto fa capo.
Altre due proprietà, infine, consentono di recuperare per ciascun
oggetto una stringa che ne permetta l’individuazione univoca
all’interno del set di origine, alla stregua di quanto avviene con le
chiavi primarie nei database relazionali:
• la proprietà__PATH contiene il path assoluto, con l’indicazione
della macchina, del namespace, della classe e dei valori di
proprietà chiave dell’oggetto;
• la proprietà__RELPATH contiene, invece, il path relativo, che a
differenza di quello assoluto non contiene né l’indicazione della
macchina né quella del namespace di provenienza.
Il percorso assoluto assume questa sintassi:

\\<Macchina>\<Namespace>:<Classe>.<Proprietà1>=<Valore1> [,<Proprietà2>=<Valore2>]

A titolo di esempio, nello script che segue si impiega la classe WMI


Win32_Printer per recuperare il path assoluto di ciascuna stampante
installata in una macchina remota:

PS C:\Users\ikmju> Get-WmiObject WIN32_Printer -ComputerName ALKAID | Select-Object


__PATH

__PATH
------
\\ALKAID\root\cimv2:Win32_Printer.DeviceID="Send To OneNote 2007"
\\ALKAID\root\cimv2:Win32_Printer.DeviceID="Microsoft XPS Document Writer"
\\ALKAID\root\cimv2:Win32_Printer.DeviceID="HP LaserJet 1020"
\\ALKAID\root\cimv2:Win32_Printer.DeviceID="Fax"
\\ALKAID\root\cimv2:Win32_Printer.DeviceID="\\\\RING0\\HP LaserJet 1020"

Per recuperare i namespace WMI disponibili in un sistema Windows,


infine, è possibile utilizzare questo cmdlet a fronte della classe
__Namespace, presente nel namespace ROOT. In tal caso PowerShell

ritorna il nome di ciascun elemento trovato attraverso la proprietà


Name:

PS C:\Users\ikmju> Get-WmiObject __Namespace -Namespace ROOT | Select-Object Name

Name
----
subscription
DEFAULT
CIMV2
Cli
nap
SECURITY
RSOP
WebAdministration
WMI
directory
Policy
Hardware
ServiceModel
Microsoft
aspnet

Il cmdlet Get-WmiObject è molto spesso abbreviato utilizzando l’alias di


sistema gwmi.

Filtrare gli oggetti WMI


Gli oggetti ritornati da Get-WmiObject sono a tutti gli effetti oggetti di
Microsoft .NET e, per tale ragione, è possibile utilizzare la pipeline e
i cmdlet analizzati nel Capitolo 8 per filtrarne i valori e recuperare
solo i dati di interesse.
Lo script che segue, per esempio, dimostra come sia possibile
utilizzare la classe WMI Win32_NetworkAdapter e il cmdletwhere-object per
ritornare le schede di rete di tipo Ethernet presenti nella macchina
locale:

PS C:\Users\ikmju> Get-WmiObject Win32_NetworkAdapter | ? { $_.AdapterType -like


'eth*' }

ServiceName : RTL8167
MACAddress : 00 :1A:5C:DF: 18 :7C
AdapterType : Ethernet 8 02.3
DevicelD : 7
Name : Realtek PCIe GBE Family Controller
NetworkAddresses :
Speed : 10000000C

L’applicazione del filtro agli elementi WMI, d’altronde, avviene a valle


rispetto all’attività di recupero degli stessi da parte del relativo
provider e questo potrebbe causare perdite di performance, in
particolare nei casi in cui il recupero avvenga a fronte di una
macchina remota o la quantità degli elementi di interesse sia molto
elevata. Lo standard WBEM risponde anche a questa necessità con
la possibilità di effettuare delle query direttamente presso il provider
di interesse, utilizzando una sintassi molto simile a SQL, chiamata
CQL (Common Information Model Query Language).
L’implementazione Microsoft dello standard CQL si chiama WQL
(Windows Management Instrumentation Query Language) e
aggiunge al primo alcuni costrutti creati in modo specifico per
supportare le funzionalità aggiuntive di WMI rispetto a WBEM.
Per quanto assomigli molto a SQL, WQL supporta solo un modesto
set di istruzioni dello standard ANSI di questo linguaggio. La sintassi
di base di una query WQL può essere rappresentata da questo
schema:

SELECT <Proprietàl>, <Proprietà2> [, <ProprietàN>]


FROM <ClasseWMI>
[WHERE <Condizione>]

Come potrebbe ricordare il lettore con esperienze precedenti nel


linguaggio SQL, gli elementi sintattici dell’istruzione SELECT all’interno
di WQL sono i seguenti:
• all’istruzione SELECT segue la lista delle proprietà di interesse
della classe da cui attingere le informazioni, separate le une
dalle altre tramite il simbolo virgola (,). Se si desidera
recuperare tutte le proprietà degli oggetti si può usare il simbolo
asterisco (*) in luogo dell’intera sequenza;
• la clausola FROM è obbligatoria e permette di specificare il nome
della classe WMI di interesse; il lettore presti attenzione al fatto
che, diversamente da SQL, in questo caso è possibile
specificare un solo elemento;
• la clausola WHERE introduce gli eventuali filtri da applicare
direttamente presso il provider, senza incorrere nel calo di
performance derivante da un filtro a posteriori. L’intera clausola
è opzionale e, in sua mancanza, sono ritornati tutti gli elementi
della classe WMI indicata.
Supponendo di voler recuperare la lista degli utenti disabilitati
all’interno di un sistema Windows, per esempio, ci si potrebbe
avvalere della classe WMI Win32_UserAccount e creare una query WQL
che filtri gli elementi in base al valore della proprietà Disabled, in modo
simile a questo:

SELECT Name
FROM Win32_UserAccount
WHERE Disabled = FALSE

Per eseguire una query WQL ci si può avvalere del parametro -Query
del cmdlet Get-WmiObject; in tal caso, d’altra parte, non è consentito
l’impiego del parametro -Class, mentre è ancora possibile specificare
il namespace desiderato utilizzando -Namespace (di default ROOT\CIMV2). In
tale scenario, dunque, la sintassi del cmdlet assume la seguente
connotazione:

Get-WmiObject [-Namespace <Namespace>] -Query <WQL>

Per eseguire la query WQL dell’esempio precedente è sufficiente


eseguire questo script:

PS C:\Users\ikmju> Get-WmiObject -Query "SELECT Name FROM Win32_UserAccount WHERE


Disabled = FALSE"

__GENUS : 2
__CLASS : Win32_UserAccount
[...]
Name : Administrator
__GENUS : 2
__CLASS : Win32_UserAccount
[...]
Name : ikmju

Si noti come l’output prodotto dal comando includa comunque le


proprietà necessarie a PowerShell per gestire correttamente gli
oggetti provenienti da WMI.

NO Per ulteriori dettagli sulla sintassi WQL si rimanda al sito Microsoft TechNet
TA (http://technet.microsoft.com/it-it/default.aspx).

I type accelerator per WMI


Poiché l’integrazione con WMI è fondamentale all’interno di
PowerShell, sono stati previsti tre diversi type accelerator in grado di
facilitare le attività che si avvalgono di questa tecnologia.
II type accelerator [WMI] è in grado di convertire un path che identifica
univocamen te un’istanza di una classe WMI nell’oggetto Microsoft
.NET che la rappresenta.
Impiegando una stringa che corrisponde al valore della proprietà
__PATH dell’oggetto desiderato, dunque, [WMI] ne ritorna l’istanza;
utilizzando un percorso relativo, corri spondente al valore __RELPATH
dell’oggetto. Inoltre, questo type accelerator utilizza la macchina
locale e il namespace di default (ROOT\CIMV2).
In questo esempio, dunque, si assegna a una variabile l’oggetto
WMI che corrisponde a una stampante collegata a una macchina
presente in rete e si mostrano a video i formati di carta supportati:

PS C:\Users\ikmju> $printer = [WMI] '\\ALKAID\root\cimv2:Win32_Printer.


DeviceID="HP LaserJet 1020"'
PS C:\Users\ikmju> $printer.PrinterPaperNames
Letter
Legal
Executive
A4
A5
Envelope #10
[...]

Il type accelerator [WMIsearcher] consente di creare un oggetto in grado


di effettuare le ricerche all’interno di WMI partendo da una query
WQL, in alternativa all’impiego del parametro -Query di Get-Wmiobject. Lo
script che segue mostra come sia possibile utilizzare questo
elemento sintattico per recuperare la lista dei servizi Windows
impostati per l’avvio automatico, ma che risultino non avviati,
impiegando la classe WMI Win32_Service:

PS C:\Users\ikmju> $search = [WMISearcher] 'SELECT * FROM Win32_Service


WHERE StartMode = "Auto" AND State != "Running"'
PS C:\Users\ikmju> $search-Get()

ExitCode : 0
Name : W32Time
ProcessId : 0
StartMode : Auto
State : Stopped
Status : OK
ExitCode : 0
Name : MMCSS
ProcessId : 0
StartMode : Auto
State : Stopped
Status : OK
[...]

La classe ritornata da [WMISearcher] è di tipo


System.Management.ManagementObject-Searcher; come si può notare dall’esempio

precedente, per recuperare gli elementi a cui punta è necessario


richiamare il metodo Get().
Il type accelerator [WMIClass], infine, consente di recuperare un
riferimento a una classe WMI in base al percorso fornito. Questo tipo
di riferimento, come illustrato più avanti, nel paragrafo dedicato
all’invocazione dei metodi WMI, è indispensabile nel caso in cui si
desideri invocare un metodo statico di WMI.

Manipolare gli oggetti WMI


Lo standard WBEM/CIM prevede che, a fianco del recupero delle
informazioni di pertinenza di un determinato sistema, sia anche
possibile effettuare delle operazioni sugli elementi di interesse.

Invocare i metodi WMI


Alla pari di quanto avviene per gli oggetti del framework Microsoft
.NET, anche in quelli provenienti da WMI esistono sia metodi
d’istanza sia metodi statici. I metodi d’istanza sono pertinenti alla
singola istanza di un oggetto, mentre quelli statici sono di pertinenza
delle classi WMI e, generalmente, portano a termine attività
svincolate dalle singole istanze di oggetto.
L’invocazione dei metodi d’istanza WMI all’interno di PowerShell è
davvero semplice, praticamente identica all’invocazione dei metodi
d’istanza per i comuni oggetti del framework. Dietro le quinte,
tuttavia, l’esecuzione dell’attività potrebbe avvenire all’interno di una
macchina remota, sfruttando il protocollo DCOM/RPC. Facendo
seguito a un esempio precedente, dunque, questo script richiama il
metodo d’istanza PrintTestPage() della classe WMI Win32_Printer, che
stampa la pagina di prova della stampante cui afferisce:

$printer = [WMI] 'Win32_Printer.DeviceID="HP LaserJet 1020"'


$printer.PrintTestPage()

Per invocare i metodi statici WMI, invece, è necessario appoggiarsi


al type accelerator [WMIClass] e recuperare un riferimento alla classe di
interesse. Nello script che segue, per esempio, si sfrutta il metodo
statico Create() della classe WMI Win32_Process per avviare un nuovo
processo nella macchina locale:

PS C:\Users\ikmju> ([WMIClass] 'Win32_Process').Create('calc.exe', $null,


[...]
ProcessId : 1124
ReturnValue : 0

Invoke-WmìMethod
Questo cmdlet permette di invocare un metodo WMI in base al
percorso dell’oggetto fornito per mezzo del parametro -path oppure di
-Class e -Namespace, seguendo la stessa logica di Get-WmiObject, a
prescindere dal fatto che il metodo sia d’istanza o statico. Il nome del
metodo è specificato tramite il parametro -Name, mentre l’array di valori
degli argomenti attesi dal metodo è fornito tramite il parametro -
ArgumentList. Rispetto alle diverse sintassi analizzate in precedenza,

l’impiego di Invoke-Wmi-Method permette, inoltre, di gestire con una


maggiore facilità l’esecuzione di un metodo a fronte di più macchine
di destinazione, favorendo uno scenario di gestione enterprise di
WMI.
La sintassi di base di questo cmdlet è rappresentata dallo schema
che segue:

Invoke-WmiMethod -ComputerName <PC1> [, <PC2>, ...] -Path <Path> -Name


<Metodo> -ArgumentList <Argomentol> [, <Argomento2>, ...]

Nello script che segue, per esempio, si avvia un processo tramite


WMI all’interno di tre diversi sistemi Windows, impiegando il metodo
statico Create() di Win32_Process già utilizzato in precedenza:

PS C:\Users\ikmju> Invoke-WmiMethod -ComputerName ALKAID, 10.0-1.2, BOZOS -Path


"Win32_Process" -Name Create -ArgumentList "cale.exe"

[...]
ProcessId : 3636
ReturnValue : 0

[...]
ProcessId : 2080
ReturnValue : 0

[...]
ProcessId : 8309
ReturnValue : 0

Remove-WmiObject
Il cmdlet Remove-WmiObject consente di eliminare una particolare istanza
di un oggetto WMI, dove il concetto di eliminazione è interamente
delegato al provider associato all’oggetto. Nonostante sia possibile
specificare il path, il namespace, la classe e la macchina di
destinazione utilizzando, rispettivamente, i parametri -Path, -Namespace,
-Class e -ComputerName, questo comando è per lo più utilizzato all’interno
della pipeline, in seguito all’emissione della sequenza di oggetti che
si desidera eliminare. La sintassi utilizzata più di frequente, pertanto,
è individuata da questo semplice schema:

<Sequenza WMI> | Remove-WmiObject

L’eliminazione di un oggetto di pertinenza della classe WMI


Win32_Process, per esempio, porta alla terminazione del processo

associato. In questo script, quindi, il cmdlet Remove-WmiObject termina


tutti i processi associati alla calcolatrice di Windows in una macchina
remota:

PS C:\Users\ikmju> Get-WmiObject -Query "SELECT * FROM Win32_Process WHERE


Name='calc.exe'" | Remove-WmiObject

L’eliminazione di un oggetto della classe WMI Win32_Share, invece,


porta all’eliminazione della condivisione di rete associata. Nello
script che segue, per esempio, si elimina la condivisione chiamata
inetpub da una macchina remota:

PS C:\Users\ikmju> Get-WmiObject -ComputerName 10.0.1.1 -Query "SELECT *


FROM Win32_Share WHERE Name='inetpub'" | Remove-WmiObject

Set-WmiInstance
Il cmdlet Set-WmiInstance permette di reimpostare i valori delle proprietà
associate a una particolare istanza di un oggetto WMI, poiché la
modifica delle proprietà degli oggetti ritornati da Get-WmiObject non ha
effetto sugli oggetti WMI di destinazione. Anche in questo caso è
possibile specificare il path, il namespace, la classe e la macchina di
destinazione utilizzando, rispettivamente, i parametri -Path, -Namespace,
-Class e -ComputerName. Le proprietà da aggiornare, invece, sono fornite

mediante un array associativo al parametro -Arguments, che riflette lo


schema Nome=Valore per ogni proprietà di interesse. La sintassi di base
per l’impiego di questo comando è rappresentata dal seguente
schema:

Set-WmiInstance -Path <Path> -Arguments @{ Proprietà1 = Valore1; [Proprietà2 =


Valore2; ...] }

Nello script che segue, per esempio, si impiega Set-WmiInstance per


reimpostare l’etichetta di un volume presente all’interno del sistema:

PS C:\Users\ikmju> Set-WmiInstance -Path 'Win32_Volume.DeviceID="\\\\?\\


Volume{869fbc91-01a2-lldf-87fl-806e6f6e69
}\\"' -Arguments β{ Label = 'Disco rigido' }

[...]
DriveLetter : C:
[...]
Label : Disco rigido
[...]

È anche possibile fornire l’oggetto WMI su cui si desidera effettuare


la modifica tramite la pipeline: in tal caso Set-WmiInstance accetta solo il
parametro -Arguments, in base a questa semplice sintassi:

<Oggetti WMI> | Set-Wmilnstance -Arguments β{ Proprietàl = Valorel; ~Proprietà2 =


Valore2; - - -] }

Nello script che segue, l’esempio precedente è ritrattato in maniera


tale da rendere più agevole il recupero del volume d’interesse,
utilizzando il cmdlet Get-WmiObject:

PS C:\Users\ikmju> Get-WmiObject -Query 'SELECT * FROM Win32_Volume WHERE


DriveLetter = "C:"'
>> Set-Wmilnstance -Arguments β{ Label = "Disco rigido" }
>>

[...]
DriveLetter : C:
[...]
Label : Disco rigido
[...]
COM

Una discussione sul supporto componenti


all’uso dei
COM all’interno della shell e alcuni esempi pratici di
integrazione con la suite di Microsoft Office, con il
firewall di Windows, con Explorer e il sistema di
sintesi vocale.

La tecnologia COM (Component Object Model) è nata all’inizio degli


anni ’90 e rappresenta il primo tentativo di Microsoft di creare
un’interfaccia per componenti software indipendente
dall’implementazione e dal linguaggio di sviluppo utilizzato. In modo
simile al framework Microsoft.NET, di cui è l’indiscusso
predecessore, COM ha l’obiettivo di rendere possibile la coesistenza
di porzioni di software indipendenti all’interno di uno stesso
applicativo, in base a una o più interfacce agnostiche rispetto
all’implementazione, che ne definiscono le caratteristiche e le
funzionalità. Nonostante l’interesse del settore IT verso COM sia
decisamente calato con l’uscita di Microsoft .NET, la realtà dei fatti è
che esistono ancora numerosi componenti software che si
appoggiano a questa tecnologia, comprese diverse porzioni di
Windows stesso. Per certi versi, inoltre, poiché i componenti COM
sono tipicamente sviluppati in codice nativo, alcuni di questi (come
Microsoft DirectX, per esempio) sono destinati a mantenere la
propria natura unmanaged per conservare le proprie performance.
Il seguito di questo capitolo illustra brevemente i concetti chiave
relativi all’impiego di COM all’interno di PowerShell e presenta alcuni
casi d’uso selezionati.

Recuperare i componenti installati


Ogni componente COM è composto da una porzione di codice
eseguibile, dove risiede la logica di funzionamento, e da una serie di
chiavi del registry di Windows (o da un file di manifesto, in formato
XML) necessarie per il corretto recupero del componente da parte
del sistema.
Ciascun componente è individuabile grazie a un codice GUID
(Globally Unique Identifier) a 128 bit, univoco, chiamato in gergo
CLSID (un acronimo di Class Identifier). Tuttavia, per facilitare
l’individuazione dei componenti, COM supporta l’impiego di un
identificativo addizionale chiamato ProgID (Programmatic Identifier),
costituto da una semplice stringa molto più facile da ricordare e
utilizzare.
Quando un’applicazione richiede un riferimento a un’istanza di un
componente COM in base al suo CLSID o al suo ProgID, il sistema si
occupa, in modo trasparente, di ricercare nel registry e nel file
system di Windows la porzione di codice eseguibile relativa al
componente richiesto, quindi la esegue e ritorna un riferimento al
chiamante.
A livello di registry tutte le informazioni sui componenti COM sono
mantenute nell’hive HKEY_CLASSES_ROOT; la chiave CLSID, figlia diretta
dell’hive, contiene una chiave per ogni CLSID gestito dalla macchina,
al cui interno sono memorizzate le informazioni relative alla porzione
di codice che implementa il componente. Anche i ProgID disponibili
sono memorizzati come chiavi figlie dell’hive e all’interno di ciascuna
è mantenuta una chiave CLSID che funge da puntatore rispetto alla
gerarchia illustrata poc’anzi. A partire da Windows XP, inoltre, i
componenti COM prevedono un’ulteriore modalità di installazione
(chiamata, in gergo, RegFree COM), che memorizza i metadati
all’interno di un file di manifesto in formato XML.
A prescindere dal sistema adottato per la registrazione, prima di
impiegare un componente all’interno di uno script o di
un’applicazione è sempre indispensabile consultarne la
documentazione tecnica: per tutti gli oggetti COM distribuiti da
Microsoft è prevista una guida in linea nel sito MSDN, con
un’accurata descrizione di ciascuna proprietà e ogni metodo esposti.

Creazione delle istanze


Per quanto la gestione dei componenti COM possa sembrare molto
complessa, per gli utilizzatori del sistema questa attività avviene in
modo completamente trasparente. PowerShell è in grado di creare
nuovi oggetti COM tramite il cmdlet New-Object e specificando il ProgID di
interesse, utilizzando il parametro-ComObject secondo questa semplice
sintassi:

New-Object -ComObject <ProgId>

Nello script che segue, per esempio, si assegna a una variabile il


riferimento a un’istanza del componente COM
InternetExplorer.Application, che permette di gestire Internet Explorer:

PS C:\Users\ikmju> $ie = New-Object -ComObject InternetExplorer.Application


PS C:\Users\ikmju> $ie | Get-Member

TypeName: System. ComObject#{d30cl6 61-cdaf-Ild0-8a3e-0 0c04fc9e26e}

Name MemberType Definition


---- ---------- ----------
ClientToWindow Method void ClientToWindow (int, int)
ExecWB Method void ExecWB (OLECMDID, OLECMDEXECOPT, Variant, Variant)
GetProperty Method Variant GetProperty (string)
GoBack Method void GoBack ()
GoForward Method void GoForward ()
GoHome Method void GoHome ()
GoSearch Method void GoSearch ()
Navigate Method void Navigate (string, Variant, Variant, Variant,
Variant)
Navigate2 Method void Navigate2 (Variant, Variant, Variant, Variant,
Variant)
PutProperty Method void PutProperty (string, Variant)
QueryStatusWB Method OLECMDF QueryStatusWB (OLECMDID)
Quit Method void Quit ()
[...]
Com’è possibile notare dall’output di Get-Member, l’oggetto contiene un
numero cospicuo di metodi che, seguendo la guida in linea presente
su MSDN, è possibile sfruttare per manipolare l’istanza di Internet
Explorer creata dall’esempio.
Il cmdlet New-Object è purtroppo limitato alla creazione di oggetti COM
in base al ProgID. Molti oggetti, d’altra parte, non sono dotati di questo
identificativo ma dispongono del solo CLSID. Per ovviare a questa
restrizione è possibile utilizzare direttamente il supporto offerto dal
framework Microsoft .NET per gestire i componenti COM, affidandosi
alle classi System.Activator e System.Type. Attraverso il metodo statico
GetTypeFromCLSID(), infatti, System.Type ritorna un tipo di riferimento per il
CLSID specificato, mentre impiegando il metodo statico Create() di
System.Activator si crea una nuova istanza del tipo desiderato.

La creazione di istanze COM a partire da un CLSID, dunque, può


essere schematizzata così:

$type = [Type]::GetTypeFromCLSID(<CLSID>)
$object = [Activator]::CreateInstance($type)

In questo script, per esempio, si crea una nuova istanza di un


oggetto COM a partire dal CLSID {13709620-C279-UCE-A49E-444553540000} e si
ottiene, di rimando, un’istanza dell’oggetto di automazione della shell
di Windows (il cui ProglD è, comunque, Shell.Application):

PS C:\Users\ikmju> $type = [Type]::GetTypeFromCLSID([Guid]'{13709620-C279-11CE-A49E-


444553540000}';
PS C:\Users\ikmju> $object = [Activator]::Createlnstance($type) PS C:\Users\ikmju>
$object | Get-Member

TypeName: System.__ComObject#{866738b9-6cf2-4de8-8767-f794ebe74f4e}

Name MemberType Definition


---- ---------- ----------
AddToRecent Method void AddToRecent (Variant, string)
BrowseForFolder Method Folder BrowseForFolder (int, string, int,
Variant)
CanStartStopService Method Variant CanStartStopService (string)
CascadeWindows Method void CascadeWindows ()
ControlPanelItem Method void ControlPanelItem (string)
EjectPC Method void EjectPC ()
Explore Method void Explore (Variant)
[...]

Alcuni casi d’uso


Il seguito del capitolo è dedicato alla dimostrazione delle potenzialità
di COM all’interno di PowerShell, con la presentazione di alcuni casi
d’uso.

Interagire con Explorer


La shell grafica di Windows, nella veste di Explorer, può essere
facilmente gestita tramite l’oggetto COM Shell.Application. Per
un’analisi approfondita sui metodi e le proprietà disponibili per
questo oggetto si rimanda al sito Microsoft MSDN. Nello script che
segue, per esempio, si istanzia un nuovo oggetto COM di questo
tipo per recuperare un riferimento a ogni finestra gestita dalla shell
(Explorer e Internet Explorer) attiva nel desktop della macchina.
Successivamente si applica un filtro agli elementi trovati e si procede
mostrandone a video il titolo, il percorso e l’handle (HWND):

PS C:\Users\ikmju> $shell = New-Object -ComObject Shell.Application


PS C:\Users\ikmju> $shell.Windows()
>> Where-Object { $_.LocationName -like '*powershell*' }
>> ForEach-Object {
>> "{0} ({1}) - 0x{2:x4}" -f $_.LocationURL, $_.Name, $_.HWNE
>> }
>>
http://www.powershell.it/ (Windows Internet Explorer) - 0x206d4
http://blogs.msdn.com/PowerShell/ (Windows Internet Explorer) - 0x503d9
file:///C:/Development/www.powershell.it (Windows Explorer) - 0x7098c

In quest’altro esempio, invece, si riducono a icona tutte le finestre


visibili:

PS C:\Users\ikmju> $shell.MinimizeAll()

Per poi annullare l’operazione, riportandole alla dimensione assunta


in precedenza:

PS C:\Users\ikmju> $shell.UndoMinimizeALL()

Gli oggetti Shell.Application, inoltre, sono in grado di aprire diverse


finestre solitamente gestite in modo automatico dalla shell, come
mostrato dagli esempi che seguono. La finestra Esegui:

PS C:\Users\ikmju> $shell.FileRun()
La Guida di Windows:

PS C:\Users\ikmju> $shell.Help()

La finestra di Explorer, associata a un qualsiasi percorso:

PS C:\Users\ikmju> $shell.Explore("C:\")

Gestire le unità di rete con WshNetwork


Anche se forse è più conveniente usare la classe WMI Win32_Share, per
gestire le unità di rete ci si può avvalere anche dell’oggetto COM
WScript.Network, nato negli anni ’90 ma ancora utile.
Il metodo EnumNetworkDrives(), per esempio, consente di recuperare
facilmente la lista delle unità registrate nel sistema, come dimostra
questo esempio:

PS C:\Users\ikmju> $network = New-Object -ComObject WScript.Network


PS C:\Users\ikmju> $network.EnumNetworkDrives()

\\tsclient\C
Z:

Il metodo MapNetworkDrive(), inoltre, consente di aggiungere una nuova


unità di rete, specificando opzionalmente le credenziali da fornire per
il collegamento:

PS C:\Users\ikmju> $network.MapNetworkDrive("W:", "\\BOZOS\Share")

Il metodo RemoveNetworkDrive(), infine, permette di eliminare l’unità di rete


specificata:

PS C:\Users\ikmju> $network.RemoveNetworkDrive("F:")

Anche in questo caso, poiché i metodi e le proprietà di WshNetwork


sono davvero in gran numero, si rimanda al sito MSDN per ulteriori
approfondimenti.

Interagire con Excel


Tutti gli applicativi del pacchetto Office sono in grado di essere
automatizzati per mezzo dei relativi oggetti COM, anche se sembra
che Microsoft non abbia mai offerto garanzie esplicite in merito a
questa possibilità: in generale, infatti, gli oggetti COM esposti da
questo pacchetto potrebbero non funzionare correttamente in alcuni
rari scenari (come, per esempio, nel caso in cui l’utente che lancia il
componente non abbia ancora confermato le iniziali del proprio
nome all’interno degli applicativi).
Microsoft Excel è automatizzabile mediante l’oggetto COM che
risponde al ProgID Excel.Application. Il numero di metodi e proprietà per
questo oggetto è decisamente ampio e si rimanda al sito MSDN per
gli approfondimenti del caso. A titolo di esempio, nello script che
segue si istanzia un nuovo oggetto COM Excel, al quale si aggiunge
un nuovo workbook; in seguito, si recupera il primo worksheet (foglio
di lavoro) di questo, per procedere con la manipolazione:

PS C:\Users\ikmju> $excel = New-Object -ComObject Excel.Application


PS C:\Users\ikmju> $workbook = $excel.Workbooks.Add()
PS C:\Users\ikmju> $worksheet = $workbook.Worksheets.Item(1)

Prima di proseguire con l’elaborazione potrebbe essere utile rendere


visibile Excel, perché di default esso è invisibile se avviato tramite
automazione COM:

PS C:\Users\ikmju> $excel.Visible = $true

A titolo di esempio, a questo punto si aggiungono quattro interi al


foglio di lavoro, per poi farne la sommatoria:

PS C:\Users\ikmju> $worksheet.Cells.Item(1, 1) = "9"


PS C:\Users\ikmju> $worksheet.Cells.Item(2, 1) = "83"
PS C:\Users\ikmju> $worksheet.Cells.Item(3, 1) = "19"
PS C:\Users\ikmju> $worksheet.Cells.Item(4, 1) = "7"
PS C:\Users\ikmju> $worksheet.Cells.Item(5, 1) = "=SUM(A1: A4) "

Interagire con Word


Microsoft Word è automatizzabile mediante l’oggetto COM che
risponde al ProglD Word. Application. Anche in questo caso il numero di
metodi e proprietà per questo oggetto è decisamente ampio e si
rimanda al sito MSDN per gli approfondimenti del caso. Nell’esempio
che segue si vuole proporre la soluzione a un problema emerso
durante la stesura di questo libro, quando si rivelava necessario
monitorare costantemente il numero di battute dei diversi documenti
Word di partenza. L’approccio utilizzato consiste nel costruire un
filtro a cui fornire, tramite pipeline, oggetti di tipo System.IO.Fileinfo,
tipicamente ritornati dai cmdlet come Get-childItem: per ogni oggetto
recuperato, il filtro apre tramite COM il relativo documento Word e
recupera il numero delle parole e dei caratteri che questo contiene,
consentendo una successiva rielaborazione dei dati. Il codice del
filtro è:

filter Measure-WordDocument ()
{
BEGIN
{
$word = New-Object -ComObject Word.Application
$word.Visible = $false
}
PROCESS
{
$document = $word.Documents.Open($_.FullName)

$result = New-Object PSObject

$result | Add-Member -MemberType NoteProperty -Name FullName -Value


$_.FullName
$result | Add-Member -MemberType NoteProperty -Name Words -Value
$document.Words.Count
$result | Add-Member -MemberType NoteProperty -Name Characters -Value
$document.Characters.Count

$result

$document.Close()
}
END
{
$word.Quit()
}
}

Si è scelto di evitare di creare un nuovo oggetto COM a ogni iterazione del filtro
NO per non incorrere nel calo di performance che si verificherebbe altrimenti. La
TA gestione dell’oggetto Word, tra l’altro, non prende volutamente in considerazione
la possibilità di errore, per rendere più semplice e comprensibile lo script.

Poiché questo approccio è fortemente orientato alla pipeline, è


possibile usufruire del cmdlet Measure-Object per calcolare la somma dei
risultati recuperati, come dimostra il seguente script, eseguito nel
pieno della realizzazione di questo libro:
PS C:\Users\ikmju> Get-ChildItem -Recurse |
>> Where-Object { $_ -like '*.docx' } |
>> Measure-WordDocument
>> Measure-Object -Sum Characters, Words>>
Count : 19
Average :
Sum : 458421
Maximum :
Minimum :
Property : Characters
Count : 19
Average :
Sum : 92753
Maximum :
Minimum :
Property : Words
Count : 19
Average :
Sum : 92753
Maximum :
Minimum :
Property : Words

Utilizzare le funzionalità di text to speech di Windows


Tutti i sistemi Windows (a partire da Windows 95) consentono di
utilizzare il set di librerie Speech API, creato da Microsoft per gestire
nei propri sistemi la funzionalità di sintesi e riconoscimento vocale.
Grazie al supporto offerto da PowerShell per gestire i componenti
COM, sfruttare queste funzionalità è un’operazione davvero
semplice. In questo script, per esempio, si crea un nuovo oggetto di
tipo SAPi.Spvoice e, impiegando il metodo Speak(), si usa il sistema di
sintesi vocale per riprodurre un testo utilizzando la voce di default
del sistema:

PS C:\Users\ikmju> $speech = New-Object -ComObject SAPI.SpVoice


PS C:\Users\ikmju> $speech- Speak("Ehi, sto parlando a te!")
1

NO A partire dalla versione 3.5 del framework Microsoft .NET, le funzionalità relative
TA alle librerie Speech API sono disponibili all’interno del namespace System.Speech.

Gestire il firewall di Windows


Per gestire l’apertura o la chiusura di una porta in Windows Firewall
è sufficiente utilizzare l’oggetto COM HNetCfg.FwMgr, presente nei
sistemi Windows XP SP2 e superiori. Recuperare la lista delle porte
aperte per il profilo corrente, per esempio, richiede la consultazione
della proprietà LocalPolicy per il profilo corrente, come dimostrato da
questo script:

PS C:\Users\ikmju> $firewall = New-Object -ComObject HNetcfg.FwMgr


PS C:\Users\ikmju> $firewall.LocalPolicy.CurrentProfile.GloballyOpenPorts
>> Format-Table Name, Port, Protocol
>>

Name Port Protocol


---- ---- --------
Skype (TCP) 20223 6
Skype (UDP) 20223 17
uPNP Router Control Port 4100 17

Il valore di Protocol corrisponde alla tipologia di protocollo per il quale


ciascuna porta è stata aperta; consultando MSDN si può verificare
che il valore 6 corrisponde a TCP, mentre il valore 17 a UDP.
Aggiungere una porta alla lista di quelle aperte comporta la
creazione di un oggetto COM il cui ProgID corrisponde a
HNetCfg.FWOpenPort, da aggiungere alla collezione GloballyOpenPorts,
recuperata in precedenza per l’oggetto firewall.
Utilizzando questo semplice script, dunque, si aggiunge al firewall la
porta 8080 in protocollo TCP alla lista delle eccezioni ammesse:

PS C:\Users\ikmju> $port = New-Object -ComObject HNetCfg.FWOpenPort


PS C:\Users\ikmju> $port.Name = "Secondary web server"
PS C:\Users\ikmju> $port.Port = 808C
PS C:\Users\ikmju> $port.Protocol = 6 # (Valore per il protocollo TCP;
PS C:\Users\ikmju>
PS C:\Users\ikmju> $flrewall -
LocalPolicy.CurrentProfile.GloballyOpenPorts.Add($port ;

Per chiudere una porta aperta, infine, è sufficiente indicarne il


numero e il protocollo al metodo Remove(), come dimostrato da questo
esempio:

PS C:\Users\ikmjii> $fìrewall .LocalPolicy. CurrentProfile. Global lyOpenPorts


.Remove (8080, 6)
Leggere e scrivere file

Un’analisi approfondita sul supporto offerto dalla shell per


operare confile binari e file di testo, con un’analisi
delle diverse codifiche gestite dal framework Microsoft.NET,
dei cmdlet preposti alla lettura e alla scrittura dei file e
degli operatori di reindirizzamento dell’output.

Nei capitoli precedenti si sono apprese le tecniche che consentono


di esplorare e manipolare la struttura del file system attraverso il
sistema dei provider e di gestire i volumi tramite WMI. Per elaborare
il contenuto dei file, tuttavia, sono necessarie competenze differenti.
Questo capitolo analizza i comandi messi a disposizione da
PowerShell per leggere e scrivere file e per reindirizzare i canali di
output.

File di testo e file binari


Così come avviene in molte altre piattaforme di sviluppo, la shell fa
distinzione tra due categorie principali di file: quelli di testo e quelli
binari. Nel primo caso ciascun file contiene del testo, memorizzato
sotto forma di sequenze di byte che hanno una diretta
corrispondenza con simboli stampabili, tra cui i caratteri alfanumerici,
lo spazio e il ritorno di carrello. Nel caso dei file binari, invece,
l’informazione è completamente libera e le sequenze di byte
contenute nei file non sono vincolate ad alcuna rappresentazione
simbolica.
A livello di sistema operativo e di macchina non c’è alcuna differenza
tra un file di testo e un file binario, perché entrambi sono considerati
mere sequenze di byte. Tuttavia, poiché i file del primo tipo possono
contenere informazioni interpretabili facilmente da un essere umano
senza dover dipendere da un tool o da un editor esterno per la
lettura, la shell include diversi strumenti ottimizzati per i file di testo.

Le codifiche
Le sequenze di byte impiegate per memorizzare un testo all’interno
di un file variano in base alla codifica utilizzata, chiamata in gergo
encoding. La prima codifica storica è l’ASCII (American Standard
Code for Information Interchange), nata agli inizi degli anni ’60 per
consentire lo scambio di informazioni tra i primi calcolatori
dell’epoca; questo standard prevede che a ogni simbolo sia
associato un intero a 7 bit, per un totale di 128 possibili elementi.
Gli anni ’80 vedono la nascita di alcune estensioni del set ASCII
chiamate codepage, che prevedono l’impiego di un bit aggiuntivo
per rappresentare i simboli non contemplati dal modello originale,
basato sulla lingua inglese. Ogni codepage diventa in seguito uno
standard e la maggior parte viene raggruppata nel documento
ISO/IEC 8859. Le codifiche ASCII a 8 bit sono a tutt’oggi largamente
utilizzate per lo scambio di testi (come e-mail e pagine web)
attraverso Internet: la lingua italiana e quella inglese, per esempio,
sono entrambe contemplate dallo standard ISO/IEC 8859-1, che è
anche la codifica predefinita quando si recuperano contenuti testuali
da un server web. Nonostante l’abbondanza di codepage esistenti
all’epoca, a cavallo tra gli anni ’80 e ’90 diventa chiaro che il numero
di simboli rappresentabili dalle codifiche menzionate in precedenza
non è più sufficiente a soddisfare le necessità di globalizzazione e
localizzazione delle informazioni. Nasce così Unicode, un consorzio
senza fini di lucro il cui obiettivo è creare un database di codici
associati a tutti i simboli conosciuti, in modo indipendente dalla
lingua e dalla piattaforma utilizzata. La codifica principale prodotta
dal consorzio è chiamata UTF-16 (o, genericamente, Unicode) e
consente di memorizzare qualsiasi simbolo attualmente conosciuto
in una sequenza di due byte.
La codifica UTF-8 è una variante della precedente e consente di
generare sequenze di byte dalla dimensione variabile (da uno a sei
elementi) in base al simbolo: la maggior parte dei simboli presenti
anche in ISO 8859-1 (e, di conseguenza, in ASCII) è memorizzata,
tuttavia, in un unico byte.
All’interno del framework Microsoft .NET è possibile impiegare tutte
le codifiche supportate dal sistema operativo. Utilizzando la classe
System.Text.Encoding e i suoi membri statici è possibile recuperare
facilmente informazioni su di una particolare codifica e adoperarla
per generare sequenze di byte a partire da un testo e viceversa.
Il metodo statico GetEncodings(), per esempio, permette di recuperare la
lista delle codifiche supportate dal sistema, come illustrato dallo
script che segue:

PS C:\Users\ikmju> [Text.Encoding]::GetEncodings() | Select Name, DisplayName Name


DisplayName

Name DisplayName
---- -----------
IBM037 IBM EBCDIC (Stati Uniti-Canada)
IBM437 OEM Stati Uniti
[...]
IBM01144 IBM EBCDIC (Italia-Europa)
IBM01145 IBM EBCDIC (Spagna-Europa)
IBM01147 IBM EBCDIC (Francia-Europa)
[...]
utf-16 Unicode
windows-1250 Europa centrale (Windows)
windows-1251 Cirillico (Windows)
Windows-1252 Europa occidentale (Windows)
[...]
x-mac-japanese Giapponese (Macintosh)
x-mac-chinesetrad Cinese tradizionale (Macintosh)
x-mac-korean Coreano (Macintosh)
[...]
iso-8859-1 Europa occidentale (ISO)
iso-8859-2 Europa centrale (ISO)
iso-8859-3 Latin 3 (ISO)
iso-8859-4 Baltico (ISO)
iso-8859-5 Cirillico (ISO)
[...]
utf-7 Unicode (UTF-7)
utf-8 Unicode (UTF-8)
Fornendo al metodo statico GetEncoding() il nome della codifica
desiderata, inoltre, si ottiene un oggetto che la rappresenta, in grado
di elaborare le sequenze di byte associate ai simboli testuali.
Nello script che segue, per esempio, si recuperano tre codifiche
differenti (ISO-8859-1, UTF-8 e Unicode) e si utilizza il metodo
d’istanza GetBytes() per ottenere la sequenza di byte generata a
partire dal cognome di un importante fisico italiano del ’900. Lo script
si appoggia per comodità a una funzione Get-EncodedBytes(), definita
così:

function Get-EncodedBytes([string]$encoding, [string]$value)


{
$bytes = [Text.Encoding]::GetEncoding($encoding).GetBytes($value)
($bytes | % { '{0:x2}' -f $_ }) -join ''
}

Il resto del codice di esempio è:

PS C:\Users\ikmju> Get-EncodedBytes 'ISO-8859-1' 'Segrè'


53656772eS
PS C:\Users\ikmju> Get-EncodedBytes 'UTF-8' 'Segrè'
53656772c3a8
PS C:\Users\ikmju> Get-EncodedBytes 'Unicode' 'Segrè
5300650067007200e80C

In particolare, si noti come il simbolo è - non compreso nel set ASCII


originale - sia codificato da ISO 8859-1 con un valore che impiega
anche l’ottavo bit, mentre UTF-8 utilizzi due byte per rappresentare
solo quel simbolo. Si può apprezzare, infine, come in questo caso la
codifica Unicode produca una sequenza simile alla prima.

I cmdlet per l’elaborazione dei file


La shell mette a disposizione dell’utente alcuni comandi in grado di
agevolare le operazioni di lettura e scrittura sui file, sia di testo sia
binari. Il seguito del paragrafo illustra i principali cmdlet di questa
categoria e ne analizza le opzioni.

Get-Content
Il cmdlet Get-Content permette di recuperare facilmente il contenuto di
un file, specificandone il nome per mezzo del parametro -Path,
posizionale. Questo comando considera sempre i file forniti come file
di testo e, nella maggior parte dei casi, è in grado di determinare
automaticamente la codifica utilizzata. Per forzare l’impiego di una
particolare codifica oppure per indicare a Get-Content di considerare il
documento un file binario ci si può avvalere del parametro -Encoding,
specificando uno dei valori ammessi dal comando: Unknown, String,
Unicode, Byte, BigEndianUnicode, UTF8, UTF7, Ascii. La codifica Byte in realtà
non esiste ma è un valore utilizzabile per forzare il cmdlet a
elaborare file binari. Come si può notare, tra l’altro, le codifiche
supportate direttamente dal comando sono molto limitate rispetto a
quelle offerte direttamente dal framework Microsoft .NET. La sintassi
di base del comando è rappresentabile da questo schema:
Get-Content <Path> [-Encoding <Codifica>]

Quando non si specifica alcuna codifica tramite il parametro -Encoding


o quando questa è diversa da Byte, il comando ritorna una sequenza
di stringhe a partire dalle righe del file originale: Get-Content è in grado
di riconoscere le sequenze di caratteri CR, LF, CR+LF, che indicano una
nuova linea in tutti i testi prodotti dai principali sistemi operativi che
producono testo basato sulla codifica ASCII (quasi tutti). Utilizzando
il parametro -TotalCount, inoltre, è possibile specificare il numero
massimo di righe da emettere nella pipeline sotto forma di stringhe. Il
parametro -ReadCount, infine, permette di specificare il numero di righe
di contenuto da emettere di volta in volta: impostando questo valore
a 0 si ottiene un’unica stringa.
Uno schema sintattico più completo del comando, dunque, può
essere rappresentato così:

Get-Content <Path> [-Encoding <Codifica>] [-TotalCount <MaxRighe>]


[-ReadCount <RigheAllaVolta>]

Nello script che segue, per esempio, si usa il comando per


recuperare l’array di righe di un file di testo che contiene il canto
primo dell’Inferno della Divina Commedia. In seguito, poiché Get-
Content restituisce di default un array di stringhe, all’output del cmdlet
è applicato l’operatore -match per estrarre le sole righe che finiscono
in elle.
PS C:\Users\ikmju> (Get-Content .\Test.txt) -match 'elle\S?$'
e 'l sol montava 'n sù con quelle stelle
mosse di prima quelle cose belle;
di quella fiera a la gaetta pelle

In quest’altro esempio, invece, ci si avvale del parametro -TotalCount


per limitare l’output ritornato alle prime tre righe del file:

PS C:\Users\ikmju> Get-Content .\Test.txt -TotalCount 3


Nel mezzo del cammin di nostra vita
mi ritrovai per una selva oscura
ché la diritta via era smarrita.

Quest’ultimo script, infine, dimostra come il comando ritorni un array


di byte quando è specificata la codifica Byte:

PS C:\Users\ikmju> Get-Content C:\Windows\winhelp.exe -Encoding Byte -


TotalCount 5
77
90
121
0
27

Il cmdlet Get-Content dispone di tre alias predefiniti: type, cat e gc.

Set-Content
A questo cmdlet è affidata la scrittura di file di testo e file binari, in
modo complementare a quanto effettuato dal comando precedente.
Il parametro posizionale -Path consente di indicare il nome del file su
cui si desidera operare, mentre con il parametro -Value, secondo
posizionale e utilizzabile via pipeline, si specifica il contenuto che
questo deve assumere. Il parametro -Encoding, poi, permette di variare
la codifica utilizzata per il salvataggio del file, in base agli stessi
valori supportati da Get-Content; lo switch -Force, infine, consente di
sovrascrivere file in sola lettura.
La sintassi di base del comando è questa:

... | Set-Content <Path> [-Encoding <Codifica>] [-Force]

Nello script che segue, per esempio, si usa Set-Content per creare (o
sostituire) un file di testo composto da una riga per ogni condivisione
di rete attiva nella macchina locale, facendo uso di WMI:
PS C:\Users\ikmju> Get-WmiObject Win32_Share | Select-Object -Expand Name | Set-
Content Shares.txt

PS C:\Users\ikmju> Get-Content .\Shares.txt


ADMIN$
C$
HP LaserJet 1020
[...]

In quest’altro esempio, infine, si utilizza il cmdlet per creare (o


sostituire) il contenuto di un file binario, in base a una sequenza di
valori arbitrari:

PS C:\Users\ikmju> [byte]1, [byte]2, [byte]3 | Set-Content Test.bin -Encoding Byte

PS C:\Users\ikmju> Get-Content .\Test.bin -Encoding Byte


1
2
3

Si noti che, in quest’ultimo caso, i valori di input forniti a Set-Content


devono obbligatoriamente essere dei byte (o sequenze di byte),
altrimenti il comando genera un errore.

Add-Content
Il cmdlet Add-Content è dotato di obiettivi e sintassi molto simili a Set-
Content e si differenzia da quest’ultimo perché, nel caso in cui venga

utilizzato a fronte di un file esistente, non ne sovrascrive il contenuto


ma lo integra aggiungendovi in coda il valore indicato. Alla stregua
del comando precedente, dunque, Add-Content supporta i parametri -
Path e -Value e lo switch -Force. Il parametro -Encode, infine, permette di
specificare la codifica desiderata: se il file desiderato è esistente,
tuttavia, l’impiego di codifiche differenti da quella originale potrebbe
causare la corruzione di tutto il contenuto e rendere il file illeggibile.
La sintassi di base di questo cmdlet è rappresentata dal seguente
schema, peraltro identico a quello di Set-Content:

... | Add-Content <Path> [-Encoding <Codifica>] [-Force]

Nell’esempio che segue, quindi, si utilizza il comando per


aggiungere a un file di testo due rappresentazioni testuali della data
corrente:
PS C:\Users\ikmju> Get-Date | Add-Content Date.txt
PS C:\Users\ikmju> Get-Date | Add-Content Date.txt

PS C:\Users\ikmju> Get-Content .Get-Content .\Date.txt


08/04/2010 16.45.09
08/04/2010 16.45.22

Clear-Content
Questo cmdlet è in grado di svuotare il contenuto di un file,
portandone la dimensione a zero byte. La sintassi del comando è
molto semplice e comprende la possibilità di specificare il parametro
posizionale -path, relativo al file su cui si desidera operare:

Clear-Content <Path>

Nello script che segue, per esempio, si svuota un particolare file di


log il cui contenuto attuale non è più necessario:

PS C:\Users\ikmju> Clear-Content .\dummy.log

Il cmdlet Clear-Content non elimina i file ma li svuota del solo contenuto. Per
NO
eliminare i file è necessario utilizzare il cmdlet Remove-Item, illustrato nel Capitolo
TA
17.

Out-File
Il cmdlet Out-File è uno dei comandi di output della pipeline, un
concetto analizzato nel Capitolo 9. Quando è presente questo
comando, per tutti gli oggetti emessi in precedenza nella pipeline
viene prodotta una rappresentazione testuale conforme al sistema di
visualizzazione di PowerShell e questa viene memorizzata all’interno
di un file. Per specificare il file di destinazione si può usare il
parametro -FilePath, posizionale, mentre attraverso il parametro -
Encoding si può indicare la codifica che Out-File deve utilizzare per
memorizzare il testo. A differenza dei cmdlet esposti in precedenza
in questa sezione, tuttavia, questo supporta solo file testuali e i valori
che -Encoding può assumere sono: Default, Unicode, BigEndianUnicode, UTF8,
UTF7, Ascii, UTF32, OEM.

La sintassi di base del comando è riassumibile in questo schema:


... | Out-File <Path> [-Encoding <Codifica>]

Nello script che segue, per esempio, si sfrutta Out-File per raccogliere
l’output testuale di un comando all’interno di un file di testo:

PS C:\Users\ikmju> Get-ChildItem C:\Windows | Out-File files.txt


PS C:\Users\ikmju> Get-Content .\files.txt -TotalCount 10

Directory: Microsoft - Powershell-Core\FileSystem::C:\Windows

Mode LastWriteTime Length Name


---- ------------- ------ ----
d---- 7/14/2009 6:52 AM <DIR> addins
d---- 7/14/2009 4:37 AM <DIR> AppCompat
d---- 10/21/2009 7:18 AM <DIR> AppPatch

Poiché questo cmdlet produce un output identico a quello


visualizzato dall’host, il numero di colonne di testo riprodotte è, di
default, pari a quello dell’host impiegato: nel caso della console
testuale di PowerShell, per esempio, questo valore è pari a so.
Utilizzando il parametro -Width, tuttavia, è possibile variare questo
valore e portarlo a un numero sufficientemente elevato, in modo da
consentire una rappresentazione ottimale delle informazioni.
Il parametro -Append, inoltre, indica a out-File di aggiungere il testo a un
file esistente, evenienza che altrimenti porta il comando a sostituire il
contenuto del file.

Reindirizzare l’output
PowerShell gestisce il flusso di dati tra i comandi e l’host utilizzando i
canali standard (standard streams): una tecnologia nata in Unix per
rispondere all’esigenza di astrazione delle periferiche di input e di
output, oggi presente nella maggior parte dei sistemi operativi.
Nella terminologia dei canali standard, lo standard input (stdin) è il
canale da cui giunge il flusso di dati di ingresso, come il testo digitato
dall’utente nell’host. Lo standard output (stdout) è, invece, il canale
destinato a ospitare il risultato generato dal comando; lo standard
error (stderr), infine, è un canale di output destinato ad accogliere le
informazioni di diagnostica e gli errori generati dal comando.
In PowerShell il concetto di canale standard è stato parzialmente
rivisto per favorire il passaggio degli oggetti all’interno della pipeline:
in questo caso, infatti, non si tratta di semplice testo che fluisce dal
canale stdout di un comando allo stdin del successivo, ma di un
complesso flusso di oggetti strutturati che transitano all’interno della
pipeline. Al termine dell’elaborazione della pipeline, comunque,
l’eventuale rappresentazione testuale prodotta viene inoltrata al
canale stdout, mentre la rappresentazione testuale degli errori è
inoltrata al canale stderr.
Così come avviene nella maggior parte delle altre shell, anche in
PowerShell è possibile utilizzare alcune istruzioni in grado di
catturare il contenuto prodotto in un canale standard e memorizzarlo
altrove, evitando l’elaborazione predefinita (come la visualizzazione
a video). I cmdlet di output, esaminati nel Capitolo 9, permettono di
gestire accuratamente la produzione di risultati all’interno del canale
stdout; gli operatori di reindirizzamento, tuttavia, permettono di
governare in modo più organico la cattura dei canali standard e
offrono un’alternativa universalmente conosciuta rispetto all’impiego
dei cmdlet.

Operatore >
L’operatore > consente di ridirigere il canale stdout all’interno di un
file, in modo pressoché identico a quanto avviene con il cmdlet Out-
File. A differenza di quest’ultimo, tuttavia, la codifica è Unicode e non
può essere modificata. La sintassi per l’impiego di questo operatore
è schematizzabile così:

... > <Path>

In questo script, per esempio, la rappresentazione testuale


dell’output prodotto dalla pipeline è memorizzata all’interno di un file
di testo:

PPS C:\Users\ikmju> Get-ChildItem *.txt > Dir.txt

PS C:\Users\ikmju> Get-Content .\Dir.txt

Directory: C:\Users\ikmju

Mode LastWriteTime Length Name


---- ------------- ------ ----
-a--- 08/04/2010 16.45 84 Date.txt
-a--- 08/04/2010 20.18 806 Dir.txt
-a--- 19/03/2010 15.01 12 HelloWorld.txt
-a--- 30/03/2010 13.53 105923 test.txt

Si noti che l’esempio precedente è equivalente a questo, perché


l’operatore > supporta un subset delle funzionalità offerte da Out-File:

PS C:\Users\ikmju> Get-Childltem * -txt | Out-File Dir.txt

Operatore >>
L’operatore » è una variante di quello precedente. Esso permette di
aggiungere il contenuto catturato dal canale stdout alla coda di un
file esistente, alla stregua di quanto è possibile ottenere specificando
lo switch -Append con il cmdlet Out-File.
La sintassi è molto simile a quella precedente:

... >> <Path>

Nello script che segue, per esempio, la rappresentazione testuale


dell’output prodotto dalla pipeline è aggiunta in coda a un file di
testo:

PS C:\Users\ikmju> Get-Date >> Date.txt PS C:\Users\ikmju> Get-Date >> Date.txt

PS C:\Users\ikmju> Get-Content .\Date.txt

giovedì 8 aprile 2010 20.27.06

giovedì 8 aprile 2010 20.27.07

Anche stavolta si noti come lo script precedente sia equivalente a


questo:

PS C:\Users\ikmju> Get-Date | Out-File .\Date.txt -Append


PS C:\Users\ikmju> Get-Date | Out-File .\Date.txt -Append

Operatore 2>
L’operatore 2> permette di reindirizzare il canale stderr all’interno di
un file di testo, così come avviene per l’operatore >. La cifra 2, posta
a capo dell’operatore, deriva dall’implementazione C utilizzata per
dare vita alla tecnologia dei canali standard all’interno delle prime
versioni di Unix, dove i canali erano identificati dal loro indice
all’interno dell’array dei canali disponibili. La sintassi per l’impiego è
molto simile a quella dell’operatore >:

...2> <Path>

Reindirizzare il canale stderr può essere utile in tutti quei casi in cui
si desidera conservare la rappresentazione testuale degli errori
generati all’interno di un file, per elaborazione successive. Nello
script che segue, per esempio, si ridirigono gli errori prodotti da un
comando all’interno di un file di testo:

PS C:\Users\ikmju> Remove-Item File.Inesistente 2> Error.txt

PS C:\Users\ikmju> Get-Content .\Error.txt


Remove-Item : Impossibile trovare il percorso 'C:\Users\ikmju\File.
Inesistente' perché non esiste.
[...]

NO La modalità predefinita di visualizzazione degli errori prodotti all’interno del canale


TA stderr prevede un reindirizzamento automatico verso il canale stdout.

Operatore 2>>
Questo operatore di reindirizzamento è una variante del precedente.
Esso permette di aggiungere il contenuto catturato dal canale stderr
in coda a un file di testo esistente, così come avviene per l’operatore
». La sintassi di 2» è quindi molto semplice:

... 2>> <Path>

In questo script, per esempio, si aggiungono le rappresentazioni


testuali degli errori generati da due comandi all’interno di un file di
testo:

PS C:\Users\ikmju> Set-Location Z:\Inesistente 2>> Error.txt


PS C:\Users\ikmju> Set-Location W:\Inesistente 2>> Error.txt

PS C:\Users\ikmju> Get-Content .\Error.txt


Set-Location : Impossibile trovare il percorso 'Z:\Inesistente' perché non esiste.
[...]
Set-Location : Impossibile trovare l'unità. Un'unità con nome 'W' non esiste.

Operatore 2>&1
L’operatore 2>&1 indica alla shell di ridirigere automaticamente l’output
prodotto dal canale stderr all’interno del canale stdout. Da un punto
di vista pratico questo tipo di operatore è utilizzato quando si
desidera controllare tutto l’output prodotto da un comando - sia
risultati sia errori - utilizzando un unico canale. La sintassi di questo
operatore non prevede l’indicazione di alcun parametro accessorio
ed è rappresentata dal seguente schema:
... 2>&1

Una volta ridiretto stderr su stdout è possibile catturare l’output di


quest’ultimo in cascata e memorizzare il risultato ottenuto. Nello
script che segue, per esempio, si catturano entrambi i canali
all’interno di un file di testo:

PS C:\Users\ikmju> Get-Date > All.txt


PS C:\Users\ikmju> Set-Location W:\Inesistente 2>&1 >> All.txt

PS C:\Users\ikmju> Get-Content .\All.txt

giovedì 8 aprile 2010 20.51.23


Set-Location : Impossibile trovare l'unità. Un'unità con nome 'W' non esiste.
[...]
Gestire processi e servizi

Una discussione approfondita sui cmdlet destinati alla


gestione dei processi e dei servizi, con una
panoramica sulle principali classi e strutture utilizzate
dal framework Microsoft.NET per rappresentarne i
dettagli.

Il framework Microsoft .NET permette di gestire con facilità i processi


e i servizi Windows, mettendo a disposizione dell’utente alcune
classi specializzate; in aggiunta all’impiego diretto di queste ultime,
PowerShell espone alcuni cmdlet in grado di facilitare questo tipo di
attività. Il seguito del capitolo è dedicato a questi comandi e alle
principali tecniche di gestione dei processi e dei servizi Windows, sia
locali sia remoti.

I processi
La gestione dei processi all’interno della shell è affidata ad alcuni
cmdlet, facilmente individuabili per via del sostantivo Process, incluso
nel loro nome.

Start-Process
Il cmdlet Start-Process permette di avviare un nuovo processo
ottenendo il relativo oggetto della classe System.Diagnostics.Process,
tramite il quale è successivamente possibile recuperare informazioni
e intervenire sul processo creato. Il parametro posizionale -FilePath è
utilizzabile in due modi: specificando direttamente il nome del file
eseguibile oppure indicando il nome del documento che si desidera
lanciare. In quest’ultimo caso la shell verifica nel registry quale sia
l’eseguibile principale associato al file, in base all’estensione di
quest’ultimo. Nel caso sia specificato lo switch -NoShellExecute, tuttavia,
il lancio di file che non siano eseguibili è inibito e genera un errore
appropriato.
Con il parametro posizionale -ArgumentList si forniscono eventuali
parametri all’eseguibile da lanciare, mentre con il parametro -verb si
può indicare l’eventuale verbo da utilizzare per lanciare il processo:
la lista dei verbi supportati da un particolare file è determinata in
base all’estensione di quest’ultimo e viene recuperata dal registry. La
sintassi di base di questo cmdlet è:

Start-Process <File> <Argomenti> [-Verb <Verbo>]

Per eseguire il Blocco note di Windows, per esempio, si potrebbe


utilizzare uno script simile a questo:

PS C:\Users\ikmju> Start-Process notepad

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ----- ---- ---- ----- ----- -- -----------
14 2 736 1932 41 0.00 4976 notepad

Il lancio di un documento non richiede modifiche al codice


precedente. A patto che il file esista, la shell utilizza l’eseguibile
associato al file oppure visualizza la finestra standard Windows di
scelta dell’applicazione da utilizzare per proseguire con l’apertura del
documento:

PS C:\Users\ikmju> Start-Process .\Test.xlsx

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ----- ---- ---- ----- ----- -- -----------
266 15 13612 25212 217 0.47 4428 EXCEL
All’interno del framework Microsoft .NET, per recuperare i verbi
associati a un file ci si può avvalere della classe
System.Diagnostics.ProcessStartInfo che, una volta istanziata con il nome

del file in questione, espone la collezione di questi elementi per


mezzo della proprietà Verbs.
In questo script, per esempio, si recuperano i verbi associati a un file
di Microsoft Word e se ne utilizza uno per lanciare la stampa del
documento:

PS C:\Users\ikmju> New-Object System-Diagnostics -ProcessStartlnfo -


ArgumentList .\Test.docx | Select -Expand VerbsEdit
OnenotePrintto
Open
OpenAsReadOnly
Print
Printto

PS C:\Users\ikmju> Start-Process .\Test.docx -Verb Print

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ----- ---- ---- ----- ----- -- -----------
794 18 20956 37664 237 0.80 4432 WINWORD

Gli oggetti ritornati da start-process contengono numerose informazioni


sul processo, recuperabili per mezzo delle proprietà e dei metodi
esposti dalla classe System.Diagnostics.Process. Per un’analisi delle
possibilità offerte da questa classe si rimanda alla sezione dedicata
al cmdlet Get-Process. Il cmdlet Start-Process è spesso utilizzato per
mezzo dell’alias predefinito start.

Get-Process
Grazie al cmdlet Get-Process è possibile recuperare facilmente un
oggetto system. Diagnostics.Process a partire da un processo esistente.
Mediante il parametro -ComputerName è possibile indicare al comando un
computer da cui ottenere le informazioni ricercate; quando non viene
specificato alcun valore, Get-Process utilizza la macchina locale.
Fornendo una o più stringhe al parametro posizionale -Name si impone
al cmdlet di filtrare i processi recuperati in base a questi valori, che
possono includere caratteri wildcard. In alternativa, specificando uno
o più valori interi tramite il parametro -Id, il filtro dei processi avviene
in base al loro identificativo (noto anche come PID, o Process
IDentifier). Se non è specificato nessuno di questi due parametri, il
comando ritorna tutti i processi.
La sintassi di base di questo cmdlet è rappresentabile così:

Get-Process [<Nome>] [-ComputerName <Computer>]


Get-Process [-Id <PID>] [-ComputerName <Computer>]

In questo script, per esempio, si recuperano tutti i processi locali


associati al Blocco note di Windows:

PS C:\Users\ikmju> Get-Process notepad

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ----- ---- ---- ----- ----- -- -----------
64 4 1216 5592 60 0.20 2964 notepad
64 4 1220 5624 60 0.22 4976 notepad
64 4 1220 5380 60 0.03 5560 notepad

In quest’altro script, invece, si recuperano le istanze di due processi


in esecuzione in una macchina remota, in base al loro identificativo:

PS C:\Users\ikmju> Get-Process -Id 3312, 4976 -ComputerName 10.0.1.2

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ----- ---- ---- ----- ----- -- -----------
77 9 6564 12148 79 0.20 3312 calc
64 4 1220 5624 60 0.22 4976 notepad

Come anticipato, gli oggetti della classe system.Diagnostics.process


contengono diverse proprietà in grado di fornire informazioni
dettagliate sullo stato e sulla configurazione del processo cui si
riferiscono; alcuni metodi, inoltre, permettono di cambiare lo stato del
processo e di richiedere ulteriori dettagli.
In questo script, per esempio, si visualizza a video la percentuale di
CPU, la memoria totale impegnata e il titolo della finestra principale
di un processo recuperato grazie a Get-Process:

PS C:\Users\ikmju> Get-Process cale I Select CPU, WorkingSet,


MainWindowTitle

CPU WorkingSet MainWindowTitle


--- ---------- ---------------
0.2028013 12439552 Calculator

In quest’altro script, invece, si utilizza la proprietà Responding


dell’oggetto per recuperare solo i processi la cui interfaccia utente
sia bloccata:

PS C:\Users\ikmju> Get-Process | ? { $_.Responding -eq $false }

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ----- ---- ---- ----- ----- -- -----------
1406 22 40824 51472 208 3.29 4548 DummyExec

Tabella 21.1 – Alcune utili proprietà di


System.Diagnostics.Process.
Proprietà Descrizione
ExitCode Codice di uscita del processo
ExitTime Data e ora di termine del processo
Id Identificativo del processo (PID)
MainModule Il modulo principale del processo
Modules Collezione dei moduli del processo
ProcessName Nome del processo
ProcessorAffinit La maschera di affinità con il processore
y
Indica se l’interfaccia utente del processo risponde o è
Responding
bloccata
StandardError Un riferimento al canale stderr del processo
Standardlnput Un riferimento al canale stdin del processo
StandardOutput Un riferimento al canale stdout del processo
Contiene informazioni sul lancio e la configurazione
StartInfo
del processo
Threads Collezione dei thread del processo
WorkingSet Dimensione totale della memoria impegnata

Specificando lo switch -Module, Get-Process ritorna inoltre tutti i moduli


(eseguibili e librerie) associati ai processi recuperati,
rappresentandoli per mezzo della classe System.Diagnostics.ProcessModule.
Anche in questo caso le informazioni ritornate sono molte e
dettagliate; nello script che segue, per esempio, si recuperano i
moduli associati a un processo e si visualizza a video il nome
completo del file:

PS C:\Users\ikmju> Get-Process winword -Module | Select FileName


FileName
--------
C:\Program Files\Microsoft Office\Office12\WINWORD.EXE
C:\Windows\SYSTEM32\ntdll.dll
C:\Windows\system32\kernel32.dll
C:\Windows\system32\KERNELBASE.dll
[...]

Tabella 21.2 – Alcune utili proprietà di


System.Diagnostics.ProcessModule.
Proprietà Descrizione
FileName Il nome del file del modulo
FileVersionInfo Dettagli sulla versione e sul produttore del modulo
Modu1eMemorySiz La memoria occupata dal modulo
e
Il nome del modulo: tipicamente corrisponde al
nome del file (senza path), ma in alcuni casi –
ModuleName
come per esempio con i moduli gestiti da Windows
Side by Side (winsxs) – potrebbe differire

Applicando lo switch -FileVersionInfo a Get-Process, infine, si recupera per


ciascun processo l’oggetto di tipo System.Diagnostics.FileVersionInfo
relativo al produttore e alla versione del modulo principale del
processo richiesto.
In questo script, per esempio, si recuperano tutti i processi in
esecuzione il cui modulo principale sia stato prodotto da Microsoft e
si visualizza a video il nome del file:

PS C:\Users\ikmju> Get-Process -FileVersionInfo | ? { $_.CompanyName -like


'Microsoft*' } | Select FileName

FileName
--------
C:\Windows\system32\conhost.exe
C:\Windows\system32\csrss.exe
C:\Windows\system32\csrss.exe
C:\Windows\system32\Dwm.exe
[...]

Tabella 21.3 – Alcune utili proprietà di


System.Diagnostics.FileVersionInfo.
Proprietà Descrizione
CompanyName Il nome del produttore
FileVersion La versione del file
FileName Il nome del file
Language La lingua delle risorse contenute nel file
ProductName Nome del prodotto associato al file
ProductVersion Versione del prodotto associato al file

Il cmdlet Get-Process dispone di due alias predefiniti, utilizzati


frequentemente: ps e gps.

Stop-Process
Questo comando è in grado di terminare uno o più processi locali,
individuati per mezzo del loro identificativo tramite il parametro
posizionale -Id oppure fornendone il nome al parametro -Name.
L’impiego più frequente di questo cmdlet, tuttavia, consiste nel
fornire il processo di interesse direttamente tramite pipeline, sotto
forma di oggetto System.Diagnostics.Process.
La sintassi più semplice per eseguire Stop-Process, dunque, è questa:

<Processo1>, <Processo2>, ... | Stop-Process

Nello script che segue, per esempio, si terminano tutti i processi


relativi alla Calcolatrice di Windows, fornendo tramite pipeline a Stop-
Process i processi ritornati da

Get-Process:

PS C:\Users\ikmju> Get-Process calc | Stop-Process

L’alias predefinito utilizzato più di frequente per questo comando è


kill.

Wait-Process
Il cmdlet Wait-Process attende il termine di uno più processi locali,
individuati per mezzo del loro identificativo tramite il parametro
posizionale -Id oppure fornendone il nome al parametro -Name:
durante l’attesa del comando la shell non elabora nuove istruzioni e,
per interrompere l’esecuzione, è necessario utilizzare la
combinazione di tasti Ctrl+C. Come nel caso precedente, l’impiego
più frequente di questo cmdlet consiste nel fornire il processo di
interesse direttamente tramite pipeline, sotto forma di oggetto
System.Diagnostics.Process.
Mediante il parametro -Timeout, infine, si indica al comando un tempo
massimo di attesa (espresso in secondi) che, se superato senza
giungere al termine del processo desiderato, porta alla generazione
di un errore. La sintassi di base per l’impiego di questo cmdlet è la
seguente:

<Processol>, <Processo2>, ... I Wait-Process [-Timeout <TempoMassimo>]

Nello script che segue, per esempio, si attende per un tempo


indefinito la chiusura di tutte le istanze del Blocco note di Windows:

PS C:\Users\ikmju> Get-Process notepad | Wait-Process

L’esecuzione della riga successiva riprende solo dopo che tutti i processi richiesti
NO
sono stati terminati. Per interrompere forzatamente l’attesa è possibile utilizzare la
TA
combinazione di tasti Ctrl+C.

I servizi
La gestione dei servizi Windows è una tra le attività più frequenti per
i sistemisti e i professionisti IT in genere: non solo questi elementi
sono alla base di qualsiasi funzionalità esposta dalle macchine con
un ruolo di server, ma il mancato funzionamento di un solo servizio
può compromettere lo stato dell’intero sistema. A livello tecnico, un
servizio non è nient’altro che un eseguibile progettato per rispondere
alle richieste di avvio, termine e sospensione generate dal Service
Control Manager (SCM) di Windows, il componente del sistema
operativo cui è affidata l’amministrazione dei servizi. Windows
utilizza il registry per memorizzare le informazioni su ogni servizio
installato nella macchina e, oltre al percorso del relativo eseguibile,
al nome con cui il servizio è individuabile e al nome da visualizzare
all’interno dell’interfaccia di gestione servizi di Windows, mantiene la
preferenza sul tipo di avvio, che può assumere uno di questi valori:
• Automatico: prevede che il servizio sia avviato
automaticamente al boot del sistema;
• Automatico (avvio ritardato): il servizio è avviato dopo il boot,
quando il sistema ha terminato di avviare gli altri servizi;
• Manuale: per avviare il servizio è necessario procedere
manualmente, utilizzando, per esempio, l’interfaccia di
amministrazione dei servizi di Windows;
• Disabilitato: il servizio non è avviabile.
Nel seguito di questo paragrafo sono analizzati i cmdlet principali
che PowerShell espone per gestire i servizi e il supporto offerto in tal
senso dal framework Microsoft .NET.

Get-Service
Il cmdlet Get-Service è in grado di recuperare informazioni dettagliate
su uno o più servizi locali o remoti, fornendone il nome al parametro
posizionale -Name; in alternativa, è possibile utilizzare il parametro -
DisplayName e specificare un filtro in base al nome visualizzato

all’interno dell’interfaccia di gestione servizi di Windows. In entrambi


i casi, si possono fornire ai parametri delle stringhe con caratteri
wildcard e, se non si specifica alcun valore, il comando ritorna tutti
gli elementi. Gli eventuali nomi delle macchine di destinazione si
possono indicare attraverso il parametro -computerName, che accetta una
o più stringhe. La sintassi di base di questo comando è la seguente:

Get-Service [<Nome>] [-ComputerName <Computer>]


Get-Service -DisplayName <NomeVisualizzato> [-ComputerName <Computer>]

Nello script che segue, per esempio, si recuperano tutti i servizi


locali il cui nome visualizzato inizi con il testo Windows:

PS C:\Users\ikmju> Get-Service -DisplayName Windows*

Status Name DisplayName


------ ---- -----------
Stopped idsvc Windows CardSpace
Running MpsSvc Windows Firewall
Stopped msiserver Windows Installer
Running wuauserv Windows Update
[...]

In quest’altro script, invece, si recupera il servizio w3svc (Servizio


Pubblicazione sul Web) di una macchina remota:
PS C:\Users\ikmju> Get-Service w3svc -ComputerName 192-168-178-25 | Select
Name, MachineName, Status

Name MachineName Status


---- ----------- ------
w3svc 192.168.178.25 Running

NO Le richieste effettuate a fronte di un Service Control Manager remoto avvengono


TA esclusivamente utilizzando il protocollo DCOM/RPC.

Gli oggetti ritornati da sono istanze della classe


Get-Service

System.ServiceProcess. ServiceController, i cui metodi e proprietà possono

essere impiegati per recuperare informazioni e controllare il servizio


a cui afferiscono.

Tabella 21.4 – Alcune utili proprietà di


System.ServiceProcess.ServiceController.
Proprietà Descrizione
DisplayName Il nome descrittivo del servizio, così come viene
visualizzato all’interno dell’interfaccia di gestione servizi
MachineName Il nome della macchina in cui il servizio è installato
ServiceName Il nome identificativo del servizio
Lo stato corrente del servizio, che può assumere uno
Status
dei valori presenti nella Tabella 21.5

Tabella 21.5 – I possibili valori che può assumere la proprietà


Status di System. ServiceProcess.ServiceController.
Valore Descrizione
Stopped Il servizio è arrestato
StartPending Il servizio sta per essere avviato
StopPending Il servizio sta per essere arrestato
Running Il servizio è avviato
ContinuePending Il servizio sta per continuare la propria esecuzione
PausePending Il servizio sta per essere messo in pausa
Paused Il servizio è in pausa
Tabella 21.6 – Alcuni utili metodi di
System.ServiceProcess.ServiceController.
Metodo Descrizione
Continue() Continua l’esecuzione del servizio (in seguito a
una pausa)
Pause() Mette in pausa il servizio
Refresh() Aggiorna le proprietà dell’oggetto in base allo
stato attuale del servizio
Start() Avvia il servizio
Stop() Arresta il servizio
Sospende l’esecuzione dello script fino a
WaitForStatus($status)
quando il servizio non è nello stato indicato

Il cmdlet Get-Service può essere utilizzato anche per recuperare i


dettagli relativi alle dipendenze tra i servizi; utilizzando lo switch -
DependentServices il comando restituisce tutti i servizi dipendenti dal
servizio specificato. In modo complementare, lo switch -RequiredServices
ritorna i servizi da cui il servizio specificato dipende. Desiderando
verificare, per esempio, quali siano i servizi necessari all’esecuzione
di w3svc, è possibile eseguire questo semplice blocco di istruzioni:

PS C:\Users\ikmju> Get-Service w3svc -RequiredServices

Status Name DisplayName


------ ---- -----------
Running WAS Servizio Attivazione processo Windows
Running HTTP HTTP

Analogamente, per recuperare i servizi dipendenti dal servizio HTTP,


per esempio, è possibile impiegare questo script:

PS C:\Users\ikmju> Get-Service HTTP -DependentServices


Status Name DisplayName
------ ---- -----------
Running WinRM Gestione remota Windows (WS-Management)
Stopped Wecsvc Raccolta eventi Windows
Running W3SVC Servizio Pubblicazione sul Web
Stopped upnphost Host di dispositivi UPnP
[...]

Il comando Get-service è talvolta utilizzato per mezzo dell’alias


predefinito gsv.
Start-Service
Questo cmdlet permette di avviare uno o più servizi, specificandone
il nome tramite il parametro posizionale -Name oppure il nome
descrittivo tramite il parametro -DisplayName; molto spesso, tuttavia, si
sfrutta la capacità del comando di accettare oggetti di tipo
System.ServiceProcess.ServiceController direttamente dalla pipeline. La
sintassi di base di start-service segue questo schema:

<Serviziol> [, <Servizio2>/ ...] I Start-Service

In questo script, per esempio, si avvia lo spooler di stampa (servizio


spooler) di una macchina remota:

PS C:\Users\ikmju> Get-Service spooler -ComputerName 192.168.178.25 I Start-Service

NO L’impiego di Start-Service richiede i privilegi di amministrazione in quei sistemi


TA che utilizzano lo User Account Control (UAC) di Windows.

Stop-Service
Il cmdlet Stop-Service ha un ruolo complementare al comando
precedente e arresta il servizio specificato tramite il parametro
posizionale -Name oppure, nel caso si fornisca il nome descrittivo,
tramite il parametro -DisplayName; anche in questo caso, d’altra parte, si
tende a sfruttare molto spesso la capacità del comando di accettare
oggetti di tipo System.ServiceProcess.ServiceController direttamente dalla
pipeline. La sintassi di base di Stop-Service è analoga a quella di Start-
Service e segue questo schema:

<Servizio1> [, <Servizio2>, ...] | Stop-Service

In questo caso, per esempio, si recuperano i servizi che dipendono


dallo spooler di stampa (il servizio Fax, per esempio) e si arrestano
per mezzo di Stop-Service:

PS C:\Users\ikmju> Get-Service spooler -DependentServices | Stop-Service

Suspend-Service e Resume-Service
Il cmdlet Suspend-Service è in grado di mettere in pausa un servizio,
mentre Resume-Service continua l’esecuzione di un servizio
precedentemente sospeso. Non tutti i servizi supportano il
meccanismo di sospensione – che, stando alle specifiche tecniche,
dovrebbe arrestare l’esecuzione mantenendo le eventuali risorse
caricate in memoria – ed è possibile far uso della proprietà logica
CanPauseAndContinue associata alle istanze di
System.ServiceProcess.ServiceController per determinarlo. Anche Suspend-

Service e Resume-Service accettano l’indicazione dei servizi su cui agire

per mezzo dei consueti parametri -Name e -DisplayName, ma spesso,


come di consuetudine per i cmdlet di gestione dei servizi, si tende a
fornire i valori di interesse direttamente tramite pipeline. La sintassi
di base di questi comandi è:

<Servizio1> [, <Servizio2>, ...] | Resume-Service


<Servizio1> [, <Servizio2>, ...] | Suspend-Service

Nello script che segue, per esempio, si mette in pausa il servizio


Strumentazione gestione Windows (Winmgmt) per poi continuarne
l’esecuzione:

PS C:\Windows\system32> Get-Service Winmgmt | Suspend-Service PS


C:\Windows\system32> Get-Service Winmgmt | Resume-Service

Restart-Service
Questo cmdlet si occupa di arrestare e successivamente avviare uno
o più servizi ed espone una sintassi identica a Start-Service, riepilogata
di seguito:

<Servizio1> [, <Servizio2>, ...] | Restart-Service

Nello script che segue, per esempio, si riavvia il servizio w3svc:

PS C:\Users\ikmju> Get-Service w3svc | Restart-Service

Set-Service
Il cmdlet Set-Service è in grado di modificare alcune informazioni su di
un servizio e, eventualmente, di cambiarne lo stato.
Il servizio su cui effettuare la modifica si specifica grazie al nome
identificativo e al nome della macchina – da fornire, rispettivamente,
tramite il parametro posizionale -Name e il parametro -ComputerName –
oppure per mezzo di un oggetto System.ServiceProcess.ServiceController,
direttamente tramite pipeline. Le informazioni da modificare sono
specificate grazie all’ausilio di alcuni parametri: -DisplayName permette
di assegnare un nuovo nome descrittivo al servizio, -StartupType
consente di modificarne il tipo di avvio e -Status cambia lo stato del
servizio (alla stregua di quanto è possibile fare con i cmdlet
analizzati in precedenza in questo paragrafo). La sintassi di base di
Set-Service è riepilogata da questo schema:

<Servizio> | Set-Service [-DisplayName <Nome descrittivo>] [-StartupType <Tipo


avvio>] [-Status <Stato>]

Nello script che segue, per esempio, si rende manuale la modalità di


avvio di Windows Update (servizio wuauserv):

PS C:\Users\ikmju> Get-Service wuauserv | Set-Service -StartupType Manual

Tabella 21.7 – I valori ammessi dal parametro -StartupType.


Metodo Descrizione
Automatic Avvio automatico
Manual Avvio manuale
Disabled Avvio disabilitato
XML, XPath e XSLT

tecnologie disponibili all’interno di PowerShell


Una trattazione dettagliata sulle
per recuperare informazioni da documenti in formato XML e
modificarne la struttura e i valori tramite i cmdlet, l’esteso supporto
offerto dal framework Microsoft.NET e la tecnologia XSLT.
La tecnologia XML, acronimo di eXtensible Markup Language,
permette di utilizzare un metalinguaggio di markup per definire la
sintassi di documenti di testo strutturati, interpretabili in modo
universale grazie alla presenza di uno standard definito dal World
Wide Web Consortium (W3C).
Il framework Microsoft .NET è dotato di un eccellente supporto per
l’elaborazione dei documenti realizzati con questa tecnologia ed
espone numerose classi che ne rendono semplice la lettura e la
modifica; al di là di questo supporto, all’interno di PowerShell è
presente un sistema nativo di recupero delle informazioni a partire
dagli elementi e dagli attributi delle strutture XML, che consente di
ridurre ulteriormente i tempi di sviluppo. In questo capitolo si
esaminano i principali strumenti messi a disposizione da PowerShell
e dal framework Microsoft .NET per interagire con i documenti XML
e con le tecnologie a questi correlate, tra cui XPath e XSLT.

Recuperare le informazioni
Come il lettore potrebbe già sapere, un file XML è uno speciale
documento di testo, strutturato grazie alla presenza di simboli e
marcatori propri del metalinguaggio stesso. La trattazione dello
standard XML è al di fuori degli obiettivi di questo libro, ma vale la
pena ricordare come le informazioni codificate tramite questa
tecnologia siano organizzate gerarchicamente: ogni ramo della
struttura prende il nome di elemento e ogni elemento può essere
dotato di uno o più attributi, in grado di immagazzinare del testo.
Ogni elemento, poi, può contenere altri elementi ed eventualmente
contenere del testo esso stesso; alla base della struttura di un
documento XML, infine, c’è un elemento progenitore, chiamato root.

NO Per una trattazione approfondita su XML e le tecnologie correlate si rimanda al


TA sito del World Wide Web Consortium (http://www.w3.org).

Per rendere più semplice la comprensione degli argomenti trattati in


questo paragrafo, gli script esposti nel seguito fanno capo quasi
esclusivamente a un unico documento di esempio, chiamato Space.xml,
che contiene una struttura XML con alcuni dati delle galassie più
vicine alla Via Lattea:

<?xml version="1 - 0" encoding="utf-8"?>


<galaxies>
<ga1axy type="Irr/SB(s)m">
<name>Large Magellanic Cloud</name>
<distance>0-163</distance>
</galaxy>
<galaxy type="dSph/E7">
<name>Sagittarius Dwarf Sphr</name>
<distance>0-0 81</distance>
</galaxy>
<galaxy type="SB(s)m pec">
<name>Small Magellanic Cloud</name>
<distance>0.206</distance>
</galaxy>
<galaxy type="d Sph">
<name>Boötes Dwarf</name>
<distance>0-197</distance>
</galaxy>
<ga1axy type="Irr">
<name>Canis Major Dwarf</name>
<distance>0-025</distance>
</galaxy>
</galaxies>

Il DOM
Il framework Microsoft .NET, così come la maggior parte delle
piattaforme di sviluppo, dedica alla lettura dei documenti XML un
intero modello di classi chiamato, genericamente, DOM (acronimo di
Document Object Model), per mezzo del quale le strutture XML sono
interamente caricate in memoria, riproducendo ogni nodo attraverso
un oggetto distintivo. Tutte le classi utilizzate dal framework per
rappresentare il modello DOM sono contenute nell’assembly
System.xml, all’interno del namespace omonimo.
L’approccio tipico di chi si appresta a utilizzare il supporto DOM
offerto dal framework Microsoft.NET con i linguaggi di sviluppo come
C# o Visual Basic .NET consiste nel creare una nuova istanza della
classe System.Xml.XmlDocument e nell’im-piegarne i metodi e le proprietà
per leggere il file desiderato e recuperare le informazioni ricercate.
PowerShell rende più semplice questo tipo di attività ed espone il
type accelerator [xml], che converte una stringa nell’equivalente
istanza di System.Xml.XmlDocument. Quando le caratteristiche del
documento lo consentono, inoltre, il supporto Extended Type System
della shell espone gli elementi del documento XML come proprietà
degli oggetti, così da rendere il codice più snello e leggibile. Per
caricare il documento di esempio Space.Xml tramite il type accelerator
[xml] e il cmdlet Get-Content, dunque, è possibile impiegare uno script
simile a questo:

PS C:\Users\ikmju> $space = [xml](Get-Content Space.xml)

È poi possibile sfruttare la parziale navigazione tramite proprietà


esposta dall’oggetto per recuperare i nomi delle galassie (elemento
name), come dimostrato da questo script:

PS C:\Users\ikmju> $space.galaxies.galaxy | Select-Object name

name
----
Large Magellanic Cloud
Sagittarius Dwarf Sphr
Small Magellanic Cloud
Boötes Dwarf
Canis Major Dwarf

Oppure, ancora, si possono ottenere i soli elementi il cui valore


distance è minore di 0.1, utilizzando il consueto cmdlet Where-Object:
PS C:\Users\ikmju> $space.galaxies.galaxy | ? { $_.distance -It 0.1 }

type name distance


---- ---- --------
dSph/E7 Sagittarius Dwarf Sphr 0.081
Irr Canis Major Dwarf 0.025

Il lettore noti che la shell provvede automaticamente alla


conversione dei tipi dei valori: nel codice precedente, per esempio, il
valore 0.1 è confrontato con distance, a tutti gli effetti una stringa
recuperata dal documento.
Ogni elemento recuperato dal documento XML è rappresentato nel
framework Microsoft .NET da un oggetto di tipo System.Xml.XmlElement,
mentre ogni attributo corrisponde a un’istanza di System.Xml.XmlAttribute.
Queste classi espongono diversi metodi e proprietà con cui è
possibile intervenire approfonditamente sui singoli dettagli del
documento.

XPath e le query complesse


XPath è una tecnologia standard che consente di recuperare
facilmente informazioni da una struttura XML, in base a una query.
Microsoft.NET dispone di un’ottima implementazione di XPath,
ampiamente supportata da PowerShell. Senza scendere nel
dettaglio dell’impiego di XPath, al di fuori degli obiettivi di questo
libro, il presente paragrafo analizza l’integrazione tra la shell e
questa tecnologia. All’interno del framework Microsoft .NET, ogni
oggetto del DOM dispone di due metodi in grado di interagire con
XPath:SelectNodes()eSelectSingleNode(). Entrambi i metodi accettano una
stringa con la query XPath da processare e ritornano nodi del
documento XML a cui fa capo l’istanza dell’oggetto del DOM; la
differenza tra i due consiste nel fatto cheSelectNodes() ritorna una
sequenza di nodi, mentre SelectSingleNode() ne ritorna uno solo. La
sintassi è:

<Nodo>.SelectNodes(<Query XPath>)
<Nodo>.SelectSingleNode(<Query XPath>)

Nello script che segue, per esempio, si ripropone l’estrazione


presentata poc’anzi, dove si filtrano gli elementi galaxy in base al
valore di distance:

PS C:\Users\ikmju> $space -SelectNodes("/galaxies/galaxy[distance < 0.1]")

type name distance


---- ---- --------
dSph/E7 Sagittarius Dwarf Sphr 0.081
Irr Canis Major Dwarf 0.025

In quest’altro script, invece, si recupera il solo elemento in cui


l’attributo type è pari a Irr:

PS C:\Users\ikmju> $space.SelectSingleNode("/galaxies/galaxy[©type = 'Irr']")

type name distance


---- ---- --------
Irr Canis Major Dwarf 0.025

Select-Xml
Poiché la tecnologia XPath presenta una notevole utilità, a partire
dalla versione 2.0 PowerShell mette a disposizione dell’utente il
cmdlet Select-Xml, per elaborare query XPath a fronte di uno o più
documenti XML. La query è specificata tramite il parametro
posizionale -Xpath, mentre utilizzando il parametro -Content o fornendo
tramite pipeline una o più stringhe è possibile indicare al comando il
contenuto XML da processare. In alternativa, è possibile specificare
uno o più percorsi di file XML tramite il parametro -path, secondo
posizionale, oppure una o più istanze di nodi XML tramite il
parametro -Xml o direttamente via pipeline.
Le sintassi di base di Select-Xml sono rappresentate da questo
schema:

<Nodol> [, <Nodo2>, ---] I Select-Xml <Query XPath> <Stringal> [, <Stringa2>, ...] I


Select-Xml <Query XPath>
Select-Xml <Query XPath> -Path <Path fìle>

Nello script che segue, per esempio, si sfrutta questo cmdlet per
ricercare qualsiasi nodo XML che abbia un attributo type pari a Irr
all’interno di tutti i file con estensione .xml della cartella corrente:

PS C:\Users\ikmju> Select-Xml '//*[©type="Irr"]' -Path *.xml


Name NodeType OuterXml
---- -------- --------
galaxy Element <galaxy type="Irr">
<name>Canis Major Dwarf</name>
<distance>0-025</distance>
</galaxy>

Quest’altro esempio, invece, ritorna solo i nodi corrispondenti alle


galassie il cui nome contiene il testo Magellanic:

PS C:\Users\ikmju> $space | Select-Xml -XPath '/galaxies/galaxy[contains(name,


"Magellanic")]'

Name NodeType OuterXml


---- -------- --------
galaxy Element <galaxy type="Irr/SB(s)m">
<name>Large Magellanic Cloud</name>
<distance>0-163</distance>
</galaxy>
galaxy Element <galaxy type="SB(s)m pec">
<name>Small Magellanic Cloud</name>
<distance>0.206</distance>
</galaxy>

Modificare elementi e attributi


La modifica dei valori attribuiti agli elementi esistenti di un
documento XML avviene recuperando il nodo di interesse tramite i
metodi degli oggetti del DOM e assegnando il nuovo valore a una
proprietà specifica, individuata dal tipo di oggetto da alterare. Per
impostare il testo di un elemento, come nel caso dell’elemento name
del file di esempio Space.Xml, è necessario intervenire sulla proprietà
InnerText del nodo; per gli attributi, come nel caso di type, si utilizza

invece la proprietà Value. Nello script che segue, per esempio, si


recupera l’elemento name del primo nodo galaxy del documento $space e
se ne reimposta il valore:

PS C:\Users\ikmju> $name = $space - SelectsingleNode("//galaxy[1]/name")


PS C:\Users\ikmju> $name- InnerText = "test"
PS C:\Users\ikmju> $space - galaxies - galaxy

type name distance


---- ---- --------
Irr/SB(s)m test 0.163
dSph/E7 Sagittarius Dwarf Sphr 0.081
[...]

In quest’altro esempio, invece, si modifica l’attributo type dello stesso


nodo galaxy:
PS C:\Users\ikmju> $type = $space - SelectsingleNode("//galaxy[1]/©type")
PS C:\Users\ikmju> $type-Value = "xyz"
PS C:\Users\ikmju>
PS C:\Users\ikmju> $space - galaxies - galaxy

type name distance


---- ---- --------
xyz test 0.163
[...]

La rappresentazione XML di un nodo può essere recuperata con


facilità mediante la proprietà d’istanza OuterXml dell’oggetto di
interesse; nell’esempio che segue si visualizza a video la
rappresentazione XML del primo nodo galaxy del documento $space:

PS C:\Users\ikmju> $space - SelectsingleNode("//galaxy[1]") -OuterXml


<galaxy type="xyz " ><name>test</namexdistance>0 .163</distancex/galaxy>

Nonostante sia possibile memorizzare il contenuto del file


usufruendo della proprietà OuterXml associata all’intero documento e di
un cmdlet come Set-Content, la classe System.Xml.XmlDocument espone
anche il metodo Save(), al quale è possibile fornire il nome del file su
cui salvare il documento. Per salvare su disco il documento XML
modificato in memoria, dunque, ci si può avvalere di uno script come
questo:

PS C:\Users\ikmju> $space.Save("C:\Temp\Space2.xml")

Creare nuovi nodi


La realizzazione di nuovi elementi e attributi è subordinata alla
creazione dell’istanza dell’oggetto DOM desiderato, per mezzo dei
metodi d’istanza Create*() esposti dalla classe System-Xml-XmlDocument.
Esiste un metodo Create*() per ogni tipologia di nodo contemplata dal
DOM, come illustrato da questo script:

PS C:\Users\ikmju> $space | Get-Member Create* | Select Name

Name
----
CreateAttribute
CreateCDataSection
CreateComment
CreateDocumentFragment
CreateDocumentType
CreateElement
CreateEntityReference
CreateNavigator
CreateNode
CreateProcessingInstruction
CreateSignificantWhitespace
CreateTextNode
CreateWhitespace
CreateXmlDeclaration

Nonostante l’abbondanza, l’impiego più frequente si riduce al


metodo CreateElement(), usato per creare nuovi elementi, e al metodo
CreateAttribute(), per creare nuovi attributi. La sintassi di questi due

metodi è rappresentata dal seguente schema:

<Documento>-CreateElement(<NomeTag>)
<Documento>- CreateAttribute(<NomeAttributo>)

Ciascun metodo ritorna un’istanza del rispettivo nodo: CreateElement()


ritorna oggetti di tipo System.Xml.XmlElement, mentre CreateAttribute()
restituisce nuovi System.Xml.XmlAttribute. L’impostazione dei valori segue
quanto analizzato nel paragrafo precedente ed è analoga
all’alterazione dei valori per i nodi esistenti. L’aggiunta di un
elemento al DOM avviene per mezzo di uno di questi metodi di
istanza, presenti nel documento stesso e in qualsiasi elemento figlio:
• AppendChild(<Elemento>) Aggiunge l’elemento specificato alla fine
della lista di elementi figlio;
• InsertBefore(<Elemento>, <Riferimento>) Inserisce l’elemento specificato
prima dell’elemento di riferimento indicato;
• InsertAfter(<Elemento>, <Riferimento>) Inserisce l’elemento specificato dopo
l’elemento di riferimento indicato.
Per creare un nuovo elemento galaxy e aggiungerlo al documento
$space, dunque, si può procedere in modo simile a quanto illustrato in

questo esempio:

PS C:\Users\ikmju> $myGalaxy = $space-CreateElement("galaxy")


PS C:\Users\ikmju> $space - SelectsingleNode("/galaxies") -AppendChild($myGalaxy)

Name : galaxy
LocalName : galaxy
[...]

NO Il metodo AppendChild() e, in generale, tutti i metodi di manipolazione del DOM


TA ritornano sempre l’oggetto aggiunto o modificato, che nell’esempio viene
visualizzato automaticamente a video.
L’aggiunta di un attributo a un elemento esistente avviene per mezzo
della proprietà Attributes e di uno dei metodi da questa esposti:
• Append(<Attributo>) Aggiunge l’attributo specificato alla fine della
lista di attributi dell’elemento;
• InsertBefore(<Attributo>, <Riferimento>) Inserisce l’attributo specificato
prima dell’attributo di riferimento indicato;
• InsertAfter(<Attributo>, <Riferimento>) Inserisce l’attributo specificato
dopo l’attributo di riferimento indicato.
Per aggiungere l’attributo type all’oggetto $myGalaxy creato nell’esempio
precedente, dunque, è sufficiente utilizzare questo script:

PS C:\Users\ikmju> $myGalaxyType = $space-CreateAttribute("type")


PS C:\Users\ikmju> $myGalaxyType-Value = "xyz"
PS C:\Users\ikmju> $myGalaxy-Attributes-Append($myGalaxyType)

ParentNode :
Name : type
LocalName : type
[...]

Per ottenere la rappresentazione XML dell’oggetto $myGalaxy e


verificare la buona riuscita della modifica è sufficiente eseguire
questo blocco:

PS C:\Users\ikmju> $myGalaxy-OuterXml
<galaxy type="xyz" />

Gli elementi del DOM espongono il metodo SetAttribute(), che consente di


NO aggiungere rapidamente nuovi attributi all’oggetto cui si riferisce. Tale metodo,
TA tuttavia, non offre lo stesso livello di dettaglio esposto dalla tecnica illustrata in
precedenza.

Eliminare i nodi esistenti


L’eliminazione dei nodi all’interno di un documento XML è affidata ad
alcuni metodi d’istanza Remove*(), presenti nella maggior parte degli
oggetti del DOM. Per eliminare un elemento è sufficiente impiegare il
metodo RemoveChild() del suo elemento contenitore all’interno del
DOM. Per eliminare tutti gli elementi, inoltre, è disponibile il metodo
RemoveAll(). La sintassi dei due metodi è rappresentata da questo

schema:
<Contenitore>.RemoveChild(<Elemento>)
<Contenitore>.RemoveAll()

In questo script, per esempio, si individua e si elimina il secondo


elemento galaxy del documento di esempio Space.Xml:

PS C:\Users\ikmju> $secondGalaxy = $space - SelectsingleNode("//galaxy [2} ")


PS C:\Users\ikmju> $space - galaxies-RemoveChild($secondGalaxy)

type name distance


---- ---- --------
dSph/E7 Sagittarius Dwarf Sphr 0.081

Per eliminare un attributo di un elemento del DOM, infine,


nonostante ci si possa avvalere del metodo RemoveAt() esposto dalla
proprietà Attributes, è più conveniente utilizzare del metodo
RemoveAttribute(), direttamente accessibile da tutte le istanze di

System.Xml.XmlElement. Il metodo segue questa semplice sintassi:

<Elemento>.RemoveAttribute(<Nome attribùto>)

Per rimuovere l’attributo type del primo elemento galaxy di ,


$space

dunque, è sufficiente impiegare uno script simile a questo:

PS C:\Users\ikmju> $firstGalaxy = $space.SelectSingleNode("//galaxy[1]")


PS C:\Users\ikmju> $firstGalaxy.RemoveAttribute("type")
PS C:\Users\ikmju> $firstGalaxy.OuterXml
<galaxyxname>Large Magellanic Cloud</namexdistance>0 .163</distancex/galaxy>

Trasformare i documenti
Lo standard XSLT, acronimo di eXtensible Stylesheet Language
Transformations, definisce un linguaggio in grado di trasformare un
documento XML in un altro file testuale, basando la trasformazione
su di un documento XML. Il framework Microsoft .NET offre un
ampio supporto per questa tecnologia e in questo paragrafo se ne
illustra brevemente un’applicazione pratica, pur evitando di trattare
direttamente il linguaggio XSLT, al di fuori degli obiettivi di questo
libro.

XSLT
All’interno del framework Microsoft .NET, le classi che consentono di
utilizzare la tecnologia XSLT sono concentrate nel namespace
System.Xml.Xsl, nell’assembly System.Xml. Tra tutte, la classe
probabilmente più utile al professionista che desidera effettuare una
trasformazione XSLT all’interno della shell èSystem.Xml.Xsl.XslTransform: le
istanze di questo tipo, infatti, espongono tutti i metodi necessari per
portare a termine questo tipo di attività.
La trasformazione si può dividere in due parti: posto di avere a
disposizione il file XML di trasformazione - per convenzione dotato di
estensione .xslt - è possibile richiedere all’istanza della classe
menzionata di caricarlo, utilizzando il metodo Load();

successivamente è possibile richiamare il metodo Transform(),


specificando il nome del file XML da trasformare e il nome del file di
destinazione. La sintassi di base di questi due metodi chiave è
riepilogata nel seguente schema:

<XslTransform> - Load(<Nome file .xslt>)


<XslTransform> - Transform(<Nome file input>, <Nome file output>)

Si supponga, per esempio, di disporre di un file Html.xslt con questo


contenuto:

<?xml version="1 - 0" encoding="utf-8"?>


<xsl: stylesheet version="1 - 0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="html" indent="yes"/>

<xsl:template match="/">
<html>
<head>
<title>Galassie vicine</title>
</head>
<body>
<xsl:apply-templates />
</body>
</html>
</xsl:template>

<xsl:template match="galaxy">
<b>
<xsl:value-of select="name/text()" />
</b>
Distanza: <xsl:value-of select="distance/text()" /> Mly
<br />
</xsl:template>

</xsl:stylesheet>
Utilizzando il supporto XSLT offerto dal framework Microsoft .NET è
possibile trasformare il documento di esempio Space.xml in un
documento in formato html, utilizzabile per la visualizzazione tramite
browser web. Lo script che segue dimostra come raggiungere
questo risultato:

PS C:\Users\ikmju> $xslt = New-Object System.Xml.Xsl.XslTransform


PS C:\Users\ikmju> $xslt.Load("Html.xslt")
PS C:\Users\ikmju> $xslt.Transform("Space.xml", "Test.html")

Figura 22.1 - Il file Test.html visualizzato da Internet Explorer.


Il registro eventi di Windows

I cmdlet che consentono di recuperare gli eventi dal


registro di Windows, sia attraverso l’infrastruttura di
registrazione classica sia tramite la piattaforma
più recente, disponibile a partire da Windows Vista;
un’accurata descrizione delle principali tecniche di filtro e
ricerca degli eventi e una panoramica sui comandi che
consentono di operare modifiche sui log.

Il registro eventi di Windows (chiamato anche log degli eventi o,


nella localizzazione inglese del sistema, event log) è sempre stato
uno degli strumenti di diagnosi più utili a disposizione dei
professionisti IT. Nato per registrare semplici notifiche hardware e
software in formato testo, oggi è in grado di archiviare e recuperare
in modalità asincrona milioni di eventi, strutturati in formato XML.
Sin dalla versione 1.0, PowerShell include diversi cmdlet che
consentono di gestire con facilità i log degli eventi e di recuperare e
manipolare gli eventi stessi. A partire dalla versione 2.0, inoltre, ai
comandi esistenti ne sono stati aggiunti di nuovi, in grado di operare
con le nuove funzionalità presenti nell’infrastruttura di registrazione
degli eventi dei sistemi operativi più recenti.
Il capitolo presenta una panoramica dei comandi di PowerShell
dedicati alla gestione del log degli eventi e contiene alcuni esempi
pratici di utilizzo di questa tecnologia.

Recuperare i log e gli eventi


All’interno della shell, la funzionalità di recupero delle informazioni
dai log degli eventi di Windows è affidata a due differenti cmdlet: il
primo è Get-EventLog e permette di interfacciarsi con i log classici di
Windows basati su testo; l’altro cmdlet è Get-WinEvent ed è nato per
esporre le funzionalità della versione 6.0 dell’infrastruttura di
registrazione degli eventi, disponibile a partire da Windows Vista.

Get-EventLog
Questo cmdlet è in grado di ottenere informazioni sui log e sugli
eventi di uno o più computer, limitandosi di default a quello locale.
Se lanciato utilizzando lo switch -List, il comando ritorna la lista dei
registri disponibili nella macchina, sotto forma di istanze della classe
System.Diagnostics.EventLog. Poiché il cmdlet non supporta la versione 6.0

dell’infrastruttura di registrazione degli eventi di Windows, i dati


restituiti non includono i log concepiti per questa nuovo sistema, ma
sono limitati a quelli classici.
Nel blocco che segue, per esempio, si recupera la lista dei log locali:
PS C:\Users\ikmju> Get-EventLog -List

Max(K) Retain OverflowAction Entries Log


------ ----- ----------------- -----------
20,480 0 OverwriteAsNeeded 40,925 Application
512 7 OverwriteOlder 0 DFS Replication
20,480 0 OverwriteAsNeeded 0 HardwareEvents
512 7 OverwriteOlder 0 Internet Explorer
20,480 0 OverwriteAsNeeded 0 Key Management Service
8,192 0 OverwriteAsNeeded 82 Media Center
[...]

Una volta recuperato il nome del log di interesse è possibile fornire


questo valore a Get-EventLog per mezzo del parametro posizionale -
LogName; in tal caso il comando restituisce tutti gli eventi registrati nel
registro indicato, tramite istanze della classe
System.Diagnostics.EventLogEntry.
Nello script che segue, per esempio, si recuperano tutti gli eventi del
registro Application (Applicazione, nella localizzazione italiana di
Windows):
PS C:\Users\ikmju> Get-EventLog Application

Index Time EntryType Source InstanceID Message


----- ---- --------- ------ ---------- -------
64816 May 03 12:27 Information MSSQLSERVER 1073758893 Microsoft
SQL Server 2008 (SP1) - 10.0.2531.0 (...
64815 May 03 12:27 Information Microsoft-Windows... 10000 Starting
session 0 - 2010-05-03T10:27:02.269513...
64814 May 03 12:26 Information Microsoft-Windows... 10000 Starting
session 0 - 2010-05-03T10:26:38.009869...
64813 May 03 12:26 Information System Restore 8194 Successfully
created restore point (Process = C...
64812 May 03 12:25 Information MsiInstaller 1040 Beginning a
Windows Installer transaction: C:\U...
64811 May 03 09:15 Information Winlogon 1073745925 Windows
license validated.
64810 May 03 09:08 0 Software Protecti... 1073742727 The Software
Protection service has stopped....
64809 May 03 09:05 Information Microsoft-Windows... 1000 Performance
counters for the WmiApRpl (WmiApRpl...
64808 May 03 09:05 Information Microsoft-Windows... 1001 Performance
counters for the WmiApRpl (WmiApRpl...
64807 May 03 09:03 Information Windows Search Se... 1073742827 The Windows
Search Service started....
[...]

NO Per interrompere anticipatamente il recupero degli eventi è possibile utilizzare la


TA combinazione di tasti Ctrl+C.

Nonostante sia possibile filtrare gli elementi ritornati utilizzando la


pipeline e il cmdlet Where-Object, il log degli eventi è in grado di
applicare uno o più filtri direttamente in loco, evitando alla shell
l’onere di procedere autonomamente e aumentando così le
performance.
Per impiegare questo sistema di filtro è necessario utilizzare i
parametri aggiuntivi di cui è dotato il cmdlet Get-EventLog. I parametri -
After e -Before consentono di specificare l’intervallo temporale entro

cui gli eventi ritornati devono essere presenti, mentre -Newest ne limita
il numero totale; con il parametro -InstanceId si forniscono al comando
uno o più identificativi di istanza degli eventi desiderati, mentre con -
Message si può applicare un filtro direttamente al testo del messaggio

degli eventi, utilizzando anche caratteri wildcard. Il parametro -Source,


poi, permette di restringere la ricerca in base alla sorgente
dell’evento, mentre -UserName applica un filtro in base ai nomi utente
associati agli elementi. Utilizzando il parametro -EntryType, infine, è
possibile restringere la ricerca al tipo generico di evento, utilizzando i
valori riportati nella Tabella 23.1.
La sintassi di filtro del cmdlet può essere dunque schematizzata
così:
Get-EventLog <Nome registro> [-Before <Data>] [-After <Data>] [-Newest
<Totale>] [-EntryType <TipoEvento>] [-InstanceId <Identificativo istanza>] [-
Message <Testo>] [-Source <Sorgente>] [-UserName <Utente>]

Tabella 23.1 – I valori ammessi dal parametro -EntryType.


Valore Descrizione
Error Identifica un errore
FailureAudit Identifica un tentativo di accesso con esito negativo a una risorsa
controllata
Information Identifica un’informazione generica o un’operazione avvenuta con successo
SuccessAudit Identifica un tentativo di accesso con esito positivo a una risorsa controllata
Warning Identifica una situazione potenzialmente problematica

Nello script che segue, per esempio, si recuperano i primi tentativi di


accesso fallito a una risorsa controllata avvenuti dopo una certa
data, all’interno del registro Security (Sicurezza, nella
localizzazione italiana) della macchina locale:
PS C:\Users\ikmju> Get-EventLog Security -EntryType FailureAudit -After
I[datetime]'2010-01-01') -Newest 3

Index Time EntryType Source InstanceID Message


----- ---- --------- --------- ---------- -------
118940 May 03 22:12 FailureA... Microsoft-Windows... 6281 Code
Integrity determined that the page hashes ...
118939 May 03 22:12 FailureA... Microsoft-Windows... 6281 Code
Integrity determined that the page hashes ...
118938 May 03 22:12 FailureA... Microsoft-Windows... 6281 Code
Integrity determined that the page hashes ...

In quest’altro esempio, invece, si recuperano gli eventi dal registro


System (Sistema, nella localizzazione italiana) e si ritornano solo
quelli che contengono il testo SQL Server al loro interno:
PS C:\Users\ikmju> Get-EventLog System -Message '*SQL Server*1
PS C:\Users\ikmju> Get-EventLog System -Message '*SQL Server*'

Index Time EntryType Source InstanceID Message


----- ---- --------- ------ --------- ------
282116 May 03 21:41 Information Service Control M... 1073748860 The SQL
Server VSS Writer service entered the r...
282048 May 03 21:35 Information Service Control M... 1073748860 The SQL
Server VSS Writer service entered the s...
281810 May 03 12:27 Information Service Control M... 1073748860 The SQL
Server (MSSQLSERVER) service entered th...
[...]

Mediante il parametro -ComputerName è possibile richiede al cmdlet di


recuperare i log o gli eventi da uno o più PC differenti da quello
locale, utilizzando il protocollo DCOM/RPC per comunicare con gli
host remoti.
Nello script che segue, per esempio, si recuperano tutte gli eventi
legati all’identificativo d’istanza 4624 (l’evento di logon con esito
positivo nel sistema) dal registro della sicurezza di un host remoto,
identificato tramite nome NetBIOS:
PS C:\Users\ikmju> Get-EventLog Security -Instanceld 4624
Index Time EntryType Source InstanceID Message
----- ---- ------ ------ ---------- -------
119008 May 04 16:41 SuccessA... Microsoft-Windows... 4624 An
account was successfully logged on....
119005 May 04 15:09 SuccessA... Microsoft-Windows... 4624 An
account was successfully logged on....
119001 May 04 14:52 SuccessA... Microsoft-Windows... 4624 An
account was successfully logged on....
[...]

Affinché il cmdlet Get-EventLog possa recuperare informazioni da remoto, nelle


NO macchine di destinazione deve essere avviato il servizio di registro remoto
TA (Remote Registry) e tra la macchina sorgente e quella di destinazione deve
essere consentito il traffico su protocollo RPC.

Tramite gli oggetti di tipo system.Diagnostics.EventLogEntry è possibile


recuperare tutti i dettagli associati a ciascun evento: la proprietà
MacnineName, per esempio, ritorna il nome del PC in cui l’evento è stato
prodotto, mentre Message ne ritorna il testo completo e TimeGenerated la
data di generazione. La proprietà ReplacementStrings, inoltre, contiene un
array di stringhe utilizzate come valori per il modello dell’evento;
esse costituiscono quindi una preziosa fonte di informazioni, la cui
strutturazione dipende dall’istanza di evento analizzato.
Tornando allo script discusso in precedenza, gli eventi d’istanza 4624
contengono diverse informazioni in merito all’attività di logon che
descrivono, come il dominio e il nome dell’utente che ha effettuato
l’operazione, l’eventuale indirizzo IP e così via.
Nello script che segue, si recuperano i dettagli menzionati a partire
dalla proprietà Replacementstrings degli ultimi due eventi di questo tipo
memorizzati nel PC locale:
PS C:\Users\ikmju> Get-EventLog Security -InstanceId 4624 -Newest 2 |

>> ForEach-Object {
>> $data = @{}
>>
>> $data.TimeGenerated = $_.TimeGenerated
>> $data.SecurityID = $_.ReplacementStrings[4]
>> $data.UserName = $_.ReplacementStrings[5]
>> $data.Domain = $_.ReplacementStrings[6]
>> $data.IPAddress = $_.ReplacementStrings[18]
>> $data.GetEnumerator()
>> }
>>

Name Value
---- -----
SecurityID S-1-5-18
Domain NT AUTHORITY
TimeGenerated 5/4/2010 4:41:58 PM
UserName SYSTEM
IPAddress -

SecurityID S-1-5-21-3959993727-2881392450-3707713393-1000
Domain Alkaid
TimeGenerated 5/4/2010 3:09:05 PM
UserName ikmju
IPAddress -

Per ulteriori informazioni sulla strutturazione dei dati all’interno della proprietà
NO
ReplacementStrings e la disponibilità degli stessi per ciascuna istanza di evento si
TA
rimanda al sito Microsoft TechNet (http://technet.microsoft.com).

Get-WìnEvent
Come anticipato nella prima parte del capitolo, questo cmdlet è in
grado di interfacciarsi con la più recente versione dell’infrastruttura di
registrazione degli eventi, disponibile a partire da Windows Vista.
Oltre a recuperare informazioni dai classici registri degli eventi in
modo analogo al precedente, il cmdlet Get-winEvent supporta la lettura
dei log creati utilizzando questa nuova tecnologia e i file creati dal
sistema Event Tracing for Windows (ETW).
NO Il cmdlet Get-WinEvent richiede la presenza del framework Microsoft .NET versione
TA 3.5 o superiore.

Il comando è anche in grado di fornire informazioni relative a


macchine remote, con il limite di poter agire su di una macchina alla
volta, specificandone il nome NetBIOS o l’indirizzo IP attraverso il
parametro -ComputerName. Anche in questo caso la comunicazione tra le
parti avviene tramite il protocollo DCOM/RPC.
Mediante il parametro -ListLog è possibile recuperare dal cmdlet tutti i
registri della macchina desiderata, inclusi i registri classici disponibili
con Get-EventLog. Al contrario del parametro -List del cmdlet
precedente, però, in questo caso è necessario specificare un filtro
testuale sul nome degli elementi di interesse, che può contenere
caratteri wildcard. Gli oggetti ritornati appartengono alla classe
System.Diagnostics. Eventing. Reader - EventLogConfiguration.
Per recuperare la lista di tutti i registri locali, dunque, è possibile
utilizzare uno script simile a questo:
PS C:\Users\ikmju> Get-WinEvent -ListLog *

LogName MaximumSizeInBytes RecordCount LogMode


------ ------------------ ----------- -------
Application 20971520 40932
Circular
DFS Replication 1052672 0
Circular
HardwareEvents 20971520 0
Circular
[...]
Security 0971520 31164
Circular
System 20971520 60657
Circular
[...]
Microsoft-IIS-Configuratio... 1052672
Circular
Microsoft-IIS-Configuratio... 1052672
Circular
Microsoft-Windows-API-Trac... 1052672 0
Circular
[...]

Per recuperare gli eventi relativi a uno o più registri, è sufficiente


utilizzare il cmdlet specificando, attraverso il parametro posizionale -
LogName, l’elenco degli elementi desiderati, utilizzando eventualmente i
caratteri wildcard; nel caso in cui il parametro non sia specificato,
inoltre, Get-WinEvent ritorna l’unione degli eventi di tutti i registri.
Quando il cmdlet è utilizzato per recuperare eventi, gli oggetti
emessi nella pipeline sono istanze della classe
System.Diagnostics.Eventing.Reader.EventLogRecord.
La sintassi del comando può essere schematizzata in questo modo:
Get-WinEvent <Nome logl> [, <Nome log2>, ...\

Nel blocco che segue, per esempio, si recuperano tutti gli eventi
memorizzati nel registro Microsoft-Windows-TaskScheduler/Operational del
computer locale, utilizzato dal Task Scheduler di Windows
(Operazioni pianificate, nella localizzazione italiana):
PS C:\Users\ikmju> Get-WinEvent Microsoft-Windows-TaskScheduler/Operational

TimeCreated ProviderName Id Message


---------- ------------ -- -------
5/4/2010 5:08:20 PM Microsoft-Windows-TaskSche... 102 Task Scheduler
successfull...
5/4/2010 5:08:20 PM Microsoft-Windows-TaskSche... 201 Task Scheduler
successfull...
5/4/2010 5:06:19 PM Microsoft-Windows-TaskSche... 200 Task Scheduler launched
ac...
[...]

NO Anche in questo caso, per terminare prematuramente l’esecuzione dello script è


TA possibile utilizzare la combinazione di tasti Ctrl+C.

Alla stregua di quanto analizzato per il cmdlet precedente,


nonostante il filtro degli eventi recuperati da Get-winEvent possa
avvenire a posteriori (utilizzando, per esempio, il cmdlet wnere-object),
è consigliabile sfruttare il meccanismo di filtro delle informazioni in
loco, al fine di aumentare le performance dei propri script.
Nonostante il numero di parametri dedicati al filtro degli eventi sia
minore rispetto a Get-EventLog, il comando è però in grado di impiegare
il potente meccanismo di filtro dell’infrastruttura di registrazione che,
a partire dalla versione 6.0, memorizza ciascun evento per mezzo di
una struttura in formato XML: grazie a questo modello, infatti, Get-
WinEvent è in grado di impiegare query XPath fornite dall’utente per

analizzare e ricercare all’interno del log degli eventi.

Filtrare gli eventi tramite XPath


Nonostante ogni evento sia tipicamente corredato dai dati peculiari
della propria tipologia, tutte le strutture XML che ne derivano
condividono lo stesso schema di base, per cui è possibile concepire
facilmente query XPath in grado di agire genericamente su qualsiasi
evento.
Tra le informazioni di base, disponibili in ogni evento, sono
comprese:
• l’identificativo;
• la data e l’ora di registrazione;
• la criticità dell’evento;
• il log di pertinenza;
• il componente che ha generato l’evento;
• la categoria dell’evento;
• l’utente Windows cui l’evento è associato;
• il computer in cui è avvenuta la registrazione.
Per comprendere come questi dati siano immagazzinati dal registro
eventi, è possibile recuperare un qualsiasi evento ed esplorarne la
struttura XML: per facilitare questa attività, la classe
System.Diagnostics.Eventing.Reader.EventLogRecord espone il metodo d’istanza

ToXml() che, per ogni evento ritornato da Get-WinEvent, restituisce la


relativa struttura XML.
Nello script che segue si recupera la struttura XML del primo evento
disponibile nel log System (Sistema, nella localizzazione italiana) e
la si memorizza nel file Event.xml, nella cartella corrente, per poi aprirla
con l’applicazione predefinita (di default Inter-net Explorer) e
sfruttare così l’eventuale supporto per la colorazione del markup:
PS C:\Users\ikmju> Get-WinEvent System |
>> Select-Object -First 1
>> ForEach-Object { $_.ToXml() }
>> Out-File Event.xml
>>
PS C:\Users\ikmju> Start-Process Event.xml

Un tipico evento - come la notifica dell’arresto di un servizio - ha una


struttura simile a quella seguente, dove all’interno dell’elemento
system sono concentrati i dati comuni a tutta l’infrastruttura:

<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Service Control Manager" Guid="{555908d1-a6d7-4695-8e1e-
26931d2012f4}" EventSourceName="Service Control Manager" />
<EventID Qualifiers="16384">7036</EventID>
<Version>0</Version>
<Level>4</Level>
<Task>0</Task>
<Opcode>0</Opcode>
<Keywords>0x8080000000000000</Keywords>
<TimeCreated SystemTime="2010-05-13T08:28:11.992666100Z" />
<EventRecordID>2596</EventRecordID>
<Correlation />
<Execution ProcessID="596" ThreadID="1416" />
<Channel>System</Channel>
<Computer>Alkaid</Computer>
<Security />
</System>
<EventData>
<Data Name="param1">Application Experience</Data>
<Data Name="param2">stopped</Data>
<Binary>410065004C006F006F006B00750070005300760063002F0031000000</
Binary>
</EventData>
</Event>

Figura 23.1 - La struttura XML dell’evento, visualizzata tramite Event Viewer.

Come si evince dall’output dell’esempio precedente, la struttura XML


ritornata da Get-WinEvent è la stessa che si può recuperare da Event
Viewer (Visualizzatore eventi, nella localizzazione italiana), optando
per la visualizzazione XML all’interno dei dettagli dell’evento di
interesse.
Senza scendere nel dettaglio dello schema impiegato, può essere
utile capire come avvalersi di questa tecnologia per recuperare le
informazioni di interesse in modo più efficiente e completo, grazie a
XPath. La query che segue, per esempio, recupera gli eventi critici e
gli errori i cui livelli di criticità sono, rispettivamente, pari a 1 oppure a
2:

*/System[(Level=1 or Level=2)]

Grazie al parametro -FilterXPath, il cmdlet Get-winEvent è in grado di


accettare query di questa tipologia e di fornirle al log degli eventi
desiderato, in modo tale da elaborarle in loco e aumentare le
performance. La sintassi d’uso è molto semplice e può essere
schematizzata così:
Get-WinEvent <Nome logl> [, <Nome log2>, ...] -FilterXPath <Query XPath>

Lo script che segue, per esempio, esegue la query XPath


precedente all’interno del log Security (Sicurezza, nella
localizzazione italiana):
PS C:\Users\ikmju> Get-WinEvent Security -FilterXPath '*/System[(Level=l or
Level=2)]

TimeCreated ProviderName Id Message


----------- ------------ -- -------
5/12/2010 7:31:24 AM Microsoft-Windows-Eventlog 1108 The event logging service
...
5/12/2010 7:31:24 AM Microsoft-Windows-Eventlog 1108 The event logging service
...
[...]

Utilizzando il parametro -FilterXml, infine, è possibile fornire a Get-


WinEvent un documento XML di descrizione della query di recupero,

creato in base alla stessa struttura delle Custom views gestite da


Event Viewer (Visualizzazioni personalizzate del Visualizzatore
eventi, nella localizzazione italiana): un documento di questo tipo è
in grado di inglobare più query XPath, a fronte di log e sorgenti
differenti, consentendo all’utente di recuperare con facilità dati
provenienti da fonti eterogenee.
La sintassi d’uso di questo parametro è:
Get-WinEvent -FilterXml <Filtro XML>

Per creare un documento XML di filtro da fornire a Get-WinEvent è


sufficiente creare una Custom view all’interno di Event Viewer ed
estrapolare il documento XML generato automaticamente. La Figura
23.2 illustra la fase principale di configurazione di un filtro in grado di
recuperare gli eventi informativi delle ultime dodici ore relativi al log
della sicurezza e a quello del backup di Windows.

Figura 23.2 - L’interfaccia di configurazione di una Custom view in Event Viewer.

Fornendo a Get-WinEvent il documento XML generato, recuperabile dal


tab XML dell’interfaccia, il cmdlet ritorna gli stessi eventi che
sarebbero ritornati dalla custom view equivalente:
PS C:\Users\ikmju> $customView = @"
>> <QueryList>
>> <Query Id="0" Path="Security">
>> <Select Path="Security">*[System[(Level=4 or Level=0) and
TimeCreated[timediff(@SystemTime) <= 43200000]]]</Select>
>> <Select Path="Microsoft-Windows-Backup">*[System[(Level=4 or Level=0)
and TimeCreated[timediff(@SystemTime) <=
43200000]]]</Select>
>> </Query>
>> </QueryList>
>> "@
>>

PS C:\Users\ikmju> Get-WinEvent -FilterXml $customView


TimeCreated ProviderName Id Message
----------- ------------ -- ------ --
5/13/2010 11:45:28 AM Microsoft-Windows-Security... 4648 E stato tentato un
accesso...
5/13/2010 11:45:28 AM Microsoft-Windows-Security... 4648 E stato tentato un
accesso...
5/13/2010 11:21:20 AM Microsoft-Windows-Security... 4648 E stato tentato un
accesso...
[...]

Filtrare gli eventi tramite array associativo


Sebbene consenta di gestire tutti i dettagli dei filtri, la ricerca di
eventi mediante XPath richiede una conoscenza quantomeno di
base di questa tecnologia, a meno che non si utilizzi l’interfaccia
Custom view di Event Viewer. Fortunatamente, utilizzando il
parametro -FilterHashtable di Get-WinEvent è possibile fornire al comando
uno o più modelli di evento da ricercare, in base ai valori degli
elementi della relativa struttura XML; per descrivere ciascun modello
di evento si utilizza un array associativo, all’interno del quale sono
memorizzate le coppie chiave-valore relative ai criteri di uguaglianza
che deve soddisfare ogni relativo sottoelemento di System nella
struttura XML dell’evento. Internamente, il comando costruisce una
query XPath a partire da ogni array associativo fornito e ne esegue
la combinazione presso il log degli eventi, in modo analogo a quanto
già illustrato nel paragrafo precedente.
La sintassi utilizzabile per filtrare gli eventi ritornati da Get-winEvent
attraverso array associativi è:
Get-WinEvent -FilterHashtable <Array 1> [, <Array 2>, ...]

Nello script che segue, per esempio, si filtrano tutti gli eventi critici
(Level pari a 1) del log Application relativi all’utente BOZOS\Tycho:
PS C:\Users\ikmju> Get-WinEvent -FilterHashTable ©{ LogName='Application';
Level=l; UserID='BOZOS\Tycho'}

TimeCreated ProviderName Id Message


5/10/2010 9:21:57 PM cdrom 11 Il driver ha rilevato un e...
5/10/2010 9:18:22 PM atikmdag 43029 Display is not active
[...]

In quest’altro script, invece, si recuperano tutti gli eventi critici e di


errore (Level pari a 1 o 2) generati nelle ultime 12 ore in qualsiasi log:
PS C:\Users\ikmju> Get-WinEvent -FilterHashtable ©{ LogName='*'; Level=l,2;
StartTime=(Get-Date)-AddHours(-12); }
TimeCreated ProviderName Id Message
----------- ------------ -- -------
5/13/2010 12:05:06 PM Microsoft-Windows-EventSystem 4621 COM+ Event System:
impossi...
5/13/2010 11:01:40 AM Microsoft-Windows-Distribu... 10016 Le impostazioni delle
auto...
5/13/2010 10:59:25 AM Microsoft-Windows-Kernel-E... Avvio della sessione "Circ...
[...]

Tabella 23.2 – Le principali chiavi utilizzabili con il parametro –


FilterHashtable.
Chiave Descrizione
Il nome di uno o più log degli eventi in cui ricercare informazioni; accetta
LogName
caratteri wildcard
Il nome di uno o più provider di log di cui filtrare gli eventi; accetta caratteri
ProviderName
wildcard
ID Uno o più identificativi numerici degli eventi desiderati
Level Uno o più livelli di criticità con cui effettuare la ricerca
StartTime La data e ora di inizio dell’intervallo entro il quale comprendere la ricerca
EndTime La data e ora di fine dell’intervallo entro il quale comprendere la ricerca
Il SID (Security Identifier) dell’utente Windows cui gli eventi da ricercare
UserID sono correlati; accetta anche una stringa con il nome dell’utente, convertita
in automatico in SID dal cmdlet

Recuperare i provider disponibili


A partire dalla versione 6.0 il log degli eventi di Windows utilizza una
struttura completamente autodescrittiva, in grado di fornire
informazioni utili sulle tipologie di evento che ogni provider è in grado
di generare. Tramite il parametro -ListProvider è possibile richiedere al
cmdlet Get-WinEvent di recuperare tutti i provider registrati in un
particolare sistema, facendo opzionalmente uso dei caratteri
wildcard. La sintassi, davvero semplice, è schematizzabile così:
Get-WinEvent -ListProvider <Nome provider>

Nello script che segue si recuperano i primi 5 provider di evento


disponibili nella macchina locale:
PS C:\Users\ikmju> Get-WinEvent -ListProvider * | Select-Object -First 5

Name LogLinks
---- --------
.NET Runtime {Application}
.NET Runtime Optimization Service {Application}
Application {Application}
Application Error {Application}
Application Hang {Application}

Ogni oggetto emesso nella pipeline da fa capo alla classe


Get-WinEvent

System-Diagnostics - Eventing-Reader -ProviderMetadata, che espone alcune utili

proprietà tramite le quali si può indagare sul supporto offerto dal


provider di interesse. La proprietà LogLinks, per esempio, contiene un
riferimento per ogni log degli eventi su cui il provider può scrivere,
mentre Events contiene una collezione di modelli di evento utilizzati
dal provider.
Nello script che segue si recuperano tutti i modelli di evento creati
dal provider Mi-crosoft-windows-security-Auditing, cui è affidata la gestione
degli eventi di audit del sistema operativo, e se ne visualizza a video
l’identificativo:
PS C:\Users\ikmju> Get-WinEvent -ListProvider Microsoft-Windows-Security-Auditing |
>> Select-Object -Expand Events |
>> Select-Object Id |
>> Format-Wide -Column 20
>>
4608 4609 4610 4611 4612 4614 4615 4616 4616 4618 4621 4622 4624
4625 4634 4646 4647 4648 4649 4650
4651 4652 4653 4654 4654 4655 4656 4656 4657 4658 4659 4660 4661
4662 4663 4664 4665 4666 4667 4668
4670 4671 4672 4673 4674 4675 4688 4689 4690 4691 4692 4693 4694
4695 4696 4697 4698 4699 4700 4701
[...]

Per ulteriori informazioni sui provider di evento e sulle informazioni recuperabili


NO
tramite i relativi metadati si rimanda al sito Microsoft TechNet
TA
(http://technet.microsoft.com) e MSDN (http://msdn.microsoft.com).

Recuperare gli eventi da file


Oltre al recupero dai log degli eventi, Get-WinEvent è anche in grado di
ottenere informazioni dai file memorizzati tramite Event Viewer
(estensione .evt e .evtx) oppure mediante la piattaforma Enterprise
Tracing for Windows (estensione .etl). Questa caratteristica rende
questo cmdlet un prezioso alleato durante le fasi di diagnosi degli
eventi archiviati e permette di evitare di utilizzare tool esterni –
come, per esempio, LogParser – per recuperare informazioni da questa
tipologia di file.
In generale, il funzionamento di Get-WinEvent non varia tra le sorgenti
live e quelle basate su file: per recuperare gli eventi presenti in un
file è sufficiente utilizzare il parametro -Path, specificando uno o più
documenti su cui intervenire.
La sintassi di base di questa operazione è illustrata dal seguente
schema:
Get-WinEvent -Path <File 1> [, <File 2>, ...] [Filtri]

Per recuperare gli eventi di livello critico da un file precedentemente


generato da Event Viewer, per esempio, è sufficiente utilizzare uno
script come questo:
PS C:\Windows\system32> Get-WinEvent -Path .\Archive.evtx -FilterXPath '*/
System[Level=1]'
TimeCreated ProviderName Id Message
----------- ------------ -------
5/13/2010 12:05:06 PM Microsoft-Windows-EventSystem 4621 COM+ Event System:
impossi...
5/12/2010 7:45:34 PM SideBySide 33 Generazione del contesto d...
[...]

Gestire i log degli eventi


In alcuni scenari d’uso, poter recuperare le informazioni dal registro
eventi e reagire di conseguenza può non essere sufficiente. Spesso,
infatti, è necessario modificare le impostazioni di un log oppure
aggiungervi un nuovo evento. In questa sezione sono analizzati i
principali cmdlet che permettono di operare sui log degli eventi e, per
ciascuno di essi, sono presentati alcuni esempi di utilizzo.

New-EventLog
Il cmdlet New-EventLog consente di creare un nuovo log degli eventi
oppure di registrare una nuova sorgente associata a un log. Il nome
del nuovo log si specifica attraverso il parametro posizionale -LogName,
mentre il nome della sorgente da registrare è indicato per mezzo del
parametro -source, anch’esso posizionale.
Nel suo impiego di base, la sintassi d’uso di questo comando può
essere schematizzata in questo modo:
New-EventLog <Nome Log> <Nome sorgente>
In questo script, per esempio, si impiega il comando per registrare la
sorgente powershell.it all’interno del log Application (Applicazione,
nella localizzazione italiana):
PS C:\Users\ikmju> New-EventLog -Log Application -Source powershell.it

Una volta creati, il nuovo log o la nuova sorgente sono pronti all’uso,
sia per il recupero delle informazioni sia per l’inserimento di nuovi
eventi.
Mediante il parametro -computerName, infine, è possibile richiede al
cmdlet di creare il nuovo log o la nuova sorgente in uno o più PC
differenti da quello locale, utilizzando il protocollo DCOM/RPC per
comunicare con gli host remoti.

Write-EventLog
Il cmdlet Write-EventLog è in grado di aggiungere un nuovo evento a un
log, utilizzando la piattaforma di registrazione classica, senza
supporto esplicito per XML. Il log di destinazione deve essere già
esistente e si specifica attraverso il parametro posizionale -LogName; la
sorgente dell’evento, invece, è fornita tramite il parametro -Source,
anch’esso posizionale, mentre l’identificativo è indicato tramite il
parametro posizionale -EventId. Il testo dell’evento, poi, si fornisce
attraverso il parametro posizionale -Message, mentre con il parametro -
EntryType è possibile specificare il tipo di voce dell’evento, in base ai

valori illustrati nella Tabella 23.1. Alla stregua degli altri comandi
dedicati al log degli eventi, infine, utilizzando il parametro -ComputerName
è possibile richiede al cmdlet di aggiungere il nuovo evento a uno o
più PC differenti da quello locale, utilizzando il protocollo
DCOM/RPC per comunicare con gli host remoti.
La sintassi di base di questo comando si può schematizzare così:
Write-EventLog <Nome Log> <Nome sorgente> <ID evento> [Tipo voce]
<Messaggio> [-ComputerName <Computer 1> [, <Computer 2>, ...]]

Nello script che segue, per esempio, si aggiunge un nuovo evento al


log Application (Applicazione, nella localizzazione italiana),
registrandolo tramite la sorgente powershell.it creata nell’esempio
precedente:
PS C:\Users\ikmju> Write-EventLog Application powershell.it 12345
Information 'Test di evento

L’evento così creato è immediatamente disponibile sia tramite Event


Viewer sia attraverso i cmdlet di recupero analizzati in precedenza
nel capitolo.

Limit-EventLog
Questo comando è in grado di intervenire sulla configurazione di uno
o più log degli eventi, modificandone la dimensione massima e la
modalità di overflow dei dati. Attraverso il parametro posizionale -
LogName si indicano al cmdlet uno o più log su cui agire; il parametro -

MaximumSize permette di impostare la dimensione massima del log,

espressa in byte, mentre il parametro -RetentionDays indica il numero


minimo di giorni di permanenza di un evento nel log. Il parametro -
OverflowAction, infine, consente di specificare la modalità con cui i nuovi

eventi sono aggiunti al log una volta che questo ha raggiunto la


dimensione massima stabilita, secondo i valori illustrati nella Tabella
23.3.

Tabella 23.3 – I valori che può assumere il parametro -


OverflowAction.
Valore Descrizione
OverwriteAsNeede È il valore più comune. Impone al log di sovrascrivere gli eventi vecchi
d con quelli nuovi, partendo dal meno recente
Impone al log di sovrascrivere gli eventi vecchi con quelli nuovi, a patto
che i primi siano rimasti nel log almeno per il numero di giorni
OverwriteOlder
specificato da ‑RetentionDays; nel caso non vi siano elementi da
sovrascrivere, i nuovi eventi vengono ignorati
Impone al log di non sovrascrivere mai gli eventi passati e di ignorare i
DoNotOverwrite
nuovi

Nello script che segue, per esempio, si reimposta il log Application


(Applicazione, nella localizzazione italiana) affinché abbia una
dimensione massima di 20 MB e sovrascriva i vecchi eventi con i
nuovi:
PS C:\Users\ikmju> Limit-EventLog Application -MaximumSize 20MB
-OverflowAction OverwriteAsNeeded
Utilizzando il parametro -ComputerName, infine, è possibile indicare al
comando di portare a termine l’attività di configurazione a fronte di
uno o più PC remoti, indicandone il nome NetBIOS o l’indirizzo IP.

Remove-EventLog
Il cmdlet Remove-EventLog permette all’utente di eliminare una o più
sorgenti di registrazione di eventi oppure uno o più log degli eventi
da una o più macchine. La rimozione di questo tipo di oggetti è
un’attività molto rara, perciò l’impiego di questo comando è di
conseguenza poco frequente.
In modo simile a quanto avviene per New-EventLog, si può utilizzare il
parametro posizionale -LogName per fornire a questo comando il nome
di uno o più log su cui intervenire; il parametro -Source, inoltre,
consente di specificare uno o più sorgenti di evento da eliminare.
La sintassi d’uso del comando è:
Remove-EventLog <Log 1> [, <Log 2>, ...]
Remove-EventLog -Source <Sorgente 1> [, <Sorgente 2>, ...]

Nello script che segue si elimina la sorgente powershell.it definita in


precedenza nel capitolo:
PS C:\Users\ikmju> Remove-EventLog -Source powershell.it

Mediante il parametro -ComputerName, infine, è possibile indicare al


cmdlet una o più macchine remote su cui operare, utilizzando il
protocollo DCOM/RPC.
Parte V
Funzionalità avanzate
Internet e la shell

Una panoramica sugli strumenti messi a disposizione dalla


shell per interagire con altre macchine in rete, attraverso
alcuni tra i più diffusi protocolli di comunicazione: il
recupero di file e l’invocazione di web service
attraverso HTTP, la risoluzione di nomi a dominio
tramite DNS, la diagnosi mediante ping e l’invio di
nuove e-mail.

Nel tipico scenario aziendale odierno, la maggior parte dei computer


è collegata costantemente a Internet e scambia in modo regolare
informazioni con altre macchine; se fino agli inizi degli anni ’90
l’accesso alla rete era una prerogativa di un ristretto numero di
aziende, oggi si assiste all’evoluzione del modello SaaS (acronimo di
Software as a Service) e, in generale, alla conferma del fatto che il
cloud computing rappresenti il prossimo futuro del mercato IT.
Il ruolo di Internet – e del web, in particolare – deve dunque essere
preso in considerazione dal lettore che intenda avvalersi di questa
tecnologia all’interno di PowerShell: il framework Microsoft .NET,
d’altra parte, espone numerose classi create per usufruire di un
ampio numero di protocolli standard, come HTTP, HTTPS, FTP,
SMTP, SSL, ICMP e DNS.
Questo capitolo presenta alcuni tra i casi d’uso più frequenti per chi
utilizza Internet all’interno della shell e analizza i principali strumenti
messi a disposizione dal framework Microsoft.NET per raggiungere
questo obiettivo.

Utilizzare il protocollo ICMP


ICMP (acronimo di Internet Control Message Protocol) è un
protocollo utilizzato per trasmettere informazioni di diagnostica e
controllo tra i componenti di una rete, implementato in tutti i sistemi
operativi attualmente in commercio. Tra le applicazioni basate su
questo standard sono presenti ping e traceroute (tracert nei sistemi
Windows), che si occupano rispettivamente di verificare la
raggiungibilità di una macchina e di ricavare il percorso necessario ai
pacchetti IP per raggiungerla.
Nonostante nel framework Microsoft .NET siano presenti tutti gli
strumenti di base necessari per effettuare comunicazioni con ICMP
(e, in generale, con i socket), le funzionalità esposte ad alto livello
per questo protocollo sono limitate al ping.

Fare il ping di una macchina


Per sfruttare il supporto ping offerto dal framework Microsoft .NET è
sufficiente utilizzare il metodo d’istanza Send(; della classe
System.Net.NetworkInformation.Ping, fornendo l’indirizzo IP della macchina

da contattare oppure il nome DNS secondo queste sintassi:


<Istanza Ping>.Send(<Indirizzo IP>)
<Istanza Ping>.Send(<Nome DNS>)

Per fare il ping di una macchina, dunque, è possibile eseguire uno


script simile a questo:
PS C:\Users\ikmju> $pinger = New-Object Net.Networkinformation.Ping
PS C:\Users\ikmju> $pinger.Send("192.168.178.25";

Status : Success
Address : 192.168.178.25
RoundtripTime : 8
Options : System.Net.NetworkInformation.PingOptions
Buffer : {97, 98, 99, 100...}
Il metodo Send() ritorna un oggetto System.Net.Networklnformation. PingReply,
da cui è possibile recuperare il risultato del ping e la latenza (in
millisecondi).

Tabella 24.1 – Alcuni tra i valori più frequenti che la proprietà


Status di System. Net.NetworkInformation.PingReply può
assumere.
Valore Descrizione
Success Ping effettuato; le altre proprietà contengono dati validi
L’operazione ha superato il tempo massimo prestabilito
TimedOut
(timeout)
Il computer di destinazione è irraggiungibile, ma non se
DestinationUnreachable
ne conosce il motivo
DestinationNetworkUnreachable La rete di destinazione del computer è irraggiungibile
DestinationHostUnreachable Il computer di destinazione è irraggiungibile

Nella maggior parte dei casi, il risultato di un ping porta al successo


dell’operazione oppure a un timeout: il tempo massimo prestabilito
per il metodo Ping() è 5 secondi, ma utilizzando un ulteriore overload
messo a disposizione dalla classe è possibile indicare un tempo
differente, espresso in millisecondi. La sintassi è:
<Istanza Ping>.Send(<Indirizzo IP>, <Timeout>;
<Istanza Ping>.Send(<Nome DNS>, <Timeout>;

Con lo script che segue, per esempio, si fa un ping verso l’host


www.google.com con un timeout di 1 secondo; grazie a un blocco
condizionale, poi, si visualizza a video un messaggio appropriato:
$pingReply = $pinger.Send("www.google.com", 1000)

if ($pingReply.Status -eq 'Success')


{
"www.google.com ha risposto al ping in {0}ms" -f $pingReply.RoundtripTime
}
else
{
"www.google.com NON ha risposto al ping, causa: {0}" -f $pingReply.Status
}

Talvolta il servizio di risposta alle richieste di echo ICMP è disabilitato per questioni
NO
di sicurezza: in tal caso l’applicazione ping e il metodo omonimo appena illustrato
TA
ritornano sempre un timeout della richiesta.
Utilizzare il protocollo HTTP
Il protocollo HTTP (acronimo di HyperText Transfer Protocol) è uno
degli standard fondamentali su cui si basa lo scambio di informazioni
sul web. Nonostante sia utilizzato in gran parte per la navigazione
web, la struttura di HTTP permette lo scambio di qualsiasi tipo di
dato e oggi è molto frequente imbattersi in servizi erogati tramite
questo protocollo e totalmente privi di interfaccia.
Nel corso di questo paragrafo sono analizzate le principali tecniche
di impiego di questo protocollo all’interno della shell.

Download di file via HTTP


Per scaricare file dalla rete tramite questo protocollo si può usare la
potente classe System.Net.WebClient, che attraverso il metodo
DowniadFiie() permette di effettuare un download indicando l’URL di
interesse e il nome del file su cui memorizzare il documento
desiderato. La sintassi di questo metodo è semplice:
<Istanza WebClient>.DownloadFile(<Url>, <Nome file locale>)

In questo script, per esempio, si memorizza un documento


recuperato dal web all’interno di un file:
PS C:\Users\ikmju> $wc = New-Object Net.WebClient
PS C:\Users\ikmju> $wc.DownloadFile("http://www.powershell.it/", "HomePage.html")

Naturalmente, il documento memorizzato si può elaborare con i


consueti cmdlet:
PS C:\Users\ikmju> Get-Content .\HomePage.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.
w3.org/TR/xhtmll/DTD/xhtmll-transitional.dtd
">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="description" content="La community italiana di Windows
PowerShell. Offre articoli, tutorial, snippet
, un f
orum di discussione e la guida ai comandi di Windows PowerShell.">
[...]

Elaborazione di un feed RSS


I feed RSS (acronimo di Really Simple Syndication) sono documenti
XML che permettono di distribuire contenuti sul web, seguendo un
formato specifico. Nella gran parte dei casi, i feed RSS sono
recuperabili tramite protocollo HTTP, alla stregua di un qualsiasi altro
documento.
Per evitare di memorizzare su disco i documenti scaricati ma
elaborarli direttamente in memoria, è possibile utilizzare il metodo
DownloadString() di System-Net.Web-Client, che ritorna la stringa
corrispondente al documento specificato da un URL, seguendo
questo schema:
$dati = <Istanza WebClient>.DownloadString(<Url>)

Grazie a questo metodo e al supporto di PowerShell per il formato


XML, scaricare un feed RSS e recuperarne particolari porzioni
diventa un’operazione davvero semplice. Nello script che segue, per
esempio, si recupera il feed RSS del portale powershell.it e si
visualizza a video il titolo dei primi cinque contenuti:
PS C:\Users\ikmju> $wc = New-Object Net.WebClient
PS C:\Users\ikmju> $feed = [xml]$wc.DownloadString("http://www.powershell.
it/Feed/Ultimi-contributi/Rss.ashx")
PS C:\Users\ikmju> $feed.rss.channel.item I Select-Object Title -First 5

title
-----
News / Community Tour 2010 - Cesena, 28 maggio 2010
Snippet / Visualizzare l'output pagina per pagina
News / Community Tour 2009 - San Vito al Tagliamento (PN), 4 dicembre 2009
News / Sessione online gratuita su Data Protection Manager
Snippet / Controllare se un IP è nelle black list DNSBL

Il type accelerator [xml] potrebbe non funzionare correttamente con il metodo


DownloadString() se il feed RSS contiene i byte del preambolo della codifica
NO (BOM). In tal caso può essere utile memorizzare temporaneamente il file su disco
TA e utilizzare il cmdlet Get-content per leggerne il valore oppure affidarsi alle classi
del framework Microsoft .NET specializzate nella gestione degli stream testuali,
come System.IO.StreamReader.

Invocare un web service


I web service sono porzioni di codice richiamabili mediante il
protocollo SOAP (acronimo di Simple Object Access Protocol), uno
standard basato su XML che, nonostante non sia vincolato ad alcun
protocollo, è impiegato quasi esclusivamente attraverso HTTP.
Senza scendere nel dettaglio dell’argomento, al di fuori degli obiettivi
di questo libro, in questo breve paragrafo si illustra come sia
possibile invocare un web service tramite il metodo GET del
protocollo HTTP e le funzionalità messe a disposizione dal
framework Microsoft .NET.
Protagonista dell’attività è ancora una volta la classe
system.Net.webciient con il metodo Downloadstring(), al quale si indica un
URL composto in base ai parametri da fornire al web service
desiderato. Nell’esempio che segue si utilizza il web service
GeoIPService, offerto gratuitamente dal sito www.webservicex.net: tra le
operazioni supportate, il servizio ne espone una che ritorna le
informazioni geografiche relative a un indirizzo IP.
L’URL da richiamare per quest’operazione è composto come segue:
http://www.webservicex.net/geoipservice.asmx/GetGeoIP?IPAddress=<Indirizzo IP>

A fronte di una qualsiasi chiamata il web service risponde con un


documento XML che contiene l’informazione richiesta.
Nello script che segue, per esempio, si richiedono le informazioni
geografiche per un indirizzo IP e, tramite il supporto XML offerto da
PowerShell, si estrae dal documento il nome della nazione cui
l’indirizzo appartiene:
PS C:\Users\ikmju> $url = "http://www.webservicex.net/geoipservice.asmx/
GetGeoIP?IPAddress={0}"
PS C:\Users\ikmju> $wc = New-Object Net.WebClient
PS C:\Users\ikmju>
PS C:\Users\ikmju> $data = [xml]$wc.DownloadString($url -f "207.46.197.32")
PS C:\Users\ikmju> $data.GeoIP.CountryName
UNITED STATES

Molti web service rispondono solo a richieste effettuate tramite il metodo POST del
NO protocollo HTTP (anziché GET): in tal caso è necessario utilizzare le classi esposte
TA dal framework Microsoft .NET per effettuare richieste HTTP più complesse, come
System-Net-WebRequest.

Rispondere alle richieste HTTP


La shell non è limitata a utilizzare HTTP (o altri protocolli) solo come
client. Grazie alle numerose classi esposte dal framework Microsoft
.NET, è anche possibile creare script che agiscono come server. La
classe system.Net.HttpListener, per esempio, può essere facilmente
impiegata per creare un server HTTP che gestisca tutte le
connessioni indirizzate verso una particolare porta (o percorso) della
macchina locale.
Senza avere la pretesa di scendere nel dettaglio di ogni metodo o
proprietà utilizzata, lo script che segue vuole essere la dimostrazione
della facilità con cui è possibile creare un mini server web tramite
PowerShell, oltre a fungere da stimolo e da spunto di riflessione
sulle potenzialità di questa piattaforma:
# Creazione e avvio del listener HTTP (locale, porta 12345)

$httpListener = New-Object Net.HttpListener


$httpListener.Prefixes.Add("http://localhost:12345/")
$httpListener.Start()

# Ciclo di ricezione delle connessioni in entrata

while ($true)
{
# Recupero del contesto HTTP

$httpContext = $httpListener.GetContext()
$httpResponse = $httpContext.Response

# Aggiunta degli header allo stream di risposta HTTP

$httpResponse.Headers.Add("Content-Type", "text/html")
$httpResponse.Headers.Add("Server", "PowerShell/1.0")

# Scrittura del corpo dell'output

$writer = New-Object IO.StreamWriter($httpResponse.OutputStream, [Text.


Encoding]::UTF8)
$writer.Write('<html><body>Cosa fai qui? Corri a ')
$writer.Write('visitare <a href="http://www.powershell.it">powershell.it</
a>! :)')
$writer.Write('</body></html>')
$writer.Close()
}

# Chiusura del listener HTTP (comunque forzata da CTRL+Break)

$httpListener.Stop()

Questo script di esempio è stato pubblicato per la prima volta sul portale
NO
powershell.it e risponde a ogni richiesta pervenuta all’indirizzo http://localhost:
TA
1234 con un messaggio in formato HTML.

Interrogare il DNS
Per quanto riguarda il recupero di informazioni tramite il protocollo
DNS, il framework Microsoft .NET espone solo la classe System.Net.Dns,
in grado di effettuare interrogazioni DNS limitate ai record A, CNAME e
AAAA. Naturalmente, nel caso si rivelasse necessario utilizzare uno

strumento più evoluto è sempre possibile fare riferimento a librerie di


terze parti oppure impiegare le classi di gestione dei socket
comprese all’interno del framework stesso.

Risoluzione DNS
La risoluzione di un nome DNS si avvale del metodo GetHostEntry()
della classe sopra citata, che restituisce una struttura con la lista
degli indirizzi IP e degli eventuali alias relativi al nome specificato. La
sintassi di chiamata segue questo semplice schema:
<Istanza Dns>.GetHostEntry(<Nome DNS>)

Nello script che segue, per esempio, si recuperano alcune


informazioni sull’host www. microsoft.com dal server DNS configurato
nella macchina locale:
PS C:\Users\ikmju> [Net.Dns]::GetHostEntry("www.microsoft.com") | Format-List

HostName : Ibi.www.ms-akadns.net
Aliases : {}
AddressList : {207.46-170.10, 207.46.170.123, 65.55.12.249}

NO Nel caso l’host specificato sia relativo a un record CNAME, la proprietà HostName
TA ritorna il nome DNS del relativo record A (o AAAA). ).

Combinando tra loro le funzionalità di risoluzione DNS e il ping è


possibile creare un semplice script che calcola la latenza minima,
massima e media di tutti gli indirizzi IP di un particolare host, come
dimostra l’esempio che segue:
PS C:\Users\ikmju> $pinger = New-Object Net.NetworkInformation.Ping
PS C:\Users\ikmju> [Net.Dns]::GetHostEntry("www.google.com") |
>> Select-Object -Expand AddressList |
>> ForEach-Object { $pinger.Send($_) } |
>> Measure-Object -Max -Min -Ave RoundtripTime
>>

Count : 6
Average : 40.5
Sum :
Maximum : 45
Minimum : 38
Property : RoundtripTime

Inviare e-mail
Dalla versione 2.0 PowerShell include il cmdlet Send-MailMessage, in
grado di generare un’e-mail e di spedirla a un server SMTP,
gestendo in modo trasparente tutti i protocolli che intervengono in
questa attività.
Tramite il parametro -From si indica al comando l’indirizzo e-mail del
mittente, mentre con -TO si specificano i destinatari; l’oggetto e il
corpo del messaggio, invece, si forniscono rispettivamente tramite i
parametri -Subject e -Body. Il server SMTP da utilizzare, infine, è
specificato tramite il parametro -SmtpServer.
La sintassi di base del comando è dunque questa:
Send-MailMessage -From <Mittente> -To <Destinatariol> [, <Destinatario2>
...] -Subject <Oggetto> -Body <Messaggio> -SmtpServer <Server SMTP>

Per spedire un’e-mail da alice@example.com a bob@example.com utilizzando il


server SMTP mail.example.com, dunque, è sufficiente utilizzare uno
script simile a questo:
PS C:\Users\ikmju> Send-MailMessage -From salice@example.com -To bob@example.
com -Subject Ciao! -Body "Test di messaggio"
-SmtpServer mail - example - com

L’indicazione del server SMTP può essere omessa e in tal caso la


shell utilizza il valore presente nella variabile automatica $PSEmailServer,
che per comodità conviene impostare nel file del profilo della shell
(argomento trattato nel Capitolo 25).
Per produrre messaggi in formato HTML (utilizzando il tipo MIME
text/html) è sufficiente utilizzare lo switch -BodyAsHtml. L’oggetto e il

corpo del messaggio, inoltre, sono recepiti automaticamente come


testo semplice (tipo MIME text/piain) con codifica ASCII; se si
desidera cambiare quest’ultima è possibile agire sul parametro -
Encoding, specificando direttamente un’istanza della classe
System.Text.Encoding. La classe system.Text.Encoding stessa espone alcune

proprietà statiche che permettono il recupero delle codifiche più


utilizzate:
PS C:\Users\ikmju> [Text - Encoding] | Get-Member -MemberType Property -Static
| Select Name

Name
--
ASCII
BigEndianUnicode
Default
Unicode
UTF32
UTF7
UTF8

Per spedire un messaggio in formato HTML utilizzando il server


SMTP predefinito, includendo nel corpo del messaggio caratteri
come le lettere accentate italiane, dunque, ci si può avvalere di uno
script simile a questo:
PS C:\Users\ikmju> Send-MailMessage -From alice@example.com -To bob@example.
com -Subject "Un fisico del '900" -Body "<u>Emilio<u> <b>Segrè</b>-" -
BodyAsHtml -Encoding ( [Text - Encoding] : :UTF8)

NO La scelta della codifica UTF8 è dettata dalla preferenza di una codifica Unicode
TA che sia quanto più possibile snella (per i testi in lingua italiana).

Il cmdlet Send-MailMessage consente, inoltre, di allegare uno o più file al


messaggio desiderato, specificando tramite il parametro -Attachments la
sequenza dei nomi di file di interesse.
Con lo script che segue, si allega al messaggio spedito da Send-
MailMessage un file presente nella cartella di lavoro corrente:

PS C:\Users\ikmju> Send-MailMessage -From alice@example.com -To bob@example.


com -Subject "Esportazione" -Body "hello, world" -Attachments Export.xlsx
Interagire con l’host

Una panoramica sulle possibilità di personalizzazione


dell’interfaccia di PowerShell, sia a console testuale
sia grafica (ISE), e un’analisi sulle principali tecniche
che consentono di variare la modalità di interazione tra
la shell e l’utente.

Per quanto concerne la personalizzazione dell’host e dell’ambiente


di esecuzione, le shell Microsoft precedenti a PowerShell hanno
sempre sofferto di un supporto molto scarno e limitato alle
funzionalità di ANSI.SYS, il driver storico incluso nel sistema operativo
sin dai tempi di MS-DOS, in grado di riconoscere le sequenze di
escape ANSI digitate all’interno della console e, tra l’altro, di
modificare il colore del testo visualizzato.
Anche se, nel momento in cui questo libro viene scritto, ANSI.SYS è
incluso anche nelle versioni più recenti di Windows (Windows Server
2008 R2), l’utilizzo di questo componente è limitato alle applicazioni
a 16 bit (attraverso il layer di compatibilità incluso all’interno del
sistema) e all’interprete dei comandi CMD. La sintassi delle
sequenze di escape ANSI, inoltre, è particolarmente tediosa e
difficile da ricordare.
PowerShell, d’altra parte, consente di intervenire in qualsiasi
dettaglio della formattazione del testo visualizzato in maniera chiara
e semplice, agendo sugli oggetti del framework Microsoft .NET che
ogni host espone verso gli script che esegue. Come si è visto nel
Capitolo 1, la shell è utilizzabile a fronte di interfacce diverse,
realizzate da produttori differenti; ciascun host, tuttavia, garantisce il
supporto per un insieme minimo di funzionalità, così che qualsiasi
script possa essere facilmente trasferito da un prodotto all’altro
senza richiedere, tipicamente, alcuna modifica.
La natura orientata agli oggetti e fortemente incentrata sulla
reflection, inoltre, permette agli utenti della shell di esplorare
facilmente le eventuali funzionalità aggiuntive di cui l’host impiegato
dispone.
Questo capitolo analizza le principali funzionalità di interazione con
l’host esposte dall’interfaccia a console testuale di PowerShell e da
PowerShell ISE: le tecniche discusse sono comunque applicabili alle
interfacce di produttori diversi da Microsoft.

Personalizzare l’ambiente
L’ambiente di esecuzione di PowerShell può essere modificato
tramite script per riflettere le esigenze e lo stile dell’utente; la shell,
infatti, espone attraverso la variabile automatica $Host diversi metodi
e proprietà che consentono di intervenire con facilità sui colori
utilizzati per visualizzare a video il testo, sul titolo della finestra
dell’host e su altre impostazioni che regolano la permanenza delle
informazioni visualizzate nel buffer video.
Tra tutte le proprietà esposte da $Host, quella forse più interessante
per gli obiettivi di questo capitolo è UI, che espone diversi metodi e
comprende, a sua volta, la proprietà RawUI, che permette di accedere
ai dettagli esposti dall’host corrente. Come di consueto, l’impiego del
cmdlet Get-Member consente di ottenere una visuale rapida dei membri
di questi oggetti; ecco qual è l’output del comando all’interno della
console testuale:
PS C:\Users\ikmju> $Host.UI | Get-Member | Select-Object Name, MemberType

Name MemberType
---- ----------
Equals Method
GetHashCode Method
GetType Method
Prompt Method
PromptForChoice Method
PromptForCredential Method
ReadLine Method
ReadLineAsSecureString Method
ToString Method
Write Method
WriteDebugLine Method
WriteErrorLine Method
WriteLine Method
WriteProgress Method
WriteVerboseLine Method
WriteWarningLine Method
RawUI Property

PS C:\Users\ikmju> $Host.UI.RawUI | Get-Member | Select-Object Name, MemberType

Name MemberType
---- ----------
Equals Method
FlushInputBuffer Method
GetBufferContents Method
GetHashCode Method
GetType Method
LengthInBufferCells Method
NewBufferCellArray Method
ReadKey Method
ScrollBufferContents Method
SetBufferContents Method
ToString Method
BackgroundColor Property
BufferSize Property
CursorPosition Property
CursorSize Property
ForegroundColor Property
KeyAvailable Property
MaxPhysicalWindowSize Property
MaxWindowSize Property
WindowPosition Property
WindowSize Property
WindowTitle Property

Nel seguito del paragrafo sono analizzate le principali tecniche


tramite le quali è possibile personalizzare l’ambiente di esecuzione
della console testuale di PowerShell e di PowerShell ISE.

La console testuale
La classica console testuale di PowerShell permette di modificare i
colori utilizzati dall’host per visualizzare i testi modificando i valori
associati alle proprietà ForegroundColor e Backgroundcoior di $Host.UI.RawUI,
che corrispondono, rispettivamente al colore di primo piano e di
sfondo. I valori utilizzabili in ambedue i casi sono limitati per
questioni storiche ai i6 colori determinati dall’enumerazione system.
consoiecoior, che riflette la palette originariamente concepita per il
sistema grafico CGA. Nonostante questo limite sia stato superato a
partire da Windows Vista, gli elementi di System.Con.soleColor sono
rimasti invariati e in tutte le applicazioni a console testuale
compatibili con il framework Microsoft .NET (compresa la versione
4.0) il limite permane.
La variazione dei colori avviene solo per il testo visualizzato nella
finestra a console successivamente alle relative impostazioni e non
ha effetto sui dati pregressi: diversamente da quanto è possibile fare
con il tool a riga di comando COLOR all’interno di CMD, dunque, con
PowerShell è possibile visualizzare informazioni con qualsiasi
combinazione di colori tra quelli ammessi.
Nello script che segue, per esempio, si modifica lo stile della console
affinché visualizzi i testi dapprima utilizzando il colore rosso su
sfondo nero, poi bianco su sfondo magenta scuro (il predefinito della
shell):
PS C:\Users\ikmju> $Host.UI.RawUI.ForegroundColor = 'Red'
PS C:\Users\ikmju> $Host.UI.RawUI.BackgroundColor = 'Black'
PS C:\Users\ikmju> $Host.UI.RawUI.ForegroundColor = 'White'
PS C:\Users\ikmju> $Host.UI.RawUI.BackgroundColor = 'DarkMagenta'

Tabella 25.1 – I valori dell’enumerazione System.ConsoleColor.


Valore Descrizione
Black Nero
DarkBlue Blu scuro
DarkGreen Verde scuro
DarkCyan Ciano scuro
DarkRed Rosso scuro
DarkMagenta Magenta scuro
DarkYellow Giallo scuro
Gray Grigio
DarkGray Grigio scuro
Blue Blu
Green Verde
Cyan Ciano
Red Rosso
Magenta Magenta
Yellow Giallo
White Bianco

Nonostante la modifica dei colori tramite $Host.UI.RawUI consenta di


personalizzare la console, spesso è ancora più utile poter
evidenziare l’output dei propri script in maniera differente a seconda
delle informazioni visualizzate. Il cmdlet Write-Host, introdotto nel
Capitolo 4, dispone dei parametri -ForegroundColor e -BackgroundColor
pensati proprio per modificare il colore dell’host solo per l’output
visualizzato dal comando, senza rendere la modifica effettiva. I valori
accettati dai due parametri corrispondono a quelli già illustrati per le
omonime proprietà dell’oggetto $Host. UI.RawUI.
Nello script che segue, per esempio, si utilizza la pipeline e il cmdlet
Write-Host per differenziare l’output del cmdlet Get-ChildItem in base ai

dettagli su ciascun oggetto ritornato. Gli elementi il cui nome termina


con il testo .exe sono colorati di rosso, su sfondo nero; le cartelle
sono colorate di blu, mentre tutto il resto utilizza la normale
formattazione della shell.
Get-ChildItem | ForEach-Object {
switch ($_) {
{ $_ -like '*.exe' } { Write-Host $_ -ForegroundColor Red -
BackgroundColor Black }
{ $_.PSIsContainer } { Write-Host $_ -ForegroundColor Blue }
default { Write-Host $_ }
}
}

La shell permette di modificare anche il titolo della finestra della


console testuale: si tratta di una tecnica utilizzata spesso per
segnalare informazioni importanti all’utente, come il fatto che si stia
eseguendo PowerShell con i privilegi di amministrazione.
Per intervenire sul titolo è sufficiente utilizzare la proprietà WindowTitle
dell’oggetto $Host.UI.RawUI, come dimostrano questo script e la figura
correlata:
PS C:\Users\ikmju> $Host.UI.RawUI.WindowTitle = 'www.powershell.it'
Figura 25.1 - La modifica del titolo della finestra della console.

PowerShell ISE
L’ambiente grafico di PowerShell supporta tutte le opzioni utilizzabili
nella controparte testuale mediante l’oggetto $Host.UI.RawUI. Gli script
eseguiti all’interno di PowerShell ISE, inoltre, dispongono di
un’ulteriore variabile automatica, chiamata $psISE, mediante la quale
questo ambiente espone una ricca struttura a oggetti che permette di
recuperare e manipolare le diverse componenti dell’interfaccia.
La proprietà CurrentFile di $psISE ritorna svariate informazioni sul file
associato al tab corrente, se presente, come il nome completo del
documento attraverso la proprietà FullPath e la codifica utilizzata, per
mezzo della proprietà Encoding. Il metodo Save() e i suoi overload,
inoltre, permettono di memorizzare il file programmaticamente.
La proprietà Editor è con tutta probabilità la più importante tra quelle
esposte da currentFiie e consente, invece, di recuperare preziose
informazioni sul testo del file, sull’eventuale selezione e sulla
posizione del cursore: i metodi di Editor, infine, permettono di
intervenire facilmente sul contenuto del documento, sulla posizione
del cursore, sulle selezioni e sull’area visualizzata dall’interfaccia.
Nello script che segue, per esempio, si visualizzano le informazioni
relative al nome del file corrente e alla riga del cursore al suo
interno:
PS C:\Users\ikmju> $psISE.CurrentFiie.FullPath, $psISE.CurrentFiie.Editor.CaretLine
C:\Users\ikmju\Desktop\WmiExplorer.psl
97

In quest’altro, invece, si inserisce una nuova riga di codice all’interno


del documento, come prima riga:
PS C:\Users\ikmju> $psISE.CurrentFiie.Editor.SetCaretPosition(1, 1)
PS C:\Users\ikmju> $psISE.CurrentFiie.Editor.InsertText('# www.powershell.it1;

La proprietà options di $PSISE fornisce l’accesso a una moltitudine di


opzioni, principalmente legate alla formattazione dei testi all’interno
di PowerShell ISE. Mediante le proprietà OutputPaneForegorundColor e
OutputPaneBackgroundColor, per esempio, si possono variare
rispettivamente i colori di primo piano e di sfondo del pannello di
output dell’interfaccia utilizzando istanze della classe
System.Windows.Media.Color, che consente di impiegare un qualsiasi colore
della palette RGBA a 32 bit. Le proprietà FontName e FontSize, inoltre,
permettono di modificare il font utilizzato da PowerShell ISE nei
pannelli di script, di output e in quello dei comandi. Tramite la
proprietà TokenColors, poi, si possono addirittura cambiare i colori
impiegati dal sistema di colorazione del codice per ciascuna
categoria di elementi sintattici riconosciuti.
Il cmdlet Get-Member può essere utile, ancora una volta, per rendersi
conto delle funzionalità esposte dalla proprietà Options:
PS C:\Users\ikmju> $psISE- Options | Get-Member | Select-Object Name, MemberType

Name MemberType
---- ----------
PropertyChanged Event
Equals Method
GetHashCode Method
GetType Method
RestoreDefaults Method
RestoreDefaultTokenColors Method
ToString Method
CommandPaneBackgroundColor Property
CommandPaneUp Property
DebugBackgroundColor Property
DebugForegroundColor Property
DefaultOptions Property
ErrorBackgroundColor Property
ErrorForegroundColor Property
FontName Property
FontSize Property
OutputPaneBackgroundColor Property
OutputPaneForegroundColor Property
OutputPaneTextBackgroundColor Property
ScriptPaneBackgroundColor Property
ScriptPaneForegroundColor Property
SelectedScriptPaneState Property
ShowToolBar Property
ShowWarningBeforeSavingOnRun Property
ShowWarningForDuplicateFiles Property
TokenColors Property
UseLocalHelp Property
VerboseBackgroundColor Property
VerboseForegroundColor Property
WarningBackgroundColor Property
WarningForegroundColor Property

Nello script che segue, per esempio, si modifica il font utilizzato


dall’interfaccia e si cambia il colore con cui PowerShell ISE
evidenzia i commenti all’interno degli script:
PS C:\Users\ikmju> $psISE- Options -FontName = 'Brook 23'
PS C:\Users\ikmju> $psISE- Options -FontSize = 3C
PS C:\Users\ikmju> $psISE- Options -TokenColors['Comment' ] = '#FFFF0000'

In questo script, infine, si utilizza il metodo RestoreDefaults(), che riporta


le impostazioni di PowerShell ISE ai loro valori predefiniti:
PS C:\Users\ikmju> $psISE.Options.RestoreDefaults()

La proprietà CurrentPowerShe11Tab di $PsisE consente inoltre di intervenire


sul tab corrente e su alcuni aspetti interessanti dell’interfaccia. Al di
là della gestione del testo della barra di stato tramite la proprietà
StatusText, del titolo del tab mediante la proprietà DisplayName e di molti

altri dettagli, infatti, PowerShell ISE consente di personalizzare i


menu dell’interfaccia, aggiungendo nuove funzionalità: agendo sulla
proprietà AddOnsMenu, infatti, è possibile aggiungere e rimuovere voci di
menu custom al menu Add-Ons (Componenti aggiuntivi, nella
localizzazione italiana), visibile solo in presenza di almeno un sotto-
elemento.
Per ogni voce di menu di questo tipo è necessario specificare il testo
visualizzato, il blocco di script da eseguire e, opzionalmente, una
combinazione di tasti per l’avvio rapido.
Nello script che segue, si aggiunge una nuova voce di menu
powershell.it a PowerShell ISE, che se invocata porta all’apertura
del sito http://www.powershell.it nel browser web predefinito nel
sistema:
$psISE- CurrentPowerShe11Tab -AddOnsMenu-SubMenus-Add("powershell.it", {
Start-Process http://www.powershell.it
}, "Ctrl+Alt+F12")
Figura 25.2 - L’aggiunta di una nuova voce di menu alla finestra di PowerShell ISE.

Intervenire sulle funzionalità


La natura estensibile di PowerShell permette all’utente di variare
quasi ogni componente del prodotto, personalizzandone le
funzionalità. In questo paragrafo sono discusse alcune tra le più
importanti caratteristiche grazie alle quali è possibile modellare
l’ambiente di esecuzione della shell in base alle proprie esigenze e
rendere permanenti i cambiamenti introdotti.

Il profilo utente
Il profilo utente di PowerShell è uno script che la shell carica ed
esegue all’avvio di ogni sessione e prima dell’esecuzione di qualsiasi
altro comando, dove è possibile definire le opzioni della shell,
caricare snap-in e definire alias, funzioni, provider e variabili che si
desidera siano sempre disponibili.
Il nome e il percorso del file di profilo sono tipicamente determinati in
base all’utente che lancia la shell e all’interfaccia impiegata ma, in
linea di massima, ogni host può utilizzare regole differenti: per
qualsiasi prodotto, tuttavia, il nome del file di profilo è disponibile
attraverso la variabile automatica $PROFILE.
L’interfaccia a console testuale di PowerShell utilizza il file
Microsoft.PowerShell_profi-le.ps1, all’interno della cartella WindowsPowerShell

nei documenti dell’utente corrente. PowerShell ISE, invece, carica il


file Microsoft.PowerShellISE_profile.ps1, leggendolo dalla stessa cartella. In
entrambi i casi, qualora il file non esistesse – e questo è il caso
tipico, all’atto dell’installazione della shell – il sistema si limita a
procedere oltre.
Poiché il file del profilo è uno script esterno come tutti gli altri, per
modificarne il contenuto è sufficiente utilizzare un qualsiasi editor di
testo, come il Blocco note di Windows.
Una degli impieghi più frequenti del file di profilo consiste nel definire
versioni personalizzate delle funzionalità del prompt e del
completamento automatico dei comandi della shell, argomenti trattati
nel seguito di questo capitolo.

Customizzare il prompt
Il testo visualizzato dal prompt dei comandi della shell è determinato
dalla funzione prompt(), che nell’implementazione predefinita ritorna
alcune informazioni sull’ambiente corrente. Richiamandone la
definizione tramite la shell, infatti, è possibile osservarne lo script:
PS C:\Users\ikmju> $function:prompt
$(if (test-path variable:/PSDebugContext) { '[DBG]: ' } else { '' }) + 'PS
+ $(Get-Location) + $(if el -gè 1) { '>>' }) + '>

Poiché questa funzione, come qualsiasi altra, è ridefinibile


dall’utente, è possibile variarne il comportamento affinché ritorni
ulteriori informazioni a seconda delle necessità. Spesso, per
esempio, quando si opera con sistemi di revisione del codice da riga
di comando (come SubVersion o CVS) sarebbe utile poter
recuperare velocemente informazioni sulla copia di lavoro locale
(working copy): personalizzando la funzione prompt(; e disponendo
dei tool a riga di comando desiderati è possibile ridefinire il testo
visualizzato dal prompt della shell in maniera tale da riportare alcune
interessanti informazioni. Nello script che segue, per esempio, si
ridefinisce la funzione prompt() di PowerShell in maniera tale che
questa visualizzi il numero di file aggiunti, rimossi, modificati o non
revisionati della copia di lavoro gestita da SubVersion e individuata
dalla posizione corrente. Le informazioni sono recuperate tramite il
tool a riga di comando svn.exe, disponibile gratuitamente con il
pacchetto Apache SubVersion (http://subversion.apache.org).
function prompt()
{
# Se la cartella corrente non è una working copy di SubVersion ritorna il
prompt originale

if (-not (Test-Path ".svn")) {


'PS ' + $(Get-Location) + $(if ($nestedpromptlevel -ge 1) { '>>' }) + '> '
return
}

# Gestione output di SVN

$svnOutput = [xml](svn status --xml)

# Recupero il numero di elementi

$added = $svnOutput.SelectNodes("//wc-status[@item='added']").Count
$deleted = $svnOutput.SelectNodes("//wc-status[@item='deleted']").Count
$modified = $svnOutput.SelectNodes("//wc-status[@item='modified']").Count
$unversioned = $svnOutput.SelectNodes("//wc-
status[@item='unversioned']").Count
# Output manuale del testo del prompt, colorato nella sezione dedicata a
SubVersion

"PS {0} " -f $(Get-Location) |


Write-Host -NoNewLine
"[A:{1} D:{2} M:{3} ?:{0}]" -f $unversioned, $added, $deleted, $modified |
Write-Host -NoNewLine -ForegroundColor Yellow
"{0}>" -f $(if ($nestedpromptlevel -ge 1) { '>>' }) |
Write-Host -NoNewLine

return ' '


}

Dopo aver eseguito questo script, il prompt dei comandi di


PowerShell varia in base alla posizione corrente e, se questa è una
copia di lavoro di SubVersion, visualizza le informazioni desiderate
all’interno di una coppia di parentesi quadre, in colore giallo; cromia
a parte, in questa circostanza l’aspetto è simile a quanto riportato
qui:
PS C:\Development> cd .\MyProject
C:\Development\MyProject
PS C:\Development\Myproject [A:1 D:0 M:1 ?:3]>

Naturalmente, se lo script di ridefinizione di prompt() viene incluso nel


profilo di PowerShell, la funzionalità è sempre disponibile
automaticamente per ogni nuova sessione locale.

Customizzare il completamento automatico


In modo analogo al precedente, la funzionalità di completamento
automatico della shell, attivata con il tasto Tab , è delegata alla
funzione TabExpansion(), cui il sistema fornisce la riga completa e
l’ultima parola digitata dall’utente.
L’implementazione predefinita di questa funzione è complessa e
contiene diversi riferimenti al sistema della reflection del framework
Microsoft .NET. Tralasciando il contenuto originale, però, può essere
utile poter aggiungere al sistema il riconoscimento di un tool esterno
a riga di comando, in maniera tale che possa fornire
automaticamente i parametri supportati.
La funzione TabExpansion() consiste principalmente in un blocco switch
articolato e in un insieme di sotto-funzioni di appoggio.
Lo schema, rivisto per una maggiore leggibilità, è il seguente:
function TabExpansion($line, $lastWord)
{
switch -regex ($lastWord) {
'<Espressione1>' { <Azione1> }
'<Espressione2>' { <Azione2> }
'<Espressione3>' { <Azione3> }
...
}
}

Come si può intuire, aggiungendo una diramazione al blocco switch


principale della funzione è possibile introdurre il supporto per un
nuovo comando esterno.
Ritornando alla sezione precedente e al pacchetto SubVersion, per
esempio, è possibile fare in modo che la shell riconosca il tool
esterno svn e ne proponga tramite completamento automatico i
comandi supportati. Nell’esempio che segue, dunque, si ridefinisce
la funzione TabExpansion() in modo tale che reagisca alla presenza del
nome del tool tramite un’espressione regolare e filtri i comandi da
ritornare in base all’eventuale testo aggiuntivo immesso dall’utente:
function TabExpansion($line, $lastWord)
{
# Alcuni comandi di SubVersion

$svnCommands = 'add',
'blame',
'cat',
'changelist',
'checkout',
'cleanup'

if ($line -match 'svn\s+(\S*)?$') {


$filter = $matches[1]

if ($filter) {
# Comando parziale
$svnCommands |
Where-Object { $_.StartsWith($filter) } |
Sort-Object
}
else {
# Nessun comando

$svnCommands
}
}
}

La funzione presentata ha uno scopo puramente didattico, non è completa di tutti i


comandi di SubVersion e contiene un’espressione regolare non adatta a tutte le
NO
circostanze. Non comprende, infine, il blocco di completamento automatico
TA
predefinito, la cui assenza preclude la funzionalità per le istruzioni normalmente
riconosciute da PowerShell.

Anche in questo caso, aggiungendo la definizione personalizzata di


TabExpansion() al profilo utente della shell si rende disponibile questa
funzione all’interno di tutte le nuove sessioni locali.
Job in background

Una guida alla configurazione di WinRM e un’analisi


dei processi in background e del loro ciclo di vita, con
un approfondimento di ogni cmdlet che la shell mette a
disposizione per crearli e gestirli, dall’avvio al recupero
dei risultati.

A partire dalla versione 2.0, PowerShell è in grado di gestire


l’esecuzione di più attività in parallelo, permettendo all’utente di
definire con facilità blocchi di script da eseguire in background, in
modo indipendente dalla sessione corrente.
Questa funzionalità, nota come supporto per i job (o, anche,
processi) in background, colma il gap tecnologico presente nelle
precedenti shell Microsoft, dove l’infrastruttura dedicata a questo tipo
di operazioni era estremamente limitata, e adotta – seppur solo
concettualmente – il paradigma di elaborazione in background
presente all’interno delle moderne shell *nix, facendolo evolvere in
modo tale da supportare il modello a oggetti di Microsoft .NET.
Nel seguito di questo capitolo sono analizzate le tecniche di
creazione e di gestione dei job in background, limitatamente
all’esecuzione di script nella macchina locale. Il prossimo capitolo,
inoltre, riprende questo tema dal punto di vista dell’esecuzione di job
in remoto e illustra, in generale, le diverse possibilità offerte da
PowerShell per eseguire attività su macchine diverse da quella
locale.

I job
I job in background – da qui in avanti, per semplicità, job – sono
script di PowerShell eseguiti in sessioni indipendenti da quella
corrente, all’interno di nuovi processi della shell. L’esecuzione di ogni
job avviene in parallelo rispetto agli altri e alla sessione corrente,
consentendo all’utente di organizzare i propri script affinché il tempo
necessario all’esecuzione sia minimizzato e, complessivamente,
aumenti il grado di automazione dell’intervento. Come si può intuire,
esiste un forte isolamento tra la sessione corrente e quelle create
per i job, ma il sistema permette comunque alla prima di gestire le
seconde e di ottenerne gli eventuali risultati prodotti, attraverso
alcuni cmdlet specifici cui è dedicato il seguito del capitolo.
L’intera infrastruttura dei job si appoggia sul servizio Windows
Remote Management (Gestione remota Windows, nei sistemi in
lingua italiana), più noto con il semplice acronimo WinRM, che, a
dispetto del nome fuorviante, si occupa di gestire questo tipo di
attività sia per i job remoti sia per quelli locali, in modo centralizzato
e indipendente dall’utente connesso. Come anticipato nei capitoli
precedenti, WinRM è l’implementazione Microsoft del protocollo WS-
Management e permette lo scambio di informazioni – tra cui i dati
relativi ai job – tramite il protocollo SOAP (Simple Object Access
Protocol), sulla base di HTTP o HTTPS.
La presenza e la corretta configurazione di WinRM, pertanto, sono
requisiti fondamentali per il supporto dei job in background.

La configurazione di WinRM
L’installazione di WinRM varia in base al sistema operativo di
destinazione ed è illustrata nel Capitolo 1. In questo paragrafo,
pertanto, ci si limita ad analizzare la configurazione di questa
piattaforma dando per scontato che il pacchetto sia già presente nel
sistema.
Per utilizzare il supporto offerto da WinRM è necessario che il
relativo servizio sia avviato, che siano stati registrati correttamente
gli endpoint utilizzati dalla piattaforma per ricevere comandi tramite il
protocollo SOAP e che siano stati configurati senza errori i permessi
necessari. WinRM è una tecnologia indipendente da PowerShell e,
come ci si potrebbe aspettare, è possibile procedere alla
configurazione di ogni elemento richiesto tramite un’interfaccia
grafica. Il pacchetto d’installazione di WinRM, inoltre, include il tool a
riga di comando winrm.cmd che, interfacciandosi con l’oggetto COM
preposto alla gestione di questa piattaforma, ne permette una
semplice ma completa configurazione.
All’interno di PowerShell, tuttavia, per configurare WinRM è
sufficiente utilizzare il cmdlet Set-WSManQuickConfig, che prima di
proseguire visualizza a video un riepilogo delle attività da portare a
termine e chiede conferma all’utente, come illustrato nel blocco che
segue:
PS C:\Windows\system32> Set-WSManQuickConfig

Configurazione rapida di Gestione remota Windows


L'esecuzione del comando Set-WSManQuickConfig ha implicazioni significative per la
sicurezza, poiché attiva la gestione
remota tramite il servizio Gestione remota Windows in questo computer.
Questo comando:
1. Controlla se il servizio WinRM è in esecuzione. Se non è in esecuzione, il
servizio viene avviato.
2. Imposta il tipo di avvio del servizio Gestione remota Windows su automatico.
3. Crea un listener per accettare richieste in qualsiasi indirizzo IP. Per
impostazione predefinita, il trasporto è HTTP.
4. Attiva un'eccezione firewall per il traffico di WS-Management .
Attivare la gestione remota tramite il servizio WinRM in questo computer?
[S] Sì [N] No [O] Sospendi [?] Guida (il valore predefinito è "S"): S

Gestione remota Windows: aggiornamento per la ricezione di richieste completato.


Servizio Gestione remota Windows avviato.

Gestione remota Windows: aggiornamento per la gestione remota completato.


Eccezione firewall per Gestione remota Windows attivata.

NO WSMan, abbreviazione di WS-Management, è un termine utilizzato spesso i per


TA indicare l’implementazione Microsoft del protocollo WinRM.

In ogni caso, per poter agire sulla configurazione di questo


componente è necessario disporre dei privilegi necessari e, nei
sistemi che prevedono l’UAC, eseguire i comandi come
amministratore.
Il cmdlet Set-WSManQuickConfig esegue queste operazioni:
• avvia il servizio Windows Remote Management (Gestione
remota Windows), se questo non è in esecuzione;
• imposta l’avvio automatico per tale servizio;
• aggiunge le prenotazioni necessarie per alcuni nuovi listener
tramite il driver http.sys;
• aggiunge un’eccezione alle regole del firewall di Windows per i
listener creati.
Nonostante il protocollo di comunicazione sia basato su HTTP e
HTTPS, WinRM utilizza le porte registrate presso l’organizzazione
IANA per il servizio WS-Management:
• TCP/UDP 5985, wsman (HTTP);
• TCP/UDP 5986, wsmans (HTTPS).
Per verificare che la configurazione di WinRM sia avvenuta con
successo è possibile utilizzare il cmdlet Test-WSMan, che riporta in tal
caso alcuni dettagli come la versione del protocollo utilizzato e il
produttore dello stack:
PS C:\Users\ikmju> Test-WSMan

wsmid :
http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor : Microsoft Corporation
ProductVersion : OS: 0.0.0 SP: 0.0 Stack: 2.0

L’eccezione aggiunta nel firewall di Windows da Set-WSManQuickConfig potrebbe


NO
non essere adeguata alle policy di sicurezza del sistema di destinazione e si
TA
consiglia di controllarne le impostazioni.

Il ciclo di vita dei job


All’interno della shell, ogni job corrisponde un oggetto della classe
System-Management.Automation.PSRemotingJob e tutti i cmdlet preposti alla

gestione dei job ritornano o accettano oggetti di questo tipo. Il ciclo


di vita dei job, inoltre, è determinato dalle operazioni effettuate sugli
oggetti corrispondenti e termina automaticamente con la chiusura
della sessione corrente (o l’uscita da PowerShell).
A prescindere dal meccanismo di creazione, tutti i job creati Nella
sessione corrente sono mantenuti in una lista cui i cmdlet possono
accedere, in maniera tale da facilitare la gestione di questi oggetti.

L’avvio
Per creare e avviare un nuovo job è sufficiente utilizzare il cmdlet
Start-Job, fornendo tramite il parametro posizionale -ScriptBlock il blocco
di script da eseguire.
Nel suo impiego più semplice, dunque, questo comando utilizza la
sintassi che segue:
Start-Job { <Comandi> }

Una volta eseguito, questo cmdlet si occupa di contattare il servizio


WinRM della macchina locale e di lanciare un nuovo processo di
PowerShell in background, eseguendo al suo interno lo script fornito.
A prescindere dalla complessità dello script e dal tempo necessario
a portarlo a termine, l’invocazione di Start-Job avviene quasi
istantaneamente e il cmdlet ritorna alla shell un nuovo oggetto
System.Management.Automation.PSRemotingJob che rappresenta il job appena

creato.
Nello script che segue, per esempio, si crea un nuovo job, tramite il
quale si emette una stringa nella pipeline:
PS C:\Users\ikmju> Start-Job { Write-Host "hello, world" }

Id Name State HasMoreData Location Command


-- ---- ----- ----------- -------- -------
235 Job235 Running True localhost Write-Host "hello, world"

Gli oggetti della classe System.Management.Automation.PSRemotingJob


espongono diverse proprietà che consentono di indagare sul job e
sul suo stato di esecuzione. La Tabella 26.1 elenca le principali.

Tabella 26.1 - Le principali proprietà della classe


System.Management.Automation. PSRemotingJob.
Proprietà Descrizione
Command Il testo del comando fornito tramite -ScriptBlock
Indica che il job può restituire nuovi dati prodotti dallo
HasMoreData
script
L’identificativo numerico univoco del job all’interno della
Id
sessione corrente
Il nome o l’indirizzo della macchina presso la quale il job è
Location
in esecuzione
Il nome descrittivo del job, utilizzabile per facilitarne la
Name
gestione
Contiene un’indicazione sullo stato del job e può
State
assumere uno dei valori di Tabella 26.2

Tabella 26.2 - I valori che può assumere la proprietà State.


Valore Descrizione
NotStarte
Il job non è ancora stato avviato
d
Running Il job è in esecuzione
Completed L’esecuzione del job è stata portata a termine
Failed Il job è terminato con un errore
Stopped L’esecuzione del job è stata interrotta
L’esecuzione del job è bloccata, in attesa di input da parte
Blocked
dell’utente

In aggiunta al passaggio di un blocco di script, il cmdlet Start-Job è in


grado di creare un job anche a partire da un file di script esterno: in
tal caso ci si può avvalere del parametro posizionale -FilePath; si
possono infine indicare gli argomenti da fornire allo script esterno
mediante il parametro -ArgumentList, che accetta un array di oggetti
qualsiasi.
La sintassi del cmdlet, dunque, può essere ampliata secondo questo
schema:
Start-Job { <Comandi> }
Start-Job <Script esterno> [-ArgumentList <Argl>, <Arg2>, ...]

Nello script che segue, per esempio, si avvia un nuovo job in


background a partire dallo script esterno Test.psl, fornendo come
unico argomento un indirizzo IP:
PS C:\Users\ikmju> Start-Job .\Test.ps1 -ArgumentList 207.46.19.254

Id Name State HasMoreData Location Command


-- ---- ----- ----------- -------- -------
253 Job253 Running True localhost param([string] $ipAddress)...

PS C:\Users\ikmju> Get-Content .\Test.ps1


param([string] $ipAddress)
{
$dnsblZones = 'dnsbl.ahbl.org',
'b.barracudacentral.org',
[...]

Come si può notare dall’output dell’esempio, il comando del job


(proprietà Command) non è il nome del file di script esterno, bensì il
contenuto del file stesso, recuperato prima di creare il job.

Il recupero dei risultati


Come anticipato nel paragrafo precedente, l’avvio di un job tramite il
cmdlet Start-Job non ritorna direttamente i risultati emessi dallo script
di interesse, ma genera un oggetto tramite il quale è possibile
recuperare informazioni relative al nuovo job e variarne il ciclo di
vita.
Qualsiasi informazione emessa dai comandi del job, sia che si tratti
di oggetti emessi nella pipeline sia che si tratti di errori, non è
trasferita immediatamente dal processo del job alla sessione
corrente, ma transita attraverso il servizio WinRM: come in una sorta
di sistema idraulico, infatti, ogni informazione prodotta dal job è
inviata a WinRM, che la memorizza temporaneamente in un buffer di
memoria. Successivamente, richiamando il cmdlet Receive-Job dalla
sessione corrente è possibile contattare WinRM e prelevare i dati
immagazzinati nel buffer di memoria, per poi elaborarli; una volta
effettuato il prelievo, questo buffer di memoria è automaticamente
svuotato e pronto per accogliere nuove informazioni, nel caso se ne
dovessero accumulare di nuovo.
Questo ciclo di recupero dei dati può continuare fino a quando il job
non ha portato a termine le proprie attività o non viene terminato.
È possibile indicare al cmdlet Receive-Job il job di interesse attraverso
uno dei parametri posizionali -Job, -Id e -Name, specificando,
rispettivamente, l’oggetto restituito da Start-Job, l’identificativo
numerico oppure il nome descrittivo. In quest’ultimo caso, poiché a
un nome descrittivo potrebbero fare capo più job, il cmdlet restituisce
l’unione dei risultati degli elementi individuati.
La sintassi di base del comando è:
Receive-Job <Oggetto job1> [, <Oggetto job1>, ...]
Receive-Job <Identificativo1> [, <Identificativo2>, ...]
Receive-Job <Nome1> [, <Nome2>, ...]

Come ci si potrebbe aspettare, il cmdlet Receive-Job è in grado anche


di prelevare molti dei propri parametri direttamente dalla pipeline. Tra
gli impieghi più frequenti c’è il passaggio di oggetti di tipo
System.Management.Automation.PSRemotingJob, secondo questa semplice
sintassi:
<Oggetto job1> [, <Oggetto job1>, ...] | Receive-Job

Nello script che segue, per esempio, si esegue un nuovo job che
ritorna il nome del computer locale e si recupera il risultato:
PS C:\Users\ikmju> $job = Start-Job { $env:COMPUTERNAME }
PS C:\Users\ikmju> $job | Receive-Job
ALKAID

Come anticipato, d’altra parte, l’istante in cui i risultati del job sono
disponibili non è esattamente prevedibile, perché il flusso delle
informazioni è asincrono: per quanto lo script del job dell’esempio
precedente sia semplice, infatti, nulla assicura che la successiva
chiamata a Receive-Job ritorni effettivamente il risultato sperato. In tal
caso è necessario verificare la proprietà HasMoreData del job: quando
equivale a $true significa che Receive-Job potrebbe ritornare dei dati; in
caso contrario il job non è più attivo e le informazioni prodotte sono
già state prelevate.
Il lettore noti che, come anticipato, le informazioni ricevute da un job
transitano attraverso WinRM e, per tale ragione, possono essere
recuperate un po’ alla volta: generalmente conviene leggere i risultati
dei job all’interno di un ciclo, fino a quando la proprietà HasMoreData del
job rimane pari a $true.
Nello script che segue, per esempio, si ricavano i risultati di un job
che recupera i processi in esecuzione tramite un ciclo, come
descritto in precedenza:
PS C:\Users\ikmju> $job = Start-Job { Get-Process }
PS C:\Users\ikmju> while ($job.HasMoreData) {
>> $job | Receive-Job
>> }
>>

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
62 2 924 3468 29 1380 audiodg
33 2 1532 3140 34 0,13 2676 conime
[...]

L’attesa del completamento


Per attendere il completamento di un job si può usare il cmdlet Wait-
Job, cui è possibile fornire il job di interesse attraverso uno dei

parametri posizionali -Job, -Id e -Name, specificando, rispettivamente,


l’oggetto restituito da Start-Job, l’identificativo numerico oppure il nome
descrittivo. Come per Receive-Job, anche per questo cmdlet è possibile
specificare più di un job di destinazione e in tal caso il comando
attende la conclusione di tutti i job specificati. Il passaggio dei
riferimenti, inoltre, può avvenire tramite pipeline.
La sintassi d’uso più semplice di Wait-Job, dunque, segue questo
schema:
Wait-Job <Oggetto job1> [, <Oggetto job1>, ...]
Wait-Job <Identificativo1> [, <Identificativo2>, ...]
Wait-Job <Nome1> [, <Nome2>, ...]
<Oggetto job1> [, <Oggetto job1>, ...] | Wait-Job

Nello script che segue, per esempio, si attende il completamento del


job illustrato nell’esempio precedente e, in seguito, se ne recuperano
i risultati. In questo caso non è necessario un ciclo, perché il cmdlet
Wait-Job attende il completamento del job in seguito al quale non

possono più essere generate nuove informazioni.


PS C:\Users\ikmju> $job = Start-Job { Get-Process }
PS C:\Users\ikmju> $job | Wait-Job

Id Name State HasMoreData Location Command


-- ---- ----- ----------- -------- -------
15 Job15 Completed True localhost Get-Process

PS C:\Users\ikmju> $job | Receive-Job

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
62 2 924 3468 29 1380 audiodg
33 2 1532 3144 34 0,14 2676 conime
402 5 1612 4876 105 484 csrss
72 4 1320 4484 101 528 csrss
[...]

Come si può intuire dall’output dell’esempio, il cmdlet Wait-Job emette


a sua volta l’oggetto System.Management.Automation.PSRemotingJob di riferimento
all’interno della pipeline, peculiarità che rende possibile un’eventuale
costruzione di pipeline a cascata sul job.
Il comando accetta inoltre il parametro -Timeout, tramite il quale è
possibile indicare un tempo massimo di attesa previsto, espresso in
secondi. Il cmdlet ritorna al prompt allo scadere di questo intervallo o
al completamento dei job indicati. Nel caso in cui tutti i job indicati
siano completati nell’intervallo specificato da -Timeout, il cmdlet Wait-Job
ne emette i relativi oggetti nella pipeline, come negli scenari descritti
in precedenza: in caso contrario, invece, il cmdlet non emette nulla.
In questo script, per esempio, si creano due job che utilizzano il
cmdlet Start-Sleep per sospendere l’esecuzione per, rispettivamente, 2
e 3 secondi. Poi si attende la chiusura di entrambi per un tempo
massimo di 10 secondi, mostrando a video i job completati:
PS C:\Users\ikmju> $job1 = Start-Job { Start-Sleep 2 }
PS C:\Users\ikmju> $job2 = Start-Job { Start-Sleep 3 }
PS C:\Users\ikmju> $job1, $job2 | Wait-Job -Timeout 10

Id Name State HasMoreData Location Command


-- ---- ----- ----------- -------- -------
25 Job25 Completed False localhost Start-Sleep 2
27 Job27 Completed False localhost Start-Sleep 3

In quest'altro script, invece, si porta la sospensione del secondo job a 30


secondi. Come si può notare dall'output, Wait-Job non ritorna in questo caso
alcun oggetto, nonostante uno dei due job – il primo – sia completato.

PS C:\Users\ikmju> $job1 = Start-Job { Start-Sleep 2 }


PS C:\Users\ikmju> $job2 = Start-Job { Start-Sleep 30 }
PS C:\Users\ikmju> $job1, $job2 | Wait-Job -Timeout 10
PS C:\Users\ikmju> $job1.State, $job2.State
Completed
Running

L’esecuzione di Wait-Job senza specificare alcun parametro, infine,


porta all’attesa del completamento di tutti i job creati nella sessione
corrente.

Oggetti deserializzati
Come anticipato nella prima parte del capitolo, il flusso di
informazioni prodotte dai job e recuperate tramite il cmdlet Receive-Job
viaggia attraverso il protocollo WS-Management/WinRM. Poiché si
tratta di un protocollo basato su XML, PowerShell utilizza una
tecnica nota come serializzazione per produrre rappresentazioni
molto accurate degli oggetti originali. D’altra parte, alcune
informazioni sono di pertinenza esclusiva della macchina di origine –
come per esempio l’identificativo di un processo o un SID (acronimo
di Security Identifier) di un utente locale - e possono avere un valore
limitato, giunte a destinazione. I metodi dell’oggetto di origine,
invece, potrebbero non avere una corrispondenza nella macchina di
destinazione e per questo sono omessi dalla rappresentazione
utilizzata da WinRM per descrivere gli oggetti.
Dato che queste limitazioni non sono compatibili con il resto
dell’ecosistema di oggetti del framework Microsoft .NET, quando
Receive-Job ricostruisce gli oggetti provenienti da WinRM all’interno di

PowerShell - un processo noto come deserializzazione - per ogni


tipo non primitivo ne crea uno nuovo, residente nella memoria della
sessione corrente della shell, che mantiene il nome del primo ma al
cui namespace è prefissata la stringa Deserialized.
Gli oggetti non primitivi deserializzati hanno le stesse proprietà degli
originali, ma differiscono da questi anche nell’assenza di metodi
della classe e nella genealogia di appartenenza, che viene
soppressa.
Come si può notare dallo script che segue, per esempio, gli oggetti
deserializzati da un job che richiama il cmdlet Get-process non sono di
tipo System-Diagnostics. Process bensì di tipo
Deserialized.System.Diagnostic.Process e non dispongono deimetodi
contenuti nella prima classe:
PS C:\Users\ikmju> $job = Start-Job { Get-Process expl* }
PS C:\Users\ikmju> Receive-Job $job | Get-Member

TypeName: Deserialized.System.Diagnostics.Process

Name MemberType Definition


---- ---------- ----------
ToString Method string ToString(), string
ToString(string format, System.IFormatProvider for...
Company NoteProperty System.String Company=Microsoft
Corporation
CPU NoteProperty System.Double CPU=45.6770928
Description NoteProperty System.String Description=Windows
Explorer
FileVersion NoteProperty System.String
FileVersion=6.1.7600.16385 (win7_rtm.090713-1255)
Handles NoteProperty System.Int32 Handles=1050

Come si evince dall’output dell’esempio precedente, inoltre, il


sistema ETS di PowerShell riconosce automaticamente la maggior
parte dei tipi deserializzati in questo modo e supporta le proprietà
aggiuntive dei tipi originali, gestendo le medesime visualizzazioni.
I tipi primitivi deserializzati, invece, non subiscono il processo di
ricostruzione descritto per i tipi complessi e mantengono sia il
namespace originale sia i propri membri, inclusi i metodi, come
dimostra lo script che segue:
PS C:\Users\ikmju> $job = Start-Job { [Math]::Sin([Math]::PI * 2) }
PS C:\Users\ikmju> Receive-Job $job | Get-Member

TypeName: System.Double

Name MemberType Definition


---- ---------- ----------
CompareTo Method int CompareTo(System.Object value), int
CompareTo(double value)
Equals Method bool Equals(System.Object obj), bool Equals(double
obj)
[...]

Sia per i tipi primitivi sia per quelli che non lo sono, infine, durante il
processo di deserializzazione l’ETS di PowerShell aggiunge a ogni
oggetto le proprietà PSComputerName e Runspaceld, utilizzabili dall’utente
per determinare quale sia la macchina (e la sessione) di provenienza
dell’elemento.

La gestione
La gestione dei job in PowerShell è così semplice che a questa
attività è dedicato un unico cmdlet, Get-Job. Per recuperare i job di
interesse è sufficiente fornire a questo comando gli identificativi
numerici o i nomi descrittivi degli elementi cercati, attraverso i
parametri posizionali -Id o -Name. In entrambi i casi, d’altra parte, il
binding tramite pipeline dei parametri avviene solo per nome di
proprietà.
La sintassi d’uso più semplice del comando è:
Get-Job -Id <Identificativo1> [, <Identificativo2>, ...]
Get-Job -Name <Nome1> [, <Nome2>, ...]
Se non si specifica alcun parametro, il cmdlet ritorna tutti i job creati
nella sessione corrente.
Nello script che segue, per esempio, si può constatare come il
cmdlet Get-job restituisca solo i job con il nome descrittivo specificato,
omettendo il terzo elemento creato:
PS C:\Users\ikmju> $job1 = Start-Job -Name Test { Write-Host "hello" }
PS C:\Users\ikmju> $job2 = Start-Job -Name Test { Write-Host "world" }
PS C:\Users\ikmju> $job3 = Start-Job { Write-Host "bye bye" }
PS C:\Users\ikmju>
PS C:\Users\ikmju> Get-Job -Name Test

Id Name State HasMoreData Location Command


-- ---- ----- ----------- -------- -------
65 Test Completed True localhost Write-Host "world"
63 Test Completed True localhost Write-Host "hello"

Continuando l’esempio precedente, quindi, una chiamata a Get-Job


priva di parametri ritorna tutti i job creati nella sessione corrente:
PS C:\Users\ikmju> Get-Job

Id Name State HasMoreData Location Command


-- ---- ----- ----------- -------- -------
67 Job67 Completed True localhost Write-Host "bye bye"
65 Test Completed True localhost Write-Host "world"
63 Test Completed True localhost Write-Host "hello"

Il cmdlet Get-Job può ritornare solo i job creati nella sessione corrente, ?51 anche
NO
se nella macchina locale sono attive altre sessioni di PowerShell gestite tramite
TA
WinRM.

Interruzione ed eliminazione
Per interrompere l’esecuzione di un job è sufficiente utilizzare il
cmdlet Stop-Job, individuando l’elemento (o gli elementi) di interesse
attraverso i consueti parametri posizionali -Job, -Id e -Name, fornendo,
rispettivamente, l’oggetto restituito da Start-Job, l’identificativo
numerico oppure il nome descrittivo. Il cmdlet accetta anche gli
oggetti su cui si desidera operare direttamente tramite pipeline.
La sintassi d’uso più semplice, pertanto, è rappresentata da questo
schema:
Stop-Job <Oggetto job1> [, <Oggetto job2>, ...]
Stop-Job <Identificativo1> [, <Identificativo2>, ...]
Stop-Job <Nome1> [, <Nome2>, ...]
<Oggetto job1> [, <Oggetto job2>, ...] | Stop-Job
Lo script che segue, per esempio, interrompe l’esecuzione di tutti i
job in background avviati nella sessione corrente:
PS C:\Users\ikmju> Get-Job | Stop-Job

Di default, questo comando non emette alcun oggetto nella pipeline;


per forzare l’emissione degli oggetti forniti in input (pratica utile
durante l’eliminazione dei job, descritta in seguito), è necessario
specificare lo switch -PassThru.
L’interruzione di un job, d’altra parte, non ne elimina né il relativo
processo né la relativa sessione, gestita da WinRM; per eliminare
del tutto il job e liberare le risorse impegnate è necessario usare il
cmdlet Remove-Job, specificando i job di interesse attraverso i consueti
parametri, comuni alla maggior parte dei comandi descritti in questo
capitolo. Il parametro posizionale -Job consente di fornire uno o più
oggetti Job al cmdlet; -Id permette di specificare uno o più
identificativi; con -Name si fornisce al comando il nome descrittivo degli
oggetti. Alla stregua di quanto avviene per gli altri comandi, infine, il
cmdlet accetta gli oggetti job da eliminare anche tramite la pipeline.
La sintassi d’uso più semplice di Remove-Job è riepilogata in questo
schema:
Remove-Job <Oggetto job1> [, <Oggetto job1>, ...]
Remove-Job <Identificativo1> [, <Identificativo2>, ...]
Remove-Job <Nome1> [, <Nome2>, ...]
<Oggetto job1> [, <Oggetto job1>, ...] | Remove-Job

Nello script che segue, per esempio, si creano tre diversi job
(sopprimendo l’output tramite il cmdlet Out-Null, per una maggiore
leggibilità). Attraverso la pipeline prodotta da Get-Job, Stop-Job e Remove-
Job si recuperano tutti i job prodotti nella sessione, si interrompono e,
infine, si eliminano:
PS C:\Users\ikmju> Start-Job { Start-Sleep 100 } | Out-Null
PS C:\Users\ikmju> Start-Job { Start-Sleep 200 } | Out-Null
PS C:\Users\ikmju> Start-Job { Start-Sleep 300 } | Out-Null
PS C:\Users\ikmju>
PS C:\Users\ikmju> Get-Job | Stop-Job -PassThru | Remove-Job

Ad una successiva esecuzione, infatti, Get-Job non restituisce alcun


oggetto:
PS C:\Users\ikmju> @(Get-Job).Count
0
Eseguire task in remoto

Il meccanismo di esecuzione remota delle attività


all’interno della shell, una descrizione approfondita dei
cmdlet destinati a lanciare comandi su macchine
remote, in parallelo, e il sistema delle sessioni
remote interattive.

Fino a questo punto del libro sono stati analizzati alcuni cmdlet in
grado di operare in macchine remote utilizzando le API del sistema
operativo e la tecnologia DCOM/RPC. Nonostante l’enfasi con cui fu
inizialmente introdotta, questa tecnologia presenta alcuni limiti,
dovuti per lo più alle difficoltà di configurazione dei firewall rispetto al
protocollo di comunicazione.
A partire dalla versione 2.0, PowerShell è in grado di utilizzare
Windows Remote Management e di far convogliare qualsiasi attività
di amministrazione remota attraverso il protocollo HTTP (o HTTPS),
colmando il gap della versione precedente e dei cmd-let basati su
DCOM/RPC. Diversamente da quando si usano questi ultimi, d’altra
parte, sulle macchine in cui si desidera operare da remoto è
necessario che PowerShell 2.0 sia installato e che il servizio di
WinRM sia configurato e avviato correttamente.
In questo capitolo sono discusse le tecniche di esecuzione di task in
remoto nella shell e i principali cmdlet che ne permettono la
completa gestione.

Eseguire comandi in remoto


Per eseguire attività in remoto all’interno della shell, è necessario
che sul sistema di destinazione siano installati e correttamente
configurati sia PowerShell sia WinRM, procedendo per quest’ultimo
in maniera simile a quanto già analizzato nel capitolo precedente. I
job in background e i task in remoto, in effetti, condividono la
maggior parte dei requisiti di funzionamento, perché si basano
internamente sulla stessa tecnologia.

La configurazione di WinRM
Nelle macchine su cui si intende operare da remoto, dunque, è
necessario procedere alla configurazione e all’avvio del servizio
Windows Remote Management (Gestione remota Windows), alla
prenotazione dell’endpoint utilizzato dall’infrastruttura e
all’abilitazione del firewall di Windows. Così come per i job in
background, il cmdlet da utilizzare per portare a termine queste
attività è Set-WSManQuickConfig.
NOT Il cmdlet Set-WSManQuickConfig è stato introdotto nel Capitolo
A 26.

In aggiunta a quella in comune con i job in background eseguiti in


locale, la corretta configurazione di Windows Remote Management
prevede che siano configurati opportunamente anche gli eventuali
firewall posti tra le macchine che partecipano all’esecuzione dei task
in remoto. In particolare, è necessario che la comunicazione tra le
macchine dove si impartiscono i comandi e quelle dove questi sono
eseguiti possa fluire senza intoppi, in base alla porta e al protocollo
utilizzato dal servizio.
Come anticipato in precedenza, il servizio rimane di default in
ascolto sulla porta TCP/UDP 5985 per ogni indirizzo IP supportato
dalla macchina, dialogando tramite il protocollo HTTP. Utilizzando lo
switch -UseSSL di Set-WSManQuickConfig, invece, Win-dows Remote
Management rimane in ascolto sulla porta TCP/UDP 5986 e dialoga
tramite il protocollo HTTPS.
Nonostante quest’ultima possibilità, il cmdlet Set-W1nQuickConfig ha un
numero di opzioni davvero limitato: nel caso in cui sia necessario
intervenire con maggiore precisione sulla configurazione di questa
piattaforma, ci si può avvalere del provider wsMan, introdotto nel
Capitolo 17. Grazie a una struttura nidificata, il provider wsMan espone
tutte le impostazioni di configurazione di WinRM, in maniera tale da
facilitarne sia la lettura sia l’impostazione.
La configurazione degli endpoint in ascolto, per esempio, è
disponibile nella cartella wsMan:\iocaihost\Listener. Nello script che
segue, per esempio, si può verificare come nel PC utilizzato sia
attivo un endpoint HTTP associato a tutti gli indirizzi supportati:
PS WSMan:\localhost\Listener> Get-ChildItem

WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Listener

Name Type Keys


---- ---- ----
Listener_1184937132 Container {Address=*, Transport=HTTP}

Entrando nell’elemento, è possibile verificare come questo contenga


informazioni sulla porta impiegata (la 5985) e il dettaglio degli indirizzi
utilizzati (sia IPv4 sia IPv6):
PS WSMan:\localhost\Listener> cd .\Listener_1184937132
PS WSMan:\localhost\Listener\Listener_1184937132> Get-ChildItem

WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Listener\
Listener_1184937132

Name Value Type


---- ----- ----
Address * System.String
Transport HTTP System.String
Port 5985 System.String
Hostname System.String
Enabled true System.String
URLPrefix wsman System.String
CertificateThumbprint System.String
ListeningOn_1201550598 127.0.0.1 System.String
ListeningOn_1163064930 192.168.178.25 System.String
ListeningOn_1508953035 ::1 System.String
[...]

NO Per navigare all’interno del provider wsMan è necessario eseguire PowerShell con
TA i privilegi di amministrazione.

In linea con la Microsoft Trustworthy Computing, il servizio Windows


Remote Management accetta solo le connessioni provenienti da una
lista di host considerati attendibili e, per impostazione predefinita,
non accetta inizialmente alcuna connessione. Prima di eseguire
comandi in remoto presso una particolare macchina, dunque, è
necessario aggiungere l’host che origina la richiesta alla lista degli
host attendibili, memorizzati nell’elemento TrustedHosts all’interno di
WSMan:\localhost\Client.

Nello script che segue, si può constatare come l’elemento TrustedHosts


di una nuova installazione non contenga alcun valore (quindi non
permetta alcuna connessione remota):
PS WSMan:\localhost\Client> Get-ChildItem

WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client

Name Value Type


---- ----- ----
NetworkDelayms 5000 System.String
URLPrefix wsman System.String
AllowUnencrypted false System.String
Auth Container
DefaultPorts Container
TrustedHosts System.String

Per modificare la lista degli host attendibili è sufficiente intervenire


sull’elemento TrustedHosts, impostando la lista di nomi NetBIOS o
indirizzi IP, separando ogni voce con una virgola. Il simbolo di
asterisco (*), infine, indica che è ammesso qualsiasi host.
Nello script che segue, per esempio, si impostano come host
attendibili dapprima due macchine, poi si permette l’impiego di
WinRM da parte di qualsiasi macchina remota:
PS WSMan:\localhost\Client> Set-Item .\TrustedHosts "BOZOS, 10.0.9.7"

Configurazione della sicurezza di Gestione remota Windows.


Questo comando modifica l'elenco TrustedHosts per il client Gestione remota
Windows. I computer presenti nell'elenco
TrustedHosts potrebbero non essere autenticati. Il client potrebbe inviare
informazioni sulle credenziali a tali
computer. Modificare questo elenco?
[S] Sì [N] No [O] Sospendi [?] Guida (il valore predefinito è "S"):

PS WSMan:\localhost\Client> Set-Item .\TrustedHosts "*"


Configurazione della sicurezza di Gestione remota Windows.
[...]

Per verificare che la macchina corrente possa comunicare


correttamente con un’installazione remota di Windows Remote
Management è possibile utilizzare il cmdlet Test-WSMan, introdotto nel
capitolo precedente, e fornire il nome o l’indirizzo del PC di
destinazione al parametro posizionale -ComputerName, secondo questo
schema:
Test-WSMan -ComputerName <Host>

Nello script che segue si verifica la possibilità di connettersi a


un’installazione WinRM tramite un indirizzo IP:
PS C:\Users\ikmju> Test-WSMan 192.168.178.25

wsmid : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor : Microsoft Corporation
ProductVersion : OS: 0.0.0 SP: 0.0 Stack: 2.0

Una volta che il sistema è configurato per l’esecuzione di task in


remoto, è possibile usufruire dei comandi descritti nel seguito di
questo capitolo in modo trasparente e senza curarsi ulteriormente di
Windows Remote Management.

Invoke-Command
Nel suo utilizzo di base, questo cmdlet consente di eseguire un
blocco di script di PowerShell all’interno di uno o più PC remoti e
restituisce gli eventuali risultati nella pipeline. Gli host di destinazione
sono forniti al comando per mezzo del parametro posizionale -
computerName, mentre il blocco da eseguire è indicato per mezzo del

parametro posizionale -ScriptBlock.


La sintassi di base del comando è:
Invoke-Command <Hostl> [, <Host2>, ...] { <Comandi> }

Nello script che segue, per esempio, si recupera il nome del


computer remoto attraverso la variabile d’ambiente COMPUTERNAME e il
provider Environment:
PS C:\Users\ikmju> Invoke-Command 192.168.178.25 { $env:COMPUTERNAME }
WIN-TT6M1MHLDN1
In quest’altro esempio, invece, il cmdlet ritorna gli elementi della
radice del disco C:\ dell’host 192.168.178.25 la cui dimensione sia
superiore a un GB:
PS C:\Users\ikmju> Invoke-Command 192.168.178.25 {
>> Get-ChildItem C:\ -Force | ? { $_.Length -gt 1GB }
>> }
>>

Directory: Microsoft.PowerShell.Core\FileSystem::C:\

Mode LastWriteTime Length Name PSComputerName


---- ------------- ------ ---- --------------
-a-hs 4/22/2010 1:47 PM 1387765760 pagefile.sys 192.168.178.25

Alla stregua di quanto avviene per i job in background, anche le


informazioni ritornate dai comandi eseguiti in remoto subiscono il
processo di serializzazione e deserializzazione descritto nel capitolo
precedente: l’interazione con questi oggetti, pertanto, è limitata dalla
classe di riferimento. Anche in questo caso, inoltre, durante il
processo di deserializzazione l’ETS di PowerShell aggiunge a ogni
oggetto le proprietà PSComputerName e Runspaceld, utilizzabili dall’utente per
determinare quale sia la macchina (e la sessione) di provenienza
dell’elemento.
Per impostazione predefinita, Invoke-Command si collega al servizio
Windows Remote Management utilizzando il protocollo HTTP sulla
porta 5985 della macchina di destinazione. Specificando lo switch -
UseSSL, d’altra parte, è possibile forzare il comando a impiegare il

protocollo HTTPS, sulla porta 5986. Agendo sul parametro -Port,


inoltre, è possibile specificare un numero di porta differente da quello
standard.
La sintassi d’uso di questi parametri può essere schematizzata così:
Invoke-Command <Host> [-Port <Porta>] [-UseSSL] ...

Il cmdlet Invoke-Command e la piattaforma di esecuzione remota di PowerShell


NO consentono di definire molti altri dettagli relativi al protocollo di comunicazione
TA utilizzato. Per ulteriori informazioni si rimanda alla guida in linea di PowerShell e al
sito Microsoft TechNet (http://technet.microsoft.com/it).

In generale, quando si utilizza Invoke-Command (o gli altri cmdlet di


invocazione remota descritti più avanti nel capitolo) il sistema di
origine si autentica in quello di destinazione utilizzando il protocollo
Kerberos e le credenziali dell’utente corrente, che pertanto devono
corrispondere a un utente recuperabile dalla macchina di
destinazione (un utente locale o un utente di un dominio riconosciuto
dalla macchina). Nella macchina di destinazione, in ogni modo,
l’utente deve appartenere al gruppo Administrators, pena l’impossibilità
di eseguire i comandi.
Per fornire al cmdlet credenziali alternative rispetto a quelle
dell’utente corrente è necessario impiegare il parametro -Credential,
specificando il nome dell’utente (dominio incluso) da usare: prima di
procedere con l’esecuzione il sistema richiede l’immissione della
password. In questo modo l’utente corrente non deve avere alcun
ruolo particolare all’interno della macchina di destinazione, ma
l’utente specificato tramite -Credential deve comunque appartenere al
gruppo Administrators della macchina remota.
Nello script che segue, per esempio, si esegue uno script in una
macchina remota, utilizzando le credenziali di un utente remoto che
non esiste nella macchina locale (entrambe le macchine coinvolte
non risiedono in un dominio Active Directory):
PS C:\Users\ikmju> Invoke-Command 192.168.178.25 { $env:USERDOMAIN } -
Credential Test
WIN-TT6M1MHLDN1

Prima di eseguire lo script, PowerShell richiede l’immissione della


password per l’utente specificato, utilizzando la classica finestra
Windows predisposta per questa attività.
Figura 27.1 - La finestra di immissione delle credenziali.

Il cmdlet Invoke-Command può anche essere richiamato specificando,


tramite il parametro -FilePath, un file di script esterno da eseguire,
purché questo risieda nella macchina locale; tramite il parametro -
ArgumentList, inoltre, è possibile specificare i valori degli eventuali

parametri ammessi dallo script stesso. La sintassi di base per questo


tipo di chiamata può essere riepilogata così:
Invoke-Command <Hostl> [, <Host2>/ ...] -FilePath <Percorso script> -
ArgumentList <Parametrol> [, <Parametro2>, ...\

Il blocco che segue, per esempio, esegue uno script esterno in


remoto tramite Invoke-Command, fornendo tre parametri:
PS C:\Users\ikmju> Invoke-Command 192.168.178.25 -FilePath .\Sum.PSl -
ArgumentList 9, 7, 83
99

NO L’esecuzione di Invoke-Command nei sistemi che prevedono l’UAC richiede i privilegi


TA di amministrazione.

Esecuzione su più macchine


Come anticipato nel paragrafo precedente, fornendo più di un host al
parametro -ComputerName il cmdlet Invoke-Command è in grado di eseguire la
stessa attività a fronte di più macchine remote: in tal caso il risultato
del comando è un aggregato di tutti i risultati prodotti.
Per determinare quale sia la macchina che ha prodotto ciascun
oggetto ritornato dal comando è sufficiente usufruire della proprietà
PSComputerName, aggiunta di default dal sistema ETS di PowerShell.

In questo script, per esempio, si esegue un blocco di comandi a


fronte di alcune macchine remote, il cui risultato è chiaramente
determinabile dalla proprietà menzionata:
PS C:\Users\ikmju> Invoke-Command 192.168.178.25, BOZOS { Get-Process expl* }

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName PSComputerName


------- ------ ----- ----- ----- ------ -- ----------- --------------
489 15 28308 30688 154 32,46 3848 explorer 192.168.178.25
372 12 26756 28782 39 16,48 1822 explorer BOZOS
412 16 32817 35671 123 18,92 2613 explorer BOZOS

Esecuzione in background
L’esecuzione di un’attività in remoto può essere facilmente convertita
in un job, grazie allo switch -Asjob del cmdlet Invoke-Command: in tal caso il
comando non ritorna direttamente il risultato del codice fornito, ma
restituisce un oggetto job creato all’interno della sessione locale, che
si può gestire con le tecniche illustrate nel capitolo precedente.
In questo script, per esempio, si crea un job a partire da un’attività
eseguita in remoto; l’impiego del job permette di liberare la sessione
corrente dall’attesa del termine dell’operazione:
PS C:\Users\ikmju> $job = Invoke-Command 192.168.178.25 { Get-Childltem C:\
-Recurse } -AsJob

Nel caso in cui lo switch -AsJob sia usato a fronte di più host remoti, il
cmdlet Invoke-Command ritorna un insieme di job subordinati a un job
principale, che funge da collettore. I job subordinati sono tanti quanti
gli host specificati e sono disponibili attraverso la proprietà ChildJobs
del job principale.
Lo script che segue, per esempio, illustra alcune informazioni sui job
creati a fronte dell’esecuzione remota di un’attività in background su
host multipli:
PS C:\Users\ikmju> $job = Invoke-Command 192.168.178.25, BOZOS, Inexistant { pwd } -
AsJob
PS C:\Users\ikmju> $job
Id Name State HasMoreData Location
Command
-- ---- ----- ----------- -------- -
------
13 Job13 Running True 192.168.178.25, BO...
pwd

PS C:\Users\ikmju> $job | Select-Object -Expand ChildJobs

Id Name State HasMoreData Location


Command
-- ---- ----- ----------- -------- ----
---
14 Job14 Completed True 192.168.178.25 pwd
15 Job15 Completed True BOZOS pwd
16 Job16 Failed False Inexistant pwd

Criteri di esecuzione e limiti


Nonostante possa sembrare banale, è sempre necessario prestare
attenzione alla macchina cui si sottopongono i propri comandi:
qualsiasi script eseguito in remoto, infatti, è preso interamente in
carico dall’installazione di PowerShell di destinazione e viene
eseguito in una sessione creata ad hoc per l’utente che lancia Invoke-
Command (o quello eventualmente specificato tramite il parametro -

Credential).
L’esecuzione dei comandi in remoto, pertanto, è soggetta al criterio
di esecuzione impostato nella macchina di destinazione ed è limitata
ai cmdlet e agli assembly disponibili in loco. Il profilo di PowerShell,
inoltre, è quello della macchina remota, quindi qualsiasi informazione
presente nel profilo locale, come gli eventuali alias e le funzioni
custom, potrebbe non trovare un elemento corrispondente nel profilo
caricato in remoto.
Come regola generale, quindi, quando si opera in remoto è sempre
conveniente utilizzare i nomi completi dei cmdlet oppure gli eventuali
alias predefiniti, ma evitare quelli che non lo sono e le funzioni
custom.

Sessioni remote
Le tecniche dedicate all’esecuzione di task remoti esposte fino a
questo punto presentano un limite, facilmente riscontrabile non
appena si desidera legare più comandi tra loro. Poiché il cmdlet
Ivoke-Command porta, di default, alla creazione di una nuova sessione per

ogni chiamata, infatti, non è possibile riutilizzare in modo efficiente il


risultato di uno script remoto all’interno di un altro e, in generale, non
c’è possibilità di mantenere in vita delle variabili remote tra la prima
esecuzione e la seconda.
Come dimostra lo script che segue, ogni script remoto vive all’interno
della propria sessione, isolata dalle altre, che al termine
dell’esecuzione di default viene rimossa:
PS C:\Users\ikmju> Invoke-Command 192.168.178.25 { $test = 'www.powershell.
it'; $test }
www.powershell.it
PS C:\Users\ikmju> Invoke-Command 192.168.178.25 { $test }

NO Come si vuole dimostrare, la seconda chiamata a Invoke-Command non ritorna


TA alcun risultato.

Per superare questo limite, PowerShell prevede la possibilità di


gestire manualmente il ciclo di vita della sessione remota,
consentendo all’utente di mantenerla in vita a fronte di più script, che
in questo modo possono condividerne il contenuto.
La condivisione delle sessioni remote tra più esecuzioni consente di
migliorare l’efficienza dei propri script e di evitare di impegnare
inutilmente il meccanismo di serializzazione di Windows Remote
Management per trasportare tra le macchine coinvolte le
informazioni parziali di stato.
Nel seguito del paragrafo sono analizzati i diversi cmdlet dedicati alla
gestione delle sessioni remote e sono illustrate le tecniche che
permettono di sfruttarne le potenzialità all’interno della shell.

New-PSSessìon
Il cmdlet New-Pssession crea una nuova sessione remota (l’acronimo
PSSession sta per PowerShell Session) verso un particolare host e
la ritorna nella pipeline. In modo molto simile a Invoke-Command, questo
comando permette di specificare il computer di destinazione tramite
il parametro posizionale -ComputerName. Anche in questo caso è
possibile agire sul parametro -Port e sullo switch -UseSSL per
modificare le impostazioni di base sulla connessione, mentre il
parametro -Credential è usato ancora una volta per connettersi alla
macchina remota utilizzando un utente differente da quello corrente.
In modo simile ai job, inoltre, le sessioni possono avere un nome
descrittivo per gestirle facilmente, da fornire tramite il parametro -
Name.

La sintassi di base di questo comando è schematizzabile così:

New-PSSession <Host>
New-PSSession <Host> [-Credential <Utente>] [-UseSSL] [-Port <Porta>] [-Name
<Nome descrittivo>[

Nel blocco che segue, per esempio, si utilizza questo cmdlet per
avviare una sessione remota, di cui PowerShell provvede poi a
mostrare a video le informazioni più importanti:
PS C:\Users\ikmju> New-PSSession 192.168.178.25 -Name "Sessione di test"

Id Name ComputerName State ConfigurationName Availability


-- ---- ------------ ----- ----------------- ------------
4 Sessione di ... 192.168.178.25 Opened Microsoft.PowerShell Available
Nel caso si fornisca più di un host al comando, quest’ultimo ritorna
un oggetto per ogni elemento specificato, come ci si potrebbe
aspettare:
PS C:\Users\ikmju> New-PSSession 192.168.178.25, 192.168.178.26

Id Name ComputerName State ConfigurationName Availability


-- ---- ------------ ----- ----------------- ------------
6 Session6 192.168.178.25 Opened Microsoft.PowerShell Available
7 Session7 192.168.178.26 Opened Microsoft.PowerShell Available

Gli oggetti ritornati da New-PSSession sono di tipo System.Management.Automation.


Runspaces.PSSession e sono generalmente utilizzati dagli altri cmdlet per

operare nella sessione individuata da ciascuna istanza; la Tabella


27.1 elenca le principali proprietà di questa classe, il cui impiego
diretto è tendenzialmente molto limitato.

Tabella 27.1 - Le principali proprietà della classe


System.Management.Automation. Runspaces.PSSession.
Proprietà Proprietà
Indica la disponibilità della sessione ad accettare
Availability
comandi, secondo i valori esposti nella Tabella 27.2
Ritorna il nome dell’host con cui la sessione è stata
ComputerName
stabilita
Id L’identificativo numerico univoco della sessione remota
Il nome descrittivo della sessione, utilizzabile per
Name
facilitarne la gestione

Tabella 27.2 - I principali valori che la proprietà Availability può


assumere.
Valore Descrizione
Availabl
Indica la disponibilità della sessione ad accettare comandi
e
Indica che la sessione sta eseguendo un comando e non ne
Busy
può al momento accettare di nuovi
None Indica che la sessione non è disponibile perché è disconnessa

Alla stregua di quanto avviene per i job in background, la creazione


di ogni sessione tramite New-PSSession porta alla registrazione
automatica di questo oggetto in una lista mantenuta internamente da
PowerShell e associata alla sessione locale corrente. L’esistenza di
questa lista consente di gestire le sessioni remote facilmente, in
modo centralizzato e senza dover ricorrere obbligatoriamente alle
istanze ritornate dal cmdlet menzionato.

Get-PSSession
Questo cmdlet è utilizzato per recuperare le sessioni remote create
all’interno della sessione corrente, ritornando gli elementi
memorizzati internamente da PowerShell all’atto della creazione di
questi oggetti. Richiamando il comando senza specificare alcun
parametro si ottiene la lista di tutte le sessioni. Mediante il parametro
posizionale -ComputerName, invece, è possibile filtrare i risultati in base a
una o più destinazioni, mentre col parametro posizionale -Id si
recuperano le sessioni dotate degli identificativi univoci indicati. Il
parametro -Name, infine, è impiegato per filtrare i risultati in base a uno
o più nomi descrittivi.
La sintassi di base di questo comando segue dunque questo
semplice schema:
Get-PSSession
Get-PSSession <Host1> [, <Host2>, ...]
Get-PSSession <Id1> [, <Id2>, ...]
Get-PSSession -Name <Nome descrittivo1> [, <Nome descrittivo2>, ...]

Nello script che segue, si recuperano le sessioni remote associate a


un particolare host:
PS C:\Users\ikmju> Get-PSSession 192.168.178.25

Id Name ComputerName State ConfigurationName Availability


-- ---- ------------ ----- ----------------- ------------
7 Session7 192.168.178.25 Opened Microsoft.PowerShell Available
6 Session6 192.168.178.25 Opened Microsoft.PowerShell Available

In quest’altro script, invece, si filtrano le sessioni remote in base al


nome descrittivo, utilizzando i caratteri wildcard:
PS C:\Users\ikmju> Get-PSSession -Name *test | Format-List
PS C:\Users\ikmju> Get-PSSession -Name *test | Format-List

ComputerName : 192.168.178.25
ConfigurationName : Microsoft.PowerShell
InstanceId : 2ede369d-a6a4-42bc-b118-26f9f9c1243d
Id : 8
Name : Sessione di test
[...]

Utilizzare la sessione in Invoke-Command


Una volta che la sessione remota è stata creata ed è disponibile per
l’esecuzione di comandi, è possibile includerla nelle chiamate a
Invoke-Command attraverso il parametro posizionale -Session, in maniera

tale da far condividere agli script desiderati lo stesso ambiente


remoto. Quando si usa -Session tutti i parametri menzionati in
precedenza relativi alla configurazione della sessione devono essere
omessi e la sintassi del cmdlet segue questo schema:
Invoke-Command <Sessione> { <Comandi> }
Invoke-Command <Sessione1> [, <Sessione2>, ...] { <Comandi> }
Invoke-Command <Sessione> -FilePath <Percorso script> -ArgumentList
<Parametro1> [, <Parametro2>, ...]
Invoke-Command <Sessione1> [, <Sessione2>, ...] -FilePath <Percorso script>
-ArgumentList <Parametro1> [, <Parametro2>, ...]

Nel caso si specifichi più di una sessione, Invoke-Command esegue le


istruzioni impartite in ognuno degli elementi indicati e ritorna un
aggregato dei risultati, in modo simile a quanto descritto in
precedenza con l’esplicitazione degli host remoti.
Nello script che segue, per esempio, si crea dapprima una sessione
remota e la si utilizza per eseguire più comandi tramite Invoke-Command.
A differenza dei casi precedenti, qui gli script condividono l’ambiente
di esecuzione remoto e possono accedere, per esempio, alle stesse
variabili remote:
PS C:\Users\ikmju> $session = New-PSSession SERVER01
PS C:\Users\ikmju> Invoke-Command $session { $test = 'powershell' }
PS C:\Users\ikmju> Invoke-Command $session { $test + '.it' }
powershell.it

Remove-PSSessìon
Come ci si potrebbe immaginare, le sessioni remote impegnano
risorse sia nella macchina che origina i comandi sia, soprattutto, in
quella che li esegue. Nonostante esista un sistema di eliminazione
automatica che interviene in caso di disconnessione, una volta
terminato di utilizzare una sessione conviene sempre procedere
all’eliminazione della stessa mediante il cmdlet Remove-Pssession. Come
nel caso di Get-Pssession, è possibile indicare a questo comando le
sessioni da rimuovere in base al nome del-l’host remoto,
all’identificativo univoco e al nome descrittivo utilizzando,
rispettivamente, i parametri -ComputerName, -Id e -Name. L’impiego più
frequente, tuttavia, consiste nell’indicare a Remove-PSSession le sessioni
desiderate tramite il parametro posizionale -Session oppure
direttamente mediante la pipeline.
La sintassi d’uso di base di questo cmdlet segue questo schema:
Remove-PSSession <Host1> [, <Host2>, ...]
Remove-PSSession <Id1> [, <Id2>, ...]
Remove-PSSession -Name <Nome descrittivo1> [, <Nome descrittivo2>, ...]
Remove-PSSession -Session <Sessione1> [, <Sessione2>, ...]
<Sessione1> [, <Sessione2>, ...] | Remove-PSSession

Nello script che segue, per esempio, si rimuovono tutte le sessioni


remote associate a un particolare host:
PS C:\Users\ikmju> Remove-PSSession 192.168.178.25

NO Nel caso non esistano sessioni associate all’host specificato, il comando genera
TA un errore appropriato.

In quest’altro esempio, invece, Remove-PSSession si occupa di eliminare la


sessione creata nell’esempio del paragrafo precedente:
PS C:\Users\ikmju> $session | Remove-PSSession

Sessioni remote interattive


Per sessione remota interattiva si intende una sessione remota (o
anche locale, ma comunque gestita da Windows Remote
Management) che permette uno scambio diretto di informazioni tra
l’host remoto e quello locale, consentendo l’esecuzione di flussi di
comandi con un’interfaccia pressoché identica a quella fisica
dell’host di destinazione. La differenza tra una sessione remota e
una sessione remota interattiva è semplicemente il modo con cui la
shell consente lo scambio di informazioni: nel primo caso
l’esecuzione di comandi avviene solo tramite Invoke-Command, mentre nel
secondo c’è una totale trasparenza e le istruzioni sono eseguite
come se si stesse operando in locale.
Nel seguito di questo paragrafo sono discussi i cmdlet che
permettono di utilizzare le sessioni remote interattive in PowerShell.

Enter-PSSession
Il cmdlet Enter-Pssession consente di avviare una sessione remota
interattiva operando all’interno di una sessione esistente oppure
creandone una ex novo.
Per utilizzare una sessione esistente è sufficiente fornire al comando
l’elemento di interesse attraverso il parametro posizionale -Session,
direttamente tramite la pipeline oppure, ancora, specificando
l’identificativo univoco mediante il parametro -Id. Per creare una
nuova sessione, invece, è possibile utilizzare lo stesso set di
parametri illustrati in precedenza per New-Pssession: -ComputerName per
specificare l’host di destinazione, -port per indicare la porta, lo switch
-UseSSL per impiegare il protocollo HTTPS e -Credential per usare

credenziali differenti da quelle dell’utente corrente.


La sintassi di base del comando è:
Enter-PSSession <Sessione>
Enter-PSSession <Identificativo sessione>
<Sessione> | Enter-PSSession

Enter-PSSession <Host>
New-PSSession <Host> [-Credential <Utente>] [-UseSSL] [-Port <Porta>]

Una volta che la shell ha avviato una sessione remota interattiva, il


prompt riporta automaticamente l’informazione sull’host in uso,
facendo precedere il nome alla classica sigla iniziale PS.
Nel blocco che segue, per esempio, si avvia una nuova sessione
interattiva verso un particolare host e si eseguono alcuni comandi in
remoto:
PS C:\Users\ikmju> Enter-PSSession SERVER01
[server01]: PS C:\Users\ikmju> Get-Process | Sort-Object WS -Descending |
Select-Object -First 3

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName


------- ------ ----- ----- ----- ------ -- -----------
1033 23 40124 48392 167 63.68 996 svchost
442 12 24488 39816 170 5.76 604 powershell
271 9 21344 32272 140 4.45 2972 wsmprovhost

[server01]: PS C:\Users\ikmju> $env:COMPUTERNAME


SERVER01
In quest’altro esempio, invece, si crea una sessione interattiva a
partire da una sessione remota creata in precedenza e già utilizzata
da Invoke-Command:
PS C:\Users\ikmju> $session = New-PSSession 192.168.178.25
PS C:\Users\ikmju> Invoke-Command $session { Get-Service w3svc }

Status Name DisplayName


PSComputerName
------ ---- ----------- ---------
-----
Stopped w3svc Servizio Pubblicazione sul Web
192.168.178.25

PS C:\Users\ikmju> Enter-PSSession $session


[192.168.178.25]: PS C:\Users\ikmju> Start-Service w3svc
[192.168.178.25]: PS C:\Users\ikmju> Get-Service w3svc

Status Name DisplayName


------ ---- -----------
Running w3svc Servizio Pubblicazione sul Web

Nelle sessioni remote interattive le informazioni subiscono un processo di


serializzazione e deserializzazione ridotto, che porta gli oggetti a mantenere le
NO
proprietà e i metodi originali e la propria gerarchia di classe. Infine, agli oggetti
TA
gestiti durante le sessioni remote interattive non sono aggiunte le proprietà che
permettono di recuperare la macchina (e la sessione) di provenienza.

Exìt-PSSessìon
Per terminare una sessione remota interattiva è sufficiente usare il
cmdlet Exit-PSSession senza fornire alcun parametro. Se la sessione
remota era già esistente al momento della chiamata di Enter-PSSession,
questo comando si limita a uscire dalla modalità di esecuzione
interattiva, ma non agisce sulla sessione remota, che può essere
riutilizzata. Nel caso in cui, invece, la sessione remota sia stata
creata direttamente da Enter-PSSession, il cmdlet Exit-PSSession si occupa
anche della sua chiusura.
Una volta terminata la sessione remota interattiva, il prompt di
PowerShell ritorna automaticamente allo stato assunto in
precedenza. Lo script che segue, continuazione dell’esempio
precedente, dimostra come la sessione remota sia ancora
utilizzabile dopo il termine della modalità interattiva e come sia
necessario chiuderla esplicitamente con Remove-PSSession:
[192.168.178.25]: PS C:\Users\ikmju> Exit-PSSession
PS C:\Users\ikmju> Invoke-Command $session { [Environment]::OSVersion.
VersionString }
Microsoft Windows NT 6.0.6001 Service Pack 1
PS C:\Users\ikmju> $session | Remove-PSSession

NO Nelle sessioni remote interattive la keyword exit richiama automaticamen te il


TA cmdlet Exit-PSSession.
La gestione degli errori

Una panoramica sui diversi tipi di problemi che possono


insorgere durante lo sviluppo di uno script e alcuni consigli
su come evitarli. Una guida dettagliata sui livelli di
gravità degli errori e un’analisi degli strumenti che
PowerShell mette a disposizione per gestire i refusi in modo
centralizzato.

La possibilità che si verifichi un problema all’interno di uno script va


generalmente aumentando con il numero di istruzioni di cui è
composto. Nonostante esistano tecniche concepite per limitare il
numero degli errori, le possibili cause sono molteplici e, a
prescindere dalla complessità del codice, è sempre auspicabile
dotare i propri script di un meccanismo in grado di gestire
automaticamente l’insorgere di possibili problemi.
PowerShell è in grado di determinare automaticamente la gravità
degli errori e permette agli script di variare il proprio comportamento
di conseguenza; all’interno della shell, inoltre, sono presenti diversi
strumenti che consentono all’utente di gestire i problemi da una
posizione centralizzata e di recuperare per ciascuno informazioni
dettagliate, così da reagire con la massima tempestività e
precisione.
Il seguito di questo capitolo discute le diverse tipologie di errore
contemplate da PowerShell, fornendo indicazioni su come evitare
ciascuna di esse, e illustra i due diversi livelli di gravità con cui il
sistema identifica ogni problema, analizzando le tecniche che ne
permettono la cattura e la gestione.

Errori sintattici
Prima di poter eseguire le istruzioni contenute in uno script o in una
semplice riga di codice, PowerShell ne effettua il parsing,
convertendone il testo in una rappresentazione simbolica in grado di
essere eseguita dal motore della shell. Durante questa fase il testo
viene sottoposto a complesse verifiche che permettono di validarne
la correttezza formale. Nel caso venga rilevato un errore di sintassi,
il comportamento predefinito della shell consiste nel mostrare a
video un messaggio descrittivo del problema e interrompere
l’esecuzione.
Disponendo di uno script esterno chiamato Test-psl dotato di questo
contenuto (si noti la mancanza del doppio apice di chiusura della
stringa):
2 + 3
"hello, world

e tentando di eseguirlo al prompt, la shell mostra a video un


messaggio di errore simile a questo:

PS C:\Users\ikmju> &.\Test.ps1
Nella stringa che inizia con:
In C:\Users\ikmju\Test.ps1:2 car:1
+ <<<< "hello, world
manca il carattere di terminazione: ".
In C:\Users\ikmju\Test.ps1:2 car:14
+ "hello, world <<<<
+ CategoryInfo : ParserError: (hello, world:String) [], ParseException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString

Come si può notare, gli errori di questo tipo sono molto semplici da
correggere perché la shell li notifica immediatamente all’utente e
fornisce il dettaglio del problema.

Errori di runtime
Gli errori di runtime sono generati durante l’esecuzione degli script (e
dei cmdlet) e sono tipicamente dovuti a problemi con l’input fornito o
con l’ambiente circostante che non sono stati presi in considerazione
dal codice, involontariamente o intenzionalmente. Diversamente
dagli errori sintattici, dunque, questo tipo di errori non è rilevato
immediatamente dalla shell ma si presenta solo al verificarsi di
determinate condizioni.
Gli errori di questo tipo sono talvolta impossibili da eliminare, perché
dipendono da circostanze fuori dal controllo dello sviluppatore dello
script. Spesso, però, un adeguato controllo del codice può portare
all’eliminazione della maggior parte dei possibili errori di runtime.
Dovendo richiedere all’utente la digitazione di un valore numerico,
per esempio, si potrebbe ritenere corretto uno script come questo:
$x = [int](Read-Host -Prompt 'Inserisci un numero')

Tuttavia, anche se le istruzioni vengono eseguite correttamente nel


caso in cui l’utente digiti un valore numerico – come, per esempio, 3
– se l’utente non è collaborativo e inserisce un valore non numerico
il blocco genera un errore di runtime:
PS C:\Users\ikmju> $x = [int](Read-Host -Prompt 'Inserisci un numero')
Inserisci un numero: UN NUMERO
Impossibile convertire il valore "UN NUMERO" nel tipo "System.Int32".
Errore: "Formato della stringa di input non corretto."
In riga:1 car:11
+ $x = [int] <<<< (Read-Host -Prompt 'Inserisci un numero')
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

Per correggere gli errori di questo tipo è necessario analizzare il


codice e verificare sistematicamente il maggior numero di scenari
possibili in cui lo script può essere eseguito. Se lo script riceve input
da parte dell’utente, inoltre, è sempre necessario verificare i dati
inseriti per evitare possibili danni causati più o meno
inconsapevolmente.

Errori logici
Questa tipologia di errori è la più dannosa e difficile da rilevare. A
differenza di quelli sintattici e di runtime, infatti, questi errori non
comportano di per sé la visualizzazione di alcun messaggio, ma
portano alla produzione di un risultato diverso da quello atteso o,
tipicamente, causano errori di runtime in blocchi differenti dello
script, cui viene fornito un input inatteso.
Nel blocco che segue, per esempio, un semplice ciclo dovrebbe
iterare una collezione di interi, mostrando a video il valore di ciascun
elemento:
$values = 1, 2, 3

for ($i = 0; $i -gt $values.Length; $i++) {


$i
}

All’atto dell’esecuzione, tuttavia, non viene mostrato a video nulla e


non viene generato alcun errore. Il lettore più attento potrebbe aver
notato che la condizione del ciclo utilizza in questo caso l’operatore
sbagliato (-gt anziché -lt), ma problemi come questo, in particolare
all’interno di una base di codice molto ampia, potrebbero essere
difficili da risolvere.
Per gestire questo tipo di errori è necessario pianificare
accuratamente il proprio lavoro, suddividendo le attività in funzioni e
script esterni, in modo tale da ridurre la complessità di ciascun
blocco e poter testare accuratamente ogni singola funzionalità.

Errori fatali ed eccezioni


Diversamente da quanto in genere avviene all’interno di altre shell e
nella maggior parte dei linguaggi di sviluppo, PowerShell suddivide
gli errori in due categorie di gravità, a seconda del tipo di problema e
della configurazione della sessione.
Gli errori con il livello di gravità più elevato sono detti errori fatali
(terminating error, nella localizzazione inglese) e la loro comparsa è
correlata a un problema che impedisce al codice, allo script o al
cmdlet di proseguire con l’esecuzione. Per tale ragione, gli errori
fatali provocano l’interruzione immediata dell’intera attività – sia
questa uno script o una semplice pipeline – a meno che non
vengano gestiti tramite le tecniche analizzate nel seguito della
sezione.
Come il lettore con precedenti esperienze di sviluppo potrebbe aver
già intuito, gli errori fatali hanno una diretta corrispondenza con il
concetto di eccezione (exception, in inglese). In informatica, la
gestione delle eccezioni (in inglese exception handling) è una
tecnica che consente di organizzare in maniera efficiente il proprio
codice, concentrando la logica di risoluzione degli errori in un’unica
posizione. Il termine eccezione, in questo contesto, indica
un’anomalia rispetto al naturale flusso di esecuzione del codice,
variato in seguito alla comparsa del problema.
Generalmente, un’eccezione sollevata in un particolare strato di
codice può essere gestita dalla logica di risoluzione applicata allo
strato stesso. Nel caso questa logica manchi oppure non sia stata
concepita per gestire la tipologia di problema emerso, l’eccezione
viene automaticamente propagata allo strato di codice chiamante,
dove il sistema verifica nuovamente la disponibilità di un blocco di
logica di gestione. Il processo si ripete fino a quando non viene
trovato un blocco di codice in grado di gestire l’eccezione. Se la
ricerca non va a buon fine, l’errore causa tipicamente il termine
dell’attività e la visualizzazione di un messaggio appropriato.
Nel framework Microsoft .NET ogni eccezione è un’istanza di una
classe particolare, che identifica la tipologia dell’errore e consente al
codice che la genera di fornire dettagli specifici sul problema e al
codice che la gestisce di poter modificare la propria logica in base a
tali informazioni. Grazie al meccanismo dell’ereditarietà, che
permette di derivare la definizione di un tipo dall’altro e di formare
una gerarchia di classi, da ogni tipo di eccezione ne possono
derivare altri, più specializzati.
Senza scendere nel dettaglio di questo argomento, è importante
sottolineare come un blocco creato per gestire eccezioni di una
classe sia automaticamente in grado di farlo anche per le classi
derivate dalla prima. PowerShell gestisce gli errori utilizzando un
meccanismo proprietario, basato in massima parte sull’infrastruttura
delle eccezioni di Microsoft .NET, da cui differisce, tra l’altro, per il
concetto delle categorie di gravità, introdotte in precedenza.
All’interno della shell, dunque, si possono sollevare nuove eccezioni,
alla stregua di quanto è possibile fare in qualsiasi altro linguaggio di
sviluppo basato su Microsoft .NET, come C# o Visual Basic .NET In
aggiunta a questo, la shell mette a disposizione due diversi
paradigmi di gestione delle eccezioni, in base alle preferenze e
all’orientamento tecnologico dell’utente: in entrambi i casi, dalla shell
è possibile gestire sia le eccezioni sollevate dagli script sia quelle
sollevate internamente dalle classi del framework Microsoft .NET Il
seguito di questo paragrafo è dedicato alle tecniche di sollevazione e
di gestione delle eccezioni e, in generale, agli errori fatali.

Throw
Per sollevare un’eccezione in PowerShell è sufficiente utilizzare
l’istruzione throw, cui è possibile far seguire l’oggetto che rappresenta
il dettaglio dell’errore. Nel caso questo oggetto non sia presente o
non sia un’istanza di una classe di eccezione, l’istruzione provvede a
generare automaticamente un’eccezione di tipo system.Management.
Automation.RuntimeException e a impostare il valore della proprietà Message

pari alla rappresentazione testuale dell’oggetto fornito (se presente).


La sintassi dell’istruzione throw è:
throw
throw <Eccezione>
throw <Oggetto>

Nello script che segue, per esempio, l’esecuzione del ciclo è


interrotta da un’eccezione generica:
PS C:\Users\ikmju> for ($i = 0; $i -lt 10; $i++) {
>> $i
>>
>> if ($i -eq 3) {
>> throw 'Ooopppsss!'
>> }
>> }
>>
0
1
2
3
Ooopppsss!
At line:4 char:14
+ throw <<<< 'Ooopppsss!'
+ CategoryInfo : OperationStopped: (Ooopppsss!:String) [],
RuntimeException
+ FullyQualifiedErrorId : Ooopppsss!
In quest’altro esempio, invece, viene sollevata un’eccezione di tipo
System. IO.IOException, destinata a segnalare problemi di I/O:

PS C:\Users\ikmju> throw New-Object IO.IOException 'Unità Z: non presente'


Unità Z: non presente
At line:1 char:6
+ throw <<<< New-Object IO.IOException 'Unità Z: non presente'
+ CategoryInfo : OperationStopped: (:) [], IOException
+ FullyQualifiedErrorId : Unità Z: non presente

Prima della versione 2.0 di PowerShell e del supporto per la


validazione dei parametri, uno degli impieghi più frequenti di throw
consisteva nel rendere obbligatori i parametri degli script e delle
funzioni sollevando un’eccezione nel caso questi avessero assunto il
rispettivo valore di default. Nonostante questa pratica possa
considerarsi superata, tuttavia, è ancora ampiamente utilizzata e
dimostra l’ampio ventaglio di possibilità di interazione offerto dalla
shell.
Questa funzione, per esempio, sfrutta il meccanismo descritto per
sollevare un’eccezione quando il chiamante non specifica un valore
per il parametro $name:
function Greet($name = $(throw 'Parametro $name obbligatorio!')) {
"Ciao, $name!"
}

Variando la chiamata alla funzione, infatti, il risultato varia come ci si


potrebbe aspettare:
PS C:\Users\ikmju> Greet
Parametro name obbligatorio!
At line:1 char:31
+ function Greet($name = $(throw <<<< 'Parametro name obbligatorio!')) {
+ CategoryInfo : OperationStopped: (Parametro name
obbligatorio!:String) [], RuntimeException
+ FullyQualifiedErrorId : Parametro name obbligatorio!

PS C:\Users\ikmju> Greet Efran


Ciao, Efran!

Poiché le eccezioni sollevate tramite l’istruzione throw sono


considerate da PowerShell errori fatali, la loro comparsa pregiudica
l’esecuzione del resto dello script o della funzione. Come anticipato,
per gestire le eccezioni e permettere al codice di proseguire con
l’esecuzione è possibile sfruttare due diversi meccanismi, analizzati
nel seguito di questa sezione.
Trap
L’istruzione trap è presente in PowerShell sin dalla prima release e
consente all’utente di definire un blocco di script da eseguire per
catturare e gestire gli errori fatali nello scope di una funzione o di
uno script esterno. L’obiettivo di trap è analogo a quello dell’istruzione
omonima presente nelle principali shell *nix, con la differenza
principale che trap supporta il concetto di eccezioni e l’intero
paradigma a oggetti di Microsoft .NET.
Fornendo all’istruzione una classe di eccezione si limita il blocco a
entrare in gioco solo nel caso in cui siano sollevate eccezioni del tipo
indicato (o di un tipo derivato), mentre omettendo questa
informazione il blocco è eseguito a ogni errore fatale.
La sintassi di trap è schematizzabile come segue:
trap [<Tipo eccezione>]
{
<Istruzioni>
}

Supponendo, per esempio, di disporre di uno script esterno Test.PS1

(o di una funzione) con questo corpo:


trap
{
Write-Host "Trap generico."
}

"Prima riga"
3 / $null
"Terza riga"

Il risultato della chiamata dimostrerebbe come il blocco trap


intervenga non appena si verifica l’eccezione generata dalla
divisione per zero:
PS C:\Users\ikmju> .\Test.ps1
Prima riga
Trap generico.
Attempted to divide by zero.
At C:\Users\ikmju\Test.ps1:7 char:4
+ 3 / <<<< $null
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

Terza riga
La peculiarità principale di questo tipo di blocco è che diventa
disponibile non appena il relativo scope viene attivato. Questa
caratteristica fornisce agli utenti - tipicamente a chi ha competenze
limitate in ambito di sviluppo - una maggiore libertà di espressione
all’interno del proprio codice. Lo script esterno dell’esempio
precedente, quindi, avrebbe portato allo stesso risultato anche se il
blocco trap fosse stato definito in seguito alla riga problematica; il
blocco che segue, dunque, è analogo al precedente:
"Prima riga"
3 / $null
"Terza riga"

trap
{
Write-Host "Trap generico." }

Se in uno stesso scope sono definiti più blocchi trap, al verificarsi di


un errore fatale la shell esegue il blocco relativo all’eccezione più
specifica, individuata da ciascun blocco. Modificando come segue lo
script esterno di esempio, quindi:
"Prima riga"
3 / $null
"Terza riga"

trap
{
Write-Host "Trap generico."
}

trap [DivideByZeroException]
{
Write-Host "Trap divisione."
}

si porterebbe la shell a eseguire solo il secondo blocco trap:


PS C:\Users\ikmju> .\Test.ps1
Prima riga
Trap divisione.
Attempted to divide by zero.
At C:\Users\ikmju\Test.ps1:2 char:4
+ 3 / <<<< $null
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

Terza riga
Infine, se in uno stesso scope fossero definiti due blocchi trap per il
medesimo tipo di eccezione, la shell prenderebbe in considerazione
solo il primo. Se lo script esterno di esempio fosse dotato di questo
corpo, dunque:
"Prima riga"
3 / $null
"Terza riga"
trap
{
Write-Host "Trap generico #1."
}
trap
{
Write-Host "Trap generico #2."
}

l’esecuzione alla riga di comando porterebbe a questo risultato:


PS C:\Users\ikmju> .\Test.ps1
Prima riga
Trap generico #1.
Attempted to divide by zero.
At C:\Users\ikmju\Test.ps1:2 char:4
+ 3 / <<<< $null
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

Terza riga

Per impostazione predefinita, in seguito all’esecuzione di un blocco


trap la shell emette dapprima un errore nel canale standard error
(stderr), poi riprende il flusso dall’istruzione successiva a quella che
lo ha generato. Utilizzando l’istruzione continue all’interno del blocco,
tuttavia, si impedisce a PowerShell di emettere l’errore nel canale
standard error, mentre impiegando l’istruzione break si termina
l’esecuzione dello script.
NOT I canali di input e di output sono analizzati nel Capitolo
A 20.

Inserendo un’istruzione continue nello script esterno di esempio, in


modo simile a questo, dunque:
"Prima riga"
3 / $null
"Terza riga"
trap
{
Write-Host "Trap generico."
continue
}

si porta la shell a evitare di emettere l’errore:


PS C:\Users\ikmju> .\Test.ps1
Prima riga
Trap generico.
Terza riga

Mentre utilizzando l’istruzione break, in questo modo:


"Prima riga"
3 / $null
"Terza riga"

trap
{
Write-Host "Trap generico."
break
}

Si impedisce allo script di proseguire con l’esecuzione:


PS C:\Users\ikmju> .\Test.ps1
Prima riga
Trap generico.
Attempted to divide by zero.
At C:\Users\ikmju\Test.ps1:2 char:4
+ 3 / <<<< $null
+ CategoryInfo : NotSpecified: (:) [],
ParentContainsErrorRecordException
+ FullyQualifiedErrorId : RuntimeException

All’interno del blocco trap, la variabile automatica $_ restituisce il


riferimento all’errore fatale corrente mediante un’istanza della classe
System.Management.Automation. ErrorRecord, analizzata nel seguito del
capitolo.

Try/catch/finally
Il costrutto try/catch/finally consente di gestire in maniera più organica
i blocchi di codice destinati a intervenire in caso di errori fatali. È una
funzionalità introdotta con la versione 2.0 di PowerShell e, poiché
impiega la stessa sintassi del costrutto omonimo disponibile nei
linguaggi di derivazione C++, è il metodo di gestione degli errori
utilizzato più spesso dagli utenti con precedenti esperienze di
sviluppo.
A differenza dell’istruzione trap, che si attiva automaticamente in tutto
lo scope dove è utilizzata, il costrutto try/catcn/finally obbliga l’utente a
definire esplicitamente il blocco di comandi su cui il gestore delle
eccezioni può intervenire. Opzionalmente, inoltre, è possibile definire
un blocco di codice da eseguire al termine dell’intero costrutto, a
prescindere dall’esito del blocco interno.
Per monitorare gli errori di una serie di comandi utilizzando questo
costrutto è necessario far precedere al blocco, racchiuso tra le usuali
parentesi graffe, la keyword try; il codice di gestione dell’errore, se
presente, va anch’esso racchiuso tra parentesi graffe e fatto
precedere dalla keyword catch; l’eventuale codice da eseguire al
termine del blocco, infine, va racchiuso tra parentesi graffe e fatto
seguire alla keyword finally. Sia l’istruzione catch sia l’istruzione finally
e i relativi blocchi sono opzionali ma, affinché il costrutto sia
sintatticamente corretto, è necessario che sia presente almeno una
delle due; in ogni caso, l’ordine delle keyword non può variare
rispetto a quello illustrato.
La sintassi del costrutto try/catch/finally può essere schematizzata
così:
try
{
<Comandi>
}
catch
{
<Gestione errore>
}
try
{
<Comandi>
}
finally
{
<Codice finale>
}
try
{
<Comandi>
}
catch
{
<Gestione errore>
}
finally
{
<Codice finale>
}
Nello script che segue, per esempio, si usa il costrutto try/catch per
gestire un errore fatale generico:
PS C:\Users\ikmju> try
>> {
>> "Prima riga"
>> 3 / $null
>> "Terza riga"
>> }
>> catch
>> {
>> Write-Host "Catch generico"
>> }
>>
Prima riga
Catch generico

Come si può evincere dall’output dello script precedente, a


differenza del blocco trap, il blocco catch non continua
automaticamente l’esecuzione del codice ma la arresta; non è inoltre
possibile variare questo comportamento con le istruzioni continue e
break.

È possibile limitare il tipo di eccezione gestito dal blocco catch


specificando, in maniera analoga al blocco trap, la classe di
interesse. In questo modo è possibile impiegare più blocchi catch
all’interno dello stesso costrutto; tuttavia la shell, al verificarsi di un
errore fatale, utilizza il primo blocco catch compatibile con il tipo
dell’eccezione sollevata, in ordine di dichiarazione. La sintassi,
dunque, può essere schematizzata così:

[<Blocco try>]
catch [<Tipo eccezione1>]
{
<Gestione errore> }
catch [<Tipo eccezione2>] {
<Gestione errore> }
...
catch
{
<Gestione errore>
}
[<Blocco finally>]

Nello script che segue si impiegano tre blocchi catch distinti, in grado
di operare con tipi differenti di eccezione. Al verificarsi dell’errore
fatale, la shell utilizza il primo blocco compatibile con l’eccezione:
PS C:\Users\ikmju> try
>> {
>> "Prima riga"
>> 3 / $null
>> "Terza riga"
>> }
>> catch [IO.IOException]
>> {
>> Write-Host "Catch problemi I/O"
>> }
>> catch [DivideByZeroException]
>> {
>> Write-Host "Catch divisione"
>> }
>> catch
>> {
>> Write-Host "Catch generico"
>> }
>>
Prima riga
Catch divisione

Il blocco finally è usato spesso per liberare le risorse eventualmente


impegnate dal codice del blocco try, a prescindere dal verificarsi di
un errore fatale.
Nello script che segue, il blocco finally è utilizzato per eliminare il file
temporaneo creato all’interno del blocco try:
PS C:\Users\ikmju> $tempFileName = [IO.Path]::GetTempFileName()
PS C:\Users\ikmju>
PS C:\Users\ikmju> try
>> {
>> "Creazione di $tempFileName..."
>>
>> "Test file" > $tempFileName
>>
>> 3 / $null
>>
>> # ...
>> }
>> catch
>> {
>> Write-Host "Catch generico"
>> }
>> finally
>> {
>> "Rimozione di $tempFileName..."
>>
>> Remove-Item $tempFileName
>> }
>>
Creazione di C:\Users\ikmju\AppData\Local\Temp\tmp9DB0.tmp...
Catch generico
Rimozione di C:\Users\ikmju\AppData\Local\Temp\tmp9DB0.tmp...

Così come avviene all’interno del blocco trap, nel blocco catch la
variabile automatica $_ restituisce il riferimento all’errore fatale
corrente attraverso un’istanza della classe
System.Management.Automation.ErrorRecord, analizzata nel seguito del capitolo.

Cause degli errori fatali


Come anticipato, la shell identifica come errori fatali solo le tipologie
di problemi che in linea di massima impediscono al codice, allo script
o al cmdlet di proseguire con l’esecuzione.
Gli errori fatali possono essere generati da uno script quando:
• lo script contiene errori di sintassi; in questo caso la shell
solleva automaticamente un’eccezione di tipo
System.Management.Automation.ParseException;

• lo script o la funzione sollevano un’eccezione mediante


l’istruzione throw.
Le cause che implicano la generazione di un errore fatale all’interno
di un cmdlet, infine, sono imputabili a queste condizioni:
• all’interno del cmdlet si è verificata un’eccezione non gestita;
• il cmdlet ha richiamato esplicitamente l’API di generazione degli
errori fatali.

Errori non fatali


Contrapposti ai precedenti, gli errori non fatali segnalano la
comparsa di un problema di entità tendenzialmente minore, che non
inficia il proseguimento dell’esecuzione del resto dell’attività. Se è
auspicabile, infatti, che in capo ad alcuni problemi - come per
esempio l’incapacità di convalidare sintatticamente uno script - la
shell precluda l’esecuzione dell’intera attività, è altrettanto gradito
che per altri tipi di problemi, di entità minore, il sistema reagisca
continuando automaticamente l’esecuzione.
Nonostante anche gli errori non fatali trovino una corrispondenza
diretta con la gestione delle eccezioni, in presenza di un errore di
questo tipo la shell non varia, per impostazione predefinita, il flusso
di esecuzione del codice; il record dell’errore, come per gli errori
fatali, è un’istanza della classe System.Management.Automation.ErrorRecord ed è
ugualmente registrato nel canale standard error (stderr).
A differenza di quelle sollevate dagli script e dai cmdlet, le eccezioni
sollevate direttamente dai metodi e dalle proprietà delle classi del
framework Microsoft .NET sono considerate dalla shell errori non
fatali. Lo script che segue, per esempio, contiene un ciclo in cui
viene sollevata un’eccezione nel codice, attraverso l’istruzione throw;
come si può notare, il flusso termina al primo errore fatale:
PS C:\Users\ikmju> 1, 2, 3 | ForEach-Object { $_; if ($_ -eq 2) { throw } }
1
2
ScriptHalted
In riga:1 car:53
+ 1, 2, 3 | ForEach-Object { $_; if ($_ -eq 2) { throw <<<< } }
+ CategoryInfo : OperationStopped: (:) [], RuntimeException
+ FullyQualifiedErrorId : ScriptHalted

In quest’altro script, invece, si induce il metodo statico Parse() di


System.Int32 a generare un’eccezione a causa dell’input scorretto.

In questo caso, come si può notare, il flusso di esecuzione dello


script continua (si noti l’ultima riga di output):
PS C:\Users\ikmju> 1, 'a', 3 | ForEach-Object { [int]::Parse($_) }
1
Eccezione durante la chiamata di "Parse" con "1" argomento/i: "Formato della
stringa di input non corretto."
In riga:1 car:42
+ 1, 'a', 3 | ForEach-Object { [int]::Parse <<<< ($_) }
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
3

Diversamente dagli errori fatali, quelli non fatali non sono gestibili
attraverso i costrutti trap e try/catch/finally illustrati in precedenza.

Write-Error
Il cmdlet write-Error consente a uno script di generare errori non fatali.
L’impiego più semplice del comando consiste nello specificare una
stringa con il messaggio di errore attraverso il parametro posizionale
-Message: in tal caso la shell genera automaticamente un’eccezione di
tipo Microsoft.PowerShell.Commands.WriteErrorException. In alternativa, è
possibile fornire al comando un’eccezione, tramite il parametro -
Exception.

La sintassi di base del comando è semplice e segue questo schema:


Write-Error <Messaggio>
Write-Error -Exception <Eccezione>
Lo script che segue, per esempio, è una revisione di un blocco
precedente dove è stata sostituita la generazione di un errore fatale
ed è stata inserita la generazione di un errore non fatale, attraverso il
cmdlet Write-Error:
PS C:\Users\ikmju> 1, 2, 3 | ForEach-Object { $_; if ($_ -eq 2) { Write-Error
"BOOOM!" } }
1
2
1, 2, 3 | ForEach-Object { $_; if ($_ -eq 2) { Write-Error "BOOOM!" } } : BOOOM!
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException
3

Variare il comportamento nei cmdlet


All’interno dei cmdlet, la generazione di errori non fatali è piuttosto
comune. In molti casi, infatti, gli sviluppatori della shell hanno
ritenuto opportuno evitare di variare il flusso di esecuzione delle
attività a causa di un problema di entità minore.
Nello script che segue, per esempio, si creano dapprima due file di
testo e poi si chiede al cmdlet Remove-Item di eliminare quelli più un file
inesistente. Il risultato è che Remove-Item genera un errore non fatale
per quest’ultimo file, ma elimina gli altri elementi, di fatto
proseguendo con l’esecuzione:
PS C:\Users\ikmju> "Test" > "DeleteMe.txt"
PS C:\Users\ikmju> "Test" > "DeleteMe2.txt"
PS C:\Users\ikmju> Remove-Item DeleteMe.txt, FakeFakeFake.txt, DeleteMe2.txt
Remove-Item : Impossibile trovare il percorso 'C:\Users\ikmju\FakeFakeFake.txt'
perché
non esiste.
In riga:1 car:12
+ Remove-Item <<<< DeleteMe.txt, FakeFakeFake.txt, DeleteMe2.txt
+ CategoryInfo : ObjectNotFound:
(C:\Users\ikmju\FakeFakeFake.txt:String)
[Remove-Item], ItemNotFoundException
+ FullyQualifiedErrorId :
PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand
PS C:\Users\ikmju> @(Get-ChildItem DeleteMe?.txt).Count
0

Grazie alla struttura orientata agli oggetti, in PowerShell il


comportamento degli errori non fatali può essere variato a ogni
chiamata di cmdlet. Mediante il parametro -ErrorAction, spesso
abbreviato con l’alias -EA, infatti, è possibile scegliere come qualsiasi
comando debba rispondere alla presenza di un errore non fatale. I
valori contemplati dal parametro sono riportati nella Tabella 28.1.

Tabella 28.1 – I valori ammessi dal parametro -ErrorAction.


Valore Descrizione
Valore predefinito, visualizza il messaggio di errore e
Continue
prosegue con l’esecuzione dell’attività
Prosegue con l’esecuzione dell’attività senza
SilentlyContinue
visualizzare il messaggio di errore
Visualizza il messaggio di errore e arresta
Stop l’esecuzione dell’attività, in maniera simile agli errori
fatali
Visualizza il messaggio di errore, ma chiede all’utente
Inquire
se proseguire con l’attività o terminarla

Nel blocco che segue, per esempio, lo script precedente è rivisto


affinché richieda conferma all’utente prima di proseguire, in seguito
all’errore non fatale:
PS C:\Users\ikmju> "Test" > "DeleteMe.txt"
PS C:\Users\ikmju> "Test" > "DeleteMe2.txt"
PS C:\Users\ikmju> Copy-Item DeleteMe.txt, FakeFakeFake.txt, DeleteMe2.txt C:\Temp -
EA Inquire

Conferma
Impossibile trovare il percorso 'C:\Users\ikmju\FakeFakeFake.txt' perché non esiste.
[S] Sì [T] Sì a tutti [I] Interrompi comando [O] Sospendi [?] Guida (il valore
predefinito è "S"): S
Copy-Item : Impossibile trovare il percorso 'C:\Users\ikmju\FakeFakeFake.txt' perché
non esiste.
In riga:1 car:10
+ Copy-Item <<<< DeleteMe.txt, FakeFakeFake.txt, DeleteMe2.txt C:\Temp -EA Inquire
+ CategoryInfo : ObjectNotFound:
(C:\Users\ikmju\FakeFakeFake.txt:String)
[Copy-Item], ItemNotFoundException
+ FullyQualifiedErrorId :
PathNotFound,Microsoft.PowerShell.Commands.CopyItemCommand

Cause degli errori non fatali


Come anticipato, la shell identifica come errori non fatali solo le
tipologie di problemi che in linea di massima non precludono il
proseguimento dell’attività nel suo complesso.
Riassumendo quanto già evidenziato in precedenza, dunque, gli
errori non fatali possono essere generati da uno script quando:
• lo script richiama un metodo o una proprietà del framework
Microsoft .NET che solleva internamente un’eccezione;
• lo script richiama il cmdlet Write-Error.
Come è stato illustrato in precedenza, inoltre, i cmdlet sono
progettati per generare errori non fatali in tutte le circostanze che lo
richiedono.

Strutture degli errori


Come anticipato, il meccanismo di gestione degli errori della shell si
basa ampiamente su quello delle eccezioni del framework Microsoft
.NET. PowerShell, d’altra parte, racchiude ogni eccezione catturata
dagli script all’interno di istanze della classe
System.Management.Automation.ErrorRecord, creata per contenere un ventaglio

di informazioni più ampio rispetto a quello disponibile con le mere


eccezioni. La Tabella 28.2 riporta le proprietà più interessanti di
questo tipo.

Tabella 28.2 – Alcune proprietà della classe


System.Management.Automation. ErrorRecord.
Proprietà Descrizione
CategoryInfo Contiene informazioni strutturate sulla tipologia dell’errore
Exception Ritorna l’eccezione originale, alla base dell’errore
InvocationInf Ritorna informazioni dettagliate sul comando e sulla riga
o dove l’errore si è verificato
Ritorna l’oggetto che il comando stava elaborando
TargetObject
quando si è verificato l’errore

Ogni oggetto relativo a un’eccezione, a sua volta, contiene alcune


informazioni utili per recuperare il dettaglio del problema, all’interno
di un cmdlet o di un oggetto del framework Microsoft .NET.
La Tabella 28.3 riporta le proprietà più interessanti della classe
System.Exception, da cui tutte le eccezioni derivano.

Tabella 28.3 – Alcune proprietà della classe System.Exception.


Proprietà Descrizione
Message Ritorna il messaggio testuale dell’eccezione
Ritorna il nome del modulo in cui si è verificato il
Source
problema
Ritorna l’eventuale eccezione interna (le eccezioni
InnerExceptio
possono essere strutturate una dentro l’altra, su più
n
livelli)

Nello script che segue, si utilizza la proprietà InvocationInfo per


recuperare informazioni sulla riga che ha generato un errore fatale:
PS C:\Users\ikmju> try
>> {
>> 3 / $null
>> }
>> catch
>> {
>> "Errore provocato da: {0}" -f $_.InvocationInfo.Line
>> }
>>
Errore provocato da: 3 / $null

In quest’altro esempio, invece, si accede alla proprietà Source di


Exception dell’errore per recuperare il modulo in cui si genera

l’eccezione:
PS C:\Users\ikmju> try
>> {
>> 3 / $null
>> }
>> catch
>> {
>> "Errore generato in: {0}" -f $_.Exception.Source
>> }
>>
Errore generato in: System-Management-Automation

NO Per una trattazione dettagliata delle eccezioni disponibili nel framework Microsoft
TA .NET si rimanda al sito Microsoft MSDN (http://msdn.microsoft.com).

Variabili automatiche e preferenze


PowerShell mette a disposizione dell’utente diversi strumenti in
grado di velocizzare il recupero e la gestione degli errori e di
impostare le preferenze relative alla risposta predefinita del sistema
rispetto agli errori non fatali.
$Error
La variabile automatica $Error contiene la collezione degli errori
generati durante la sessione corrente, ordinati a partire dal più
recente. A prescindere dal fatto che sia visualizzato o meno a video,
qualsiasi errore - fatale o non fatale - all’atto della generazione è
automaticamente memorizzato in questa collezione, in maniera tale
che l’utente possa consultarla rapidamente e verificare così gli
eventuali problemi emersi. Nello script che segue, per esempio, si
genera dapprima un errore fatale tramite l’istruzione throw, poi se ne
genera uno non fatale grazie al cmdlet Remove-Item, evitando oltretutto
di visualizzare l’errore a video. In seguito si utilizza la variabile
automatica $Error per recuperare le istanze di errore generate nel
corso della sessione:
PS C:\Users\ikmju> throw "Test"
Test
In riga:1 car:6
+ throw <<<< "Test"
+ CategoryInfo : OperationStopped: (Test:String) [],
RuntimeException
+ FullyQualifiedErrorId : Test

PS C:\Users\ikmju> Remove-Item fakefakefake.txt -EA SilentlyContinue


PS C:\Users\ikmju> $Error
Remove-Item : Impossibile trovare il percorso 'C:\Users\ikmju\fakefakefake.
txt' perché non esiste.
In riga:1 car:12
+ Remove-Item <<<< fakefakefake.txt -EA SilentlyContinue
+ CategoryInfo : ObjectNotFound: (C:\Users\ikmju\fakefakefake.
txt:String) [Remove-Item], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.
RemoveItemCommand

Test
In riga:1 car:6
+ throw <<<< "Test"
+ CategoryInfo : OperationStopped: (Test:String) [], RuntimeException
+ FullyQualifiedErrorId : Test

$MaximumErrorCount
La variabile automatica $MaximumErrorCount è utilizzata per impostare il
numero massimo di elementi contenuti da $Error: per ragioni di
performance, infatti, vengono mantenuti in memoria solo gli ultimi
errori generati, in base a tale valore.
Per impostazione predefinita, il valore iniziale di $MaximumErrorCount è
pari a 256:
PS C:\Users\ikmju> $MaximumErrorCount
256

$LastExìtCode
La variabile $LastExitCode è automaticamente valorizzata quando si
lancia all’interno della shell un eseguibile a console; in tal caso, il
valore della variabile è pari al codice numerico di uscita ritornato
dall’eseguibile. Il lettore con precedenti esperienze con altre shell
potrebbe ritrovare nell’impiego di questa variabile alcune analogie
sia con la variabile ERRORLEVEL di COMMAND.COM e CMD nei sistemi Windows
sia, per esempio, con l’istruzione $? di bash o l’istruzione ? di ksh nei
sistemi *nix.
Nello script che segue, si richiama l’interprete dei comandi CMD,
chiedendo di lanciare il comando DIR all’interno di un drive
inesistente. In questo caso CMD ritorna un codice di uscita pari a 1, che
indica un problema.
PS C:\Users\ikmju> cmd.exe /C "DIR Z:\"
Dispositivo non pronto.
PS C:\Users\ikmju> $LASTEXITCODE
1

Non esiste alcuna restrizione sui possibili valori ritornabili dagli


applicativi e ogni software gestisce autonomamente i codici che
indicano un avvenuto successo rispetto a quelli che indicano un
fallimento. La maggior parte degli applicativi a console, tuttavia,
ritorna un valore pari a o quando l’operazione richiesta è andata a
buon fine.

NO Per conoscere i codici di uscita degli applicativi a console è necessario consultare


TA i rispettivi manuali o le guide in linea.

$?
La variabile automatica $? contiene un valore logico che varia in base
allo stato dell’ultima operazione eseguita: un valore pari a $true indica
che il comando precedente è andato a buon fine, mentre un valore
pari a $false0 segnala l’opposto.
La shell assegna il valore $false alla variabile $? appena viene rilevato
un errore - fatale o meno - da parte dello script, del cmdlet chiamato
oppure di un oggetto del framework Microsoft .NET; nel caso di
eseguibili a console, inoltre, la shell imposta automaticamente il
valore $? a $false se questi ritornano un codice di uscita numerico
diverso da 0.
Nello script che segue, per esempio, si utilizza la variabile $? per
verificare che la rimozione di un file sia andata a buon fine e, in caso
contrario, si mostra a video un messaggio:
PS C:\Users\ikmju> Remove-Item fakefakefake.txt -EA SilentlyContinue
PS C:\Users\ikmju> if (-not $?) { "Rimozione fallita!" }
Rimozione fallita!

Diversamente da quanto avviene con l’omonima istruzione presente


nella shell bash, questa variabile non contiene il codice numerico di
uscita dell’ultimo eseguibile a console lanciato. Per risalire a tale
valore è sufficiente utilizzare la variabile automatica $LastExitCode,
descritta nel paragrafo precedente.

$ErrorActionPreference
Attraverso la variabile automatica $ErrorActionPreference è possibile
modificare la gestione predefinita degli errori non fatali all’interno dei
cmdlet. Il valore contenuto in questa variabile, infatti, fornisce
automaticamente ai cmdlet la preferenza per il parametro -ErrorAction
nel caso quest’ultimo non sia esplicitamente fornito dall’utente.
Come ci si potrebbe aspettare, dunque, questa variabile può
assumere gli stessi valori del parametro correlato, riportati nella
Tabella 28.1.
Come dimostra lo script che segue, per esempio, impostando il
valore di $ErrorAc-tionPreference a Stop è possibile modificare il flusso
predefinito di gestione degli errori non fatali dei cmdlet affinché ogni
problema rilevato generi un errore fatale e termini l’esecuzione del
comando (si noti la presenza del file residuo, al termine della
chiamata a Remove-Item):
PS C:\Users\ikmju> $ErrorActionPreference = 'Stop'
PS C:\Users\ikmju>
PS C:\Users\ikmju> "Test" > "DeleteMe.txt"
PS C:\Users\ikmju> "Test" > "DeleteMe2.txt"
PS C:\Users\ikmju> Remove-Item DeleteMe.txt, FakeFakeFake.txt, DeleteMe2.txt
Remove-Item : Cannot find path 'C:\Users\ikmju\FakeFakeFake.txt' because it does not
exist.
At line:1 char:12
+ Remove-Item <<<< DeleteMe.txt, FakeFakeFake.txt, DeleteMe2.txt
+ CategoryInfo : ObjectNotFound:
(C:\Users\ikmju\FakeFakeFake.txt:String)
[Remove-Item], ItemNotFoundExce
ption
+ FullyQualifiedErrorId :
PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

PS C:\Users\ikmju> @(Get-ChildItem DeleteMe?.txt).Count


1

Potrebbero piacerti anche