Adam the Automator
Quando inizi a scrivere script PowerShell, inevitabilmente arriverai a un punto in cui avrai bisogno di elaborare più elementi da una collezione. È allora che avrai bisogno di scavare nei cicli foreach di PowerShell e imparare di cosa si tratta.
Quasi tutti i linguaggi di programmazione hanno un costrutto chiamato loop; PowerShell non è diverso. Uno dei tipi più popolari di cicli in PowerShell è il ciclo foreach. Nella sua forma più elementare, un ciclo foreach legge un’intera collezione di elementi e, per ogni elemento, esegue un qualche tipo di codice.
Uno degli aspetti più confusi del ciclo foreach di PowerShell per i principianti sono tutte le opzioni che avete. Non c’è solo un modo per elaborare ogni elemento di una collezione; ce ne sono tre!
In questo articolo, imparerai come funziona ogni tipo di ciclo foreach e quando usarne uno piuttosto che un altro. Quando avrai finito questo articolo, avrai una buona comprensione di ogni tipo di ciclo foreach.
Tabella dei contenuti
Fondamenti di PowerShell ForEach Loop
Uno dei tipi più comuni di loop che userai in PowerShell è il tipo di loop foreach
. Un ciclo foreach
legge un insieme di oggetti (iterazione) e si completa quando ha finito con l’ultimo. La collezione di oggetti che vengono letti è tipicamente rappresentata da un array o una hashtable.
NOTA: Il termine iterazione è un termine di programmazione che si riferisce ad ogni esecuzione di un ciclo. Ogni volta che un loop completa un ciclo, questo è chiamato un’iterazione. L’atto di eseguire un ciclo su un insieme di oggetti è comunemente chiamato iterazione sull’insieme.
Forse avete bisogno di creare un file di testo attraverso alcune cartelle sparse in un file system. Diciamo che i percorsi delle cartelle sono C:\Folder
C:\Program Files\Folder2
e C:\Folder3
. Senza un ciclo, dovremmo fare riferimento al Add-Content
cmdlet tre volte.
Add-Content -Path 'C:\Folder\textfile.txt' -Value 'This is the content of the file'Add-Content -Path 'C:\Program Files\Folder2\textfile.txt' -Value 'This is the content of the file'Add-Content -Path 'C:\Folder2\textfile.txt' -Value 'This is the content of the file'
Qual è l’unica differenza tra ognuno di questi riferimenti ai comandi? È il valore Path
. Il valore per Path
è l’unico valore che cambia tra ognuno di questi.
Stai duplicando un sacco di codice. Stai perdendo tempo a scrivere e ti stai aprendo a problemi lungo la strada. Invece, dovreste creare un “set” che include solo tutti gli elementi che stanno cambiando. Per questo esempio, useremo un array.
$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
Ora avete ognuno di questi percorsi memorizzati in un singolo “set” o array. Ora siete pronti ad usare un ciclo per iterare su ognuno di questi elementi. Prima di farlo, però, è un buon momento per menzionare un argomento che di solito fa inciampare chi è nuovo di PowerShell. A differenza di altri tipi di cicli, i cicli foreach
non sono tutti uguali.
Ci sono tecnicamente tre tipi di cicli foreach
in PowerShell. Anche se ognuno è simile da usare, è importante capire la differenza.
La dichiarazione foreach
Il primo tipo di foreach
loop è una dichiarazione. foreach
è una parola chiave interna di PowerShell che non è una cmdlet né una funzione. L’istruzione foreach
è sempre usata nella forma: foreach ($i in $array)
.
Usando l’esempio sopra, la variabile $i
rappresenta l’iteratore o il valore di ogni elemento in $path
mentre itera su ogni elemento dell’array.
Nota che la variabile iteratore non deve essere necessariamente
$i
. Il nome della variabile può essere qualsiasi cosa.
Nell’esempio seguente, si può realizzare lo stesso compito della ripetizione del riferimento Add-Content
facendo così:
# Create an array of folders$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')# Perform iteration to create the same file in each folderforeach ($i in $folders) { Add-Content -Path "$i\SampleFile.txt" -Value "This is the content of the file"}
L’istruzione foreach
è nota per essere un’alternativa più rapida rispetto all’utilizzo del ForEach-Object
cmdlet.
Il CmdLet ForEach-Object
Se foreach
è una dichiarazione e può essere usata solo in un modo, ForEach-Object
è un cmdlet con parametri che può essere impiegato in molti modi diversi. Come l’istruzione foreach
, il ForEach-Object
cmdlet può iterare su un insieme di oggetti. Solo che questa volta, passa l’insieme di oggetti e l’azione da intraprendere su ogni oggetto come parametro, come mostrato di seguito.
$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')$folders | ForEach-Object (Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file")
Nota: Per confondere le cose, la
ForEach-Object
cmdlet ha un alias chiamatoforeach
. A seconda di come viene chiamato il termine “foreach”, viene eseguita l’istruzioneforeach
, oppure viene eseguito ilForEach-Object
.Un buon modo per differenziare queste situazioni è notare se il termine “foreach” è seguito da
($someVariable in $someSet)
. Altrimenti, in qualsiasi altro contesto, l’autore del codice sta probabilmente usando l’alias diForEach-Object
.
Il metodo foreach()
Uno dei più nuovi foreach
loop è stato introdotto in PowerShell v4 chiamato metodo foreach()
. Questo metodo esiste su un oggetto array o collection. Il metodo foreach()
ha un parametro standard di blocco di script che contiene le azioni da intraprendere su ogni iterazione, proprio come gli altri.
$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')$folders.ForEach({Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file"})
La differenza più significativa con il metodo foreach()
è come funziona sotto il cappuccio.
Utilizzare il metodo
foreach()
è considerevolmente più veloce e lo è notevolmente su grandi set. Si raccomanda di usare questo metodo rispetto agli altri due, se possibile.
Mini-Progetto: Looping su un insieme di nomi di server
Uno degli usi più comuni per un loop in PowerShell è la lettura di un insieme di server da qualche fonte e l’esecuzione di qualche azione su ciascuno di essi. Per il nostro primo mini-progetto, costruiamo del codice che ci permetterà di leggere i nomi dei server (uno per riga) da un file di testo e di eseguire il ping di ogni server per determinare se sono online o meno.
Per questo flusso di lavoro, quale tipo di ciclo pensate possa funzionare meglio?
Nota che ho menzionato la parola “set” come “set di server”. Questo è un indizio. Avete già un numero specificato di nomi di server, e vorreste eseguire qualche azione su ognuno di essi. Questa sembra un’ottima occasione per provare il ciclo PowerShell più comunemente usato; il ciclo foreach
.
Il primo compito è quello di creare un insieme di nomi di server nel codice. Il modo più comune per farlo è creare un array. Per nostra fortuna Get-Content
, per default, restituisce un array con ogni elemento dell’array rappresentato da una singola riga nel file di testo.
Prima di tutto, create un array di tutti i miei nomi di server e chiamatelo $servers
.
$servers = Get-Content .\servers.txt
Ora che avete creato l’array, dovrete confermare se ogni server è online o meno. Un ottimo cmdlet per testare la connessione di un server si chiama Test-Connection
. Questo cmdlet esegue alcuni test di connessione su un computer per vedere se è online o meno.
Eseguendo Test-Connection
all’interno di un ciclo foreach
per leggere ogni riga di file, è possibile passare ogni nome di server rappresentato dalla variabile $server
a Test-Connection
. Questo è il modo in cui PowerShell esegue il loop attraverso un file di testo. Potete vedere un esempio di come funziona qui sotto.
foreach ($server in $servers) {try {$null = Test-Connection -ComputerName $server -Count 1 -ErrorAction STOPWrite-Output "$server - OK"}catch {Write-Output "$server - $($_.Exception.Message)"}}
Quando il ciclo foreach
viene eseguito, avrà un aspetto simile a questo:
Ora hai testato con successo la connessione di un file di testo pieno di nomi di server! A questo punto, puoi aggiungere o rimuovere nomi di server all’interno del file di testo a tuo piacimento senza cambiare il codice.
Hai messo in pratica con successo ciò che abbiamo predicato, il metodo DRY.
Esempi di ForEach PowerShell
Guardiamo alcuni esempi di come utilizzare il ciclo ForEach. Questi sono basati su casi d’uso reali che mostrano il concetto che puoi modificare per adattarlo alle tue esigenze.
Esempio 1: creare un file in ogni sottocartella di una directory usando l’istruzione ForEach
Questo esempio dimostra l’uso comune di PowerShell foreach in una directory.
Supponiamo che ci siano dieci sottocartelle all’interno della cartella C:\ARCHIVE_VOLUMES
. Ogni sottocartella rappresenta un volume di archivio di cui viene eseguito il backup giornaliero. Dopo che ogni backup è completo, un file chiamato BackupState.txt
viene creato all’interno di ogni cartella contenente la data in cui è stato eseguito il backup.
Si può vedere un esempio di ciò che potrebbe apparire di seguito.
Lo script sottostante esegue tre azioni:
- ottenere l’elenco di tutte le sotto-cartellecartelle all’interno di
C:\ARCHIVE_VOLUMES
- circa ogni cartella
- crea un file di testo chiamato
BackupState.txt
contenente la data e l’ora attuali come valore
L’esempio qui sotto sta usando l’istruzione foreach
.
# Define the TOP-level folder$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"# Get all sub folders recursively$Child_Folders = Get-ChildItem -Path $TOP_FOLDER -Recurse | Where-Object { $_.PSIsContainer -eq $true }# Create a text file in each sub-folder and add the current date/time as value.foreach ($foldername in $Child_Folders.FullName) { (get-date -Format G) | Out-File -FilePath "$($foldername)\BackupState.txt" -Force}
Utilizzando la cmdlet Get-ChildItem
, è possibile confermare che i file sono stati creati o aggiornati all’interno di ogni sottocartella.
Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include backupstate.txt | Select-Object Fullname,CreationTime,LastWriteTime,Length
Lo screenshot sottostante mostra l’output dello script che mostra tutti i BackupState.txt
file trovati in ogni sottocartella.
Esempio 2: Leggere il contenuto di ogni file di testo nelle sottodirectory
In seguito, per dimostrare l’uso di PowerShell foreach in una directory, lo script seguente leggerà ogni BackupState.txt
file creato nell’esempio 1.
- Trova ricorsivamente tutti i
BackupState.txt
file dentro ogni sottodirectory. - Utilizzando l’istruzione
foreach
, leggi ogni file di testo per ottenere il valore “last backup time”. - Visualizza il risultato sullo schermo.
## Find all BackupState.txt files in C:\ARCHIVE_VOLUMES$files = Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include 'BackupState.txt' | Select-Object DirectoryName,FullName## Read the contents of each fileforeach ($file in $files) { Write-Output ("$($file.DirectoryName) last backup time - " + (Get-Content $file.FullName))}
Una volta che lo script viene eseguito nella tua sessione PowerShell, dovresti vedere un output simile allo screenshot sottostante. Questo mostra che PowerShell esegue il loop dei file, ne legge il contenuto e visualizza l’output.
Esempio 3: Ottenere i servizi e avviarli usando il CmdLet ForEach-Object
Gli amministratori di sistema hanno spesso bisogno di ottenere lo stato dei servizi e di attivare un flusso di lavoro manuale o automatico per rimediare a qualsiasi servizio fallito. Diamo un’occhiata allo script di esempio che utilizza la cmdlet ForEach-Object
.
Per questo esempio, lo script qui sotto farà quanto segue:
- Prendi un elenco di servizi che sono configurati per un avvio automatico ma che attualmente non sono in stato di esecuzione.
- Poi, le voci dell’elenco vengono inviate alla cmdlet
ForEach-Object
per tentare di avviare ogni servizio. - Un messaggio di successo o di fallimento viene visualizzato a seconda del risultato del
Start-Service
comando.
## Get a list of automatic services that are stopped.$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}## Pass each service object to the pipeline and process them with the Foreach-Object cmdlet$services | ForEach-Object { try { Write-Host "Attempting to start '$($.DisplayName)'" Start-Service -Name $.Name -ErrorAction STOP Write-Host "SUCCESS: '$($.DisplayName)' has been started" } catch { Write-output "FAILED: $($.exception.message)" }}
Quando lo script viene eseguito, sarà come la schermata di output qui sotto. Come potete vedere, ogni servizio ha ricevuto un comando di avvio. Alcuni sono stati avviati con successo, mentre altri non sono riusciti a partire.
Esempio 4: Leggere dati da CSV usando il metodo ForEach()
Utilizzare dati da file CSV è popolare tra gli amministratori di sistema. Mettere i record all’interno di un file CSV rende facile l’esecuzione di operazioni di massa utilizzando la combinazione Import-CSV
e ForEach
. Questa combinazione è comunemente usata per creare più utenti in Active Directory.
In questo prossimo esempio, si presume che tu abbia un file CSV con due colonne – Nome e Cognome. Questo file CSV dovrebbe essere popolato con i nomi dei nuovi utenti da creare. Il file CSV dovrebbe essere qualcosa di simile a questo.
"Firstname","Lastname""Grimm","Reaper""Hell","Boy""Rick","Rude"
Ora per il prossimo blocco di script. Per prima cosa, importate il file CSV passando il percorso del contenuto alla cmdlet Import-CSV
. Poi, usando il metodo foreach()
, esegui il loop dei nomi e crea i nuovi utenti in Active Directory.
# Import list of Firstname and Lastname from CSV file$newUsers = Import-Csv -Path .\Employees.csvAdd-Type -AssemblyName System.Web# Process the list$newUsers.foreach( { # Generate a random password $password = ::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3) $secPw = ConvertTo-SecureString -String $password -AsPlainText -Force # Formulate a username $userName = '{0}{1}' -f $_.FirstName.Substring(0, 1), $_.LastName # Build new user attributes $NewUserParameters = @{ GivenName = $_.FirstName Surname = $_.LastName Name = $userName AccountPassword = $secPw } try { New-AdUser @NewUserParameters -ErrorAction Stop Write-Output "User '$($userName)' has been created." } catch { Write-Output $_.Exception.Message } })
Una volta eseguito, dovresti aver creato un utente AD per ogni riga del file CSV!
Riassunto
La logica dietro al ciclo foreach in PowerShell non è diversa da quella di altri linguaggi di programmazione. Cambia solo con il modo in cui viene usato e quale variante del ciclo foreach viene scelta per qualsiasi compito specifico.
In questo articolo, hai imparato i diversi tipi di cicli foreach disponibili in PowerShell, e cosa considerare per decidere quale usare. Hai anche visto i tre tipi di cicli foreach in azione usando diversi scenari di esempio.
Ulteriori letture
- Gestire i file CSV in PowerShell con Import-Csv (ciclo foreach)
- New-ADUser: Creazione di utenti Active Directory con PowerShell
- Basics of PowerShell Looping: Foreach | Scripting Blog