Articles

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:\FolderC:\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 chiamato foreach. A seconda di come viene chiamato il termine “foreach”, viene eseguita l’istruzione foreach, oppure viene eseguito il ForEach-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 di ForEach-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.

Lista di nomi di server in un file di testo
Lista di nomi di server in un file di testo

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:

Looping Test-Connection attraverso una lista di nomi di server
Looping Test-Connessione attraverso una lista di nomi di server

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.

Sub-directory sotto C:\ARCHIVE_VOLUMES
Sub-directory sotto C:\ARCHIVE_VOLUMES

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.

Un file di testo viene creato in ogni sotto-directory
Un file di testo viene creato in ogni sotto-directory

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.

Usando foreach per eseguire il loop dei file e leggerne il contenuto.
Usando foreach per eseguire il loop dei file e leggerne il contenuto.

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.

Usare il ciclo ForEach-Object per avviare i servizi
Usando il ciclo ForEach-Object per avviare i servizi

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

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *