Loop ForEach di PowerShell: Tipologie e Migliori Pratiche

Quando inizi a scrivere script PowerShell, inevitabilmente arriverai a un punto in cui dovrai elaborare più elementi da una raccolta. È in questo momento che dovrai approfondire i cicli foreach di PowerShell e capire di cosa si tratta.

Quasi tutti i linguaggi di programmazione hanno una costruzione chiamata cicli; PowerShell non fa eccezione. Uno dei tipi di cicli più popolari in PowerShell è il ciclo foreach. Nel modo più basilare, un ciclo foreach legge un’intera raccolta di elementi e, per ogni elemento, esegue un certo tipo di codice.

Uno degli aspetti più confusi del ciclo foreach di PowerShell per i principianti è la varietà di opzioni disponibili. Non c’è un solo modo per elaborare ciascun elemento in una raccolta; ce ne sono tre!

In questo articolo, imparerai come funziona ciascun tipo di ciclo foreach e quando usarne uno rispetto agli altri. Al termine di questo articolo, avrai una buona comprensione di ciascun tipo di ciclo foreach.

Principi di base del ciclo PowerShell ForEach

Uno dei tipi di cicli più comuni che userai in PowerShell è il ciclo di tipo foreach. Un ciclo foreach legge un insieme di oggetti (itinerari) e si completa quando ha finito con l’ultimo. La raccolta di oggetti letti è tipicamente rappresentata da un array o una tabella hash.

NOTA: Il termine iterazione è un termine di programmazione che si riferisce a ogni esecuzione di un ciclo. Ogni volta che un ciclo completa un ciclo, questo viene chiamato un’iterazione. L’atto di eseguire un ciclo su un insieme di oggetti è comunemente definito come iterare sull’insieme.

Forse hai bisogno di creare un file di testo attraverso alcune cartelle distribuite in un sistema di file. Diciamo che i percorsi delle cartelle sono C:\Folder, C:\Program Files\Folder2 e C:\Folder3. Senza un ciclo, dovremmo fare riferimento al cmdlet Add-Content 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’unico elemento che cambia tra ciascuno di questi riferimenti al comando? È il valore di Path. Il valore di Path è l’unico valore che cambia tra questi.

Stai duplicando molto codice. Stai perdendo tempo a digitare e ti stai aprendo a problemi in futuro. Invece, dovresti creare un “set” che includa solo gli elementi che stanno cambiando. Per questo esempio, useremo un array.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

Ora hai ciascuno di questi percorsi memorizzati in un singolo “set” o array. Ora sei pronto per utilizzare un ciclo per iterare su ciascuno di questi elementi. Prima di farlo, è un buon momento per menzionare un argomento che di solito ingarbuglia chi è nuovo a 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 ciclo foreach è una dichiarazione. foreach è una parola chiave interna di PowerShell che non è un cmdlet né una funzione. La dichiarazione foreach viene sempre utilizzata nella forma: foreach ($i in $array).

Utilizzando l’esempio sopra, la variabile $i rappresenta l’iteratore o il valore di ogni elemento in $path mentre itera su ogni elemento nell’array.

Nota che la variabile iteratore non deve necessariamente essere $i. Il nome della variabile può essere qualsiasi cosa.

Nell’esempio sottostante, puoi ottenere lo stesso risultato ripetendo il riferimento a Add-Content facendo così:

# Crea un array di cartelle
$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

# Esegue l'iterazione per creare lo stesso file in ogni cartella
foreach ($i in $folders) {
    Add-Content -Path "$i\SampleFile.txt" -Value "This is the content of the file"
}

Lo foreach statement è noto come un’alternativa più veloce rispetto all’uso del ForEach-Object cmdlet.

Il CmdLet ForEach-Object

Se foreach è uno statement e può essere utilizzato solo in un modo, ForEach-Object è un cmdlet con parametri che può essere impiegato in molti modi diversi. Come lo statement foreach, il cmdlet ForEach-Object può iterare su un insieme di oggetti. Solo che questa volta, passa tale insieme di oggetti e l’azione da intraprendere su ciascun 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 complicare le cose, il cmdlet ForEach-Object ha un alias chiamato foreach. A seconda di come viene chiamato il termine “foreach”, viene eseguito lo statement foreach oppure viene eseguito il ForEach-Object.

A good way to differentiate these situations is to notice if the term “foreach” is followed by ($someVariable in $someSet). Otherwise, in any other context, the code author is probably using the alias for ForEach-Object.

Il Metodo foreach()

Uno dei più nuovi cicli foreach è stato introdotto in PowerShell v4 chiamato metodo foreach(). Questo metodo esiste su un oggetto array o collezione. Il metodo foreach() ha un parametro standard di un blocco di script che contiene le azioni da eseguire in 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 del metodo foreach() è il suo funzionamento interno.

Utilizzare il metodo foreach() è considerevolmente più veloce e notevolmente migliore per set di grandi dimensioni. Se possibile, è consigliato utilizzare questo metodo rispetto agli altri due.

Mini-Progetto: Scorrere un insieme di nomi di server

Uno degli usi più comuni per un ciclo in PowerShell è leggere un insieme di server da una certa fonte e eseguire un’azione su ognuno di essi. Per il nostro primo mini-progetto, costruiamo un codice che ci permetta di leggere i nomi dei server (uno per riga) da un file di testo e fare il ping di ogni server per determinare se sono online o meno.

List of server names in a text file

Per questo flusso di lavoro, quale tipo di ciclo pensi che funzioni meglio?

Nota che ho menzionato la parola “insieme”, come “insieme di server”. Questo è un indizio. Hai già un numero specificato di nomi di server e desideri eseguire un’azione su ognuno di essi. Questo sembra essere un’ottima occasione per provare il ciclo foreach più comune usato in PowerShell.

Il primo compito è creare un insieme di nomi server nel codice. Il modo più comune per farlo è creare un array. Per fortuna, Get-Content, per impostazione predefinita, restituisce un array con ciascun elemento rappresentato da una singola riga nel file di testo.

Per prima cosa, crea un array di tutti i nomi dei miei server e chiamalo $servers.

$servers = Get-Content .\servers.txt

Ora che hai creato l’array, dovrai 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 del file, puoi passare ogni nome server rappresentato dalla variabile $server a Test-Connection. Questo è come PowerShell per ciclare attraverso un file di testo. Puoi vedere un esempio di come funziona qui sotto.

foreach ($server in $servers) {
	try {
		$null = Test-Connection -ComputerName $server -Count 1 -ErrorAction STOP
		Write-Output "$server - OK"
	}
	catch {
		Write-Output "$server - $($_.Exception.Message)"
	}
}

Quando il ciclo foreach viene eseguito, assomiglierà a qualcosa del genere:

Looping Test-Connection through a list of server names

Hai ora testato con successo la connessione di un file di testo pieno di nomi server! A questo punto, ora puoi aggiungere o rimuovere nomi server all’interno del file di testo a tuo piacimento senza modificare il codice.

Hai praticato con successo ciò che predicavamo, il metodo DRY.

Esempi di ForEach PowerShell

Diamo un’occhiata a qualche esempio su 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: Creazione di un file in ogni sottocartella in una directory utilizzando l’istruzione ForEach

Questo esempio illustra l’uso comune di PowerShell per ogni cartella in una directory.

Supponiamo che ci siano dieci sottocartelle all’interno della cartella C:\ARCHIVE_VOLUMES. Ogni sottocartella rappresenta un volume di archivio che viene eseguito il backup quotidianamente. Dopo ogni backup completato, viene creato un file chiamato BackupState.txt all’interno di ogni cartella contenente la data in cui è stato eseguito il backup.

Puoi vedere un esempio di come potrebbe apparire ciò di seguito.

Sub-directories under C:\ARCHIVE_VOLUMES

Lo script di seguito esegue tre azioni:

  • ottiene l’elenco di tutte le sottocartelle all’interno di C:\ARCHIVE_VOLUMES
  • scorre ogni cartella
  • crea un file di testo chiamato BackupState.txt contenente la data e l’ora corrente come valore

L’esempio di seguito utilizza l’istruzione foreach.

# Definire la cartella di livello superiore
$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"

# Ottenere tutte le sottocartelle in modo ricorsivo
$Child_Folders = Get-ChildItem -Path $TOP_FOLDER -Recurse | Where-Object { $_.PSIsContainer -eq $true }

# Creare un file di testo in ogni sottocartella e aggiungere la data/ora corrente come valore.
foreach ($foldername in $Child_Folders.FullName) {
   (get-date -Format G) | Out-File -FilePath "$($foldername)\BackupState.txt" -Force
}

Utilizzando il cmdlet Get-ChildItem, puoi confermare che i file sono stati creati o aggiornati all’interno di ciascuna delle sottocartelle.

Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include backupstate.txt | Select-Object Fullname,CreationTime,LastWriteTime,Length

Lo screenshot qui sotto mostra l’output dello script che visualizza tutti i file BackupState.txt trovati in ciascuna sottodirectory.

A text file is created in each sub-directory

Esempio 2: Lettura dei Contenuti di ciascun File di Testo nelle Sottodirectory

Successivamente, per dimostrare l’uso di PowerShell per ogni file in una directory, lo script qui sotto leggerà ogni file BackupState.txt creato nell’Esempio 1.

  • Trovare in modo ricorsivo tutti i file BackupState.txt all’interno di ogni sottodirectory.
  • Utilizzando l’istruzione foreach, leggere ciascun file di testo per ottenere il valore “ultimo backup”.
  • Visualizzare il risultato sullo schermo.
## Trova tutti i file BackupState.txt in C:\ARCHIVE_VOLUMES
$files = Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include 'BackupState.txt' | Select-Object DirectoryName,FullName

## Leggere i contenuti di ciascun file
foreach ($file in $files) {
    Write-Output ("$($file.DirectoryName) last backup time - " + (Get-Content $file.FullName))
}

Una volta eseguito lo script nella sessione PowerShell, dovresti vedere un output simile allo screenshot qui sotto. Ciò dimostra che PowerShell cicla attraverso i file, legge i contenuti e visualizza l’output.

Using foreach to loop through files and read its contents.

Esempio 3: Ottenere i Servizi e Avviarli utilizzando il Cmdlet ForEach-Object

Gli amministratori di sistema spesso devono ottenere lo stato dei servizi e avviare un flusso di lavoro manuale o automatico per risolvere eventuali servizi falliti. Vediamo lo script di esempio utilizzando il cmdlet ForEach-Object.

Per questo esempio, lo script qui sotto farà quanto segue:

  • Ottenere un elenco di servizi configurati per l’avvio automatico ma attualmente non in esecuzione.
  • Successivo, gli elementi nell’elenco vengono inviati al cmdlet ForEach-Object per tentare di avviare ciascun servizio.
  • A message of either success or failed is displayed depending on the result of the Start-Service command.
## Ottenere un elenco dei servizi automatici che sono fermi.
$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}

## Passare ogni oggetto servizio al pipeline e elaborarli con il cmdlet Foreach-Object
$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, assomiglierà allo screenshot di output qui sotto. Come puoi vedere, a ciascun servizio è stato dato un comando di avvio. Alcuni sono stati avviati con successo mentre alcuni di essi non sono riusciti ad avviarsi.

Using the ForEach-Object loop to start services

Esempio 4: Lettura dei dati da CSV usando il Metodo ForEach()

L’utilizzo dei dati dai file CSV è popolare tra gli amministratori di sistema. Mettere i record all’interno di un file CSV rende semplici le operazioni in blocco 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 quindi essere popolato con i nomi dei nuovi utenti da creare. Il file CSV assomiglierebbe a qualcosa del genere.

"Firstname","Lastname"
"Grimm","Reaper"
"Hell","Boy"
"Rick","Rude"

Ora per il prossimo blocco di script. Prima, importa il file CSV passando il percorso del contenuto al cmdlet Import-CSV. Quindi, utilizzando il metodo foreach(), fai un loop attraverso i nomi e crea i nuovi utenti in Active Directory.

# Importa elenco di Nome e Cognome da file CSV
$newUsers = Import-Csv -Path .\Employees.csv

Add-Type -AssemblyName System.Web

# Elabora l'elenco
$newUsers.foreach(
    {
        # Genera una password casuale
        $password = [System.Web.Security.Membership]::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3)
        $secPw = ConvertTo-SecureString -String $password -AsPlainText -Force

        # Formulare un nome utente
        $userName = '{0}{1}' -f $_.FirstName.Substring(0, 1), $_.LastName

        # Costruire nuovi attributi utente
        $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
        }
    }
)

Quando eseguito, ora dovresti aver creato un utente AD per ogni riga nel file CSV!

Sommario

La logica dietro il ciclo foreach in PowerShell non è diversa da quella di altri linguaggi di programmazione. Cambia solo con il modo in cui viene utilizzata e quale variante del ciclo foreach viene scelta per una specifica attività.

In questo articolo, hai imparato i diversi tipi di cicli foreach disponibili in PowerShell, e cosa considerare su quale utilizzare. Hai anche visto i tre tipi di cicli foreach in azione utilizzando scenari di esempio diversi.

Ulteriori letture

Source:
https://adamtheautomator.com/powershell-foreach/