PowerShell – Operatori e condizioni
Finora abbiamo eseguito i comandi uno dopo l’altro, senza mai fermarci a pensare. Per rendere uno script davvero utile dobbiamo insegnargli a prendere decisioni: fare una cosa se il file esiste e un’altra se non esiste, trattare in modo diverso un PDF e una foto, ignorare i file più vecchi di 30 giorni.
Avete già letto le mie guide PowerShell – Introduzione e PowerShell – Gestione del File System?
In PowerShell le decisioni funzionano in due tempi. Gli operatori di confronto, come -eq e -like, valutano una condizione e restituiscono True o False. I costrutti if e switch agiscono poi su quel risultato, decidendo quale blocco di codice eseguire.
Prepariamo una cartella di prova
Come sempre, prima di sperimentare creiamo una cartella di test: gli esempi di questo capitolo useranno sei file con dimensioni e date di modifica diverse, così avremo qualcosa su cui confrontare e decidere. Copiate ed eseguite questo blocco (le date di modifica le “falsifichiamo” con LastWriteTime):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
New-Item -ItemType Directory -Path "Test" Set-Location "Test" $file = [ordered]@{ "documento.txt" = 1KB "note.txt" = 0 "log.log" = 200 "report.pdf" = 250KB "foto.jpg" = 3MB "archivio.zip" = 12MB } foreach ($nome in $file.Keys) { $f = [System.IO.File]::Create((Join-Path $PWD $nome)) $f.SetLength($file[$nome]) $f.Close() } (Get-Item "log.log").LastWriteTime = (Get-Date).AddDays(-45) (Get-Item "report.pdf").LastWriteTime = (Get-Date).AddDays(-3) (Get-Item "foto.jpg").LastWriteTime = (Get-Date).AddDays(-10) (Get-Item "archivio.zip").LastWriteTime = (Get-Date).AddDays(-120) |
NOTA: usiamo Join-Path $PWD perché i metodi .NET come [System.IO.File]::Create risolvono i percorsi relativi partendo dalla cartella in cui è stato avviato il processo, non da quella in cui ci siamo spostati con Set-Location. Il suffisso KB, MB e GB è invece un comodo “zucchero” di PowerShell: 3MB vale 3145728.
Verifichiamo cosa abbiamo creato:
|
1 2 |
Get-ChildItem | Select-Object Name, Length |

Figura 1: la cartella di prova
Operatori di confronto
Un operatore di confronto mette a confronto due valori e restituisce sempre un valore booleano: True oppure False. Chi arriva da altri linguaggi cercherà ==, > e <, ma in PowerShell non funzionano come ci si aspetta: = serve per assegnare un valore e > per redirigere l’output su un file. Per questo gli operatori di confronto iniziano con un trattino e sono abbreviazioni inglesi (equal, greater than, less than, …).
Confrontare numeri e valori: -eq, -ne, -gt, -ge, -lt, -le
| Operatore | Significato | Esempio |
| -eq | Uguale a (equal) | 5 -eq 5 |
| -ne | Diverso da (not equal) | 5 -ne 3 |
| -gt | Maggiore di (greater than) | 10 -gt 3 |
| -ge | Maggiore o uguale (greater or equal) | 10 -ge 10 |
| -lt | Minore di (less than) | 2 -lt 5 |
| -le | Minore o uguale (less or equal) | 2 -le 2 |
Come i nomi dei cmdlet, anche gli operatori non sono case sensitive: “PowerShell” -eq “powershell” è vero. Se la differenza tra maiuscole e minuscole conta basta anteporre una c all’operatore (-ceq, -cne, -clike, -cmatch, …); con una i si dichiara esplicitamente il comportamento di default (-ieq).
|
1 2 3 4 5 6 7 8 |
5 -eq 5 5 -ne 5 10 -gt 3 10 -le 3 "PowerShell" -eq "powershell" "PowerShell" -ceq "powershell" 1MB -gt 900KB |

Figura 2: operatori di confronto numerici e case sensitivity
Confrontare testo: -like, -notlike, -match, -notmatch
Per il testo abbiamo due famiglie di operatori. Gli operatori -like e -notlike usano i caratteri jolly che conosciamo dal file system: * sostituisce un numero qualsiasi di caratteri, ? un carattere solo. Gli operatori -match e -notmatch usano invece le espressioni regolari (regex), molto più potenti. Con -match, quando la ricerca ha successo, PowerShell salva il testo trovato nella variabile automatica $Matches.
| Operatore | Significato | Esempio |
| -like | Corrisponde al carattere jolly | “a.txt” -like “*.txt” |
| -notlike | Non corrisponde al jolly | “a.txt” -notlike “*.pdf” |
| -match | Corrisponde alla regex | “2026-03” -match “\d{4}” |
| -notmatch | Non corrisponde alla regex | “abc” -notmatch “\d” |
|
1 2 3 4 5 6 |
"documento.txt" -like "*.txt" "documento.txt" -notlike "*.pdf" "fattura_2026-03.pdf" -match "\d{4}-\d{2}" $Matches[0] "documento.txt" -match "^report" |

Figura 3: -like, -notlike e -match
Appartenenza a una collezione: -contains, -in
Per sapere se un valore compare in un elenco usiamo -contains (con la collezione a sinistra) oppure -in (con il valore a sinistra): dicono la stessa cosa, cambia solo l’ordine dei termini, e -in si legge spesso più naturalmente. Esistono anche le versioni negate -notcontains e -notin.
|
1 2 3 4 5 |
$giorni = "lun","mar","mer" $giorni -contains "mar" "gio" -in $giorni "gio" -notin $giorni |

Figura 4: -contains, -in e -notin
Quando a sinistra c’è un array
Qui PowerShell ci riserva una sorpresa. Se a sinistra di un operatore di confronto c’è una collezione (e non un singolo valore), l’operatore non restituisce True o False: funziona da filtro e restituisce gli elementi che soddisfano la condizione.
|
1 2 3 4 |
1,2,3,2,1 -eq 2 (1,2,3,2,1 -eq 2).Count 1,2,3,2,1 -gt 1 |

Figura 5: confronto con un array a sinistra: restituisce gli elementi
Attenzione: se volete sapere se un valore è presente in un elenco non usate -eq: usate -contains o -in, che restituiscono davvero True o False. Con -eq su un array un if può darvi la risposta sbagliata: if (@(1,0,2) -eq 0) risulta falso anche se lo 0 nell’elenco c’è, perché il filtro restituisce proprio 0, e 0 viene valutato come falso.
Operatori logici: -and, -or, -not, -xor
Quando una decisione dipende da più condizioni, gli operatori logici le combinano. Ogni condizione va racchiusa tra parentesi tonde, per evitare ambiguità e per rendere il codice leggibile.
| Operatore | Significato | Esempio |
| -and | Vero se entrambe sono vere | ($a -gt 1) -and ($a -lt 9) |
| -or | Vero se almeno una è vera | ($a -lt 1) -or ($a -gt 9) |
| -not oppure ! | Inverte il risultato | -not ($a -eq 5) |
| -xor | Vero se solo una è vera | $true -xor $false |
|
1 2 3 4 5 6 7 8 |
$eta = 25 ($eta -ge 18) -and ($eta -lt 65) ($eta -lt 18) -or ($eta -gt 65) -not ($eta -eq 25) !($eta -eq 25) $true -xor $true $false -and (Write-Output "non lo vedrai mai") |

Figura 6: operatori logici
Nell’ultimo esempio si vede il corto circuito: se il primo termine di un -and è già False il risultato non può più cambiare, quindi PowerShell non valuta nemmeno il secondo e la scritta “non lo vedrai mai” non compare. Lo stesso vale per -or quando il primo termine è True.
La condizione if
Sintassi: if, elseif, else
Il costrutto if valuta una condizione tra parentesi tonde: se è vera esegue il blocco tra graffe, altrimenti passa all’eventuale elseif successivo e, se nessuno è vero, a else. I blocchi elseif e else sono facoltativi e elseif può essere ripetuto quante volte serve.
Sintassi:
|
1 2 3 4 5 6 7 8 |
if (condizione) { # eseguito se la condizione è vera } elseif (altra condizione) { # eseguito se la prima è falsa e questa è vera } else { # eseguito se tutte le condizioni sono false } |
Mettiamolo alla prova con la cartella di test. Il primo esempio usa
|
1 2 |
Test-Path |
(visto nel capitolo sul file system), il secondo decide in base alla dimensione di un file:
|
1 2 3 4 5 6 7 8 9 10 11 |
if (Test-Path "documento.txt") { "Trovato" } else { "Non trovato" } $dim = (Get-Item "foto.jpg").Length if ($dim -gt 10MB) { "foto.jpg è enorme" } elseif ($dim -gt 1MB) { "foto.jpg pesa più di 1 MB" } else { "foto.jpg è leggera" } |

Figura 7: if, elseif ed else
NOTA: le condizioni di un elseif vengono valutate in ordine e PowerShell si ferma alla prima vera. Per questo nell’esempio il controllo -gt 10MB viene prima di -gt 1MB: a parti invertite, una foto da 12 MB verrebbe classificata nel ramo sbagliato.
Cosa è vero e cosa è falso
La condizione di un if non deve per forza essere un confronto: PowerShell converte qualsiasi valore in booleano. Ecco i casi più comuni:
| Valore | Viene valutato come |
| 0 | False |
| qualsiasi numero diverso da 0 | True |
| “” (stringa vuota) | False |
| “testo” (stringa non vuota) | True |
| “0” (stringa non vuota!) | True |
| $null | False |
| @() (array vuoto) | False |
|
1 2 3 4 5 |
0,1,"","testo","0",$null | ForEach-Object { "[{0}] -> {1}" -f $_, [bool]$_ } if (@()) { "vero" } else { "falso" } |

Figura 8: conversione in booleano
Attenzione al terzo caso: la stringa “0” è vera, perché è una stringa non vuota, mentre il numero 0 è falso.
L’errore classico: = al posto di -eq
Attenzione: in un if, scrivere = al posto di -eq non dà errore: assegna il valore alla variabile, e il risultato dell’assegnazione (che è quasi sempre “vero”) diventa la condizione.
|
1 2 3 4 |
$x = 1 if ($x = 5) { "Condizione vera!" } $x |

Figura 9: if ($x = 5) assegna invece di confrontare
La condizione è risultata vera e, peggio, la variabile $x ora vale 5. La forma corretta è if ($x -eq 5).
Scegliere tra molti valori: switch
Quando dobbiamo confrontare lo stesso valore con molte alternative una lunga catena di elseif diventa illeggibile. Il costrutto switch risolve il problema: valuta un’espressione ed esegue il blocco della prima (o di tutte) le alternative che corrispondono. Il ramo default viene eseguito se nessuna alternativa corrisponde.
Sintassi:
|
1 2 3 4 5 6 |
switch (espressione) { valore1 { # azione 1 } valore2 { # azione 2 } default { # nessuna corrispondenza } } |
|
1 2 3 4 5 6 7 8 |
$estensione = ".pdf" switch ($estensione) { ".txt" { "File di testo" } ".pdf" { "Documento PDF" } ".jpg" { "Immagine" } default { "Tipo sconosciuto" } } |

Figura 10: switch su un valore
Più rami possono scattare: break
differenza di molti altri linguaggi, in PowerShell switch non si ferma alla prima corrispondenza: valuta tutte le alternative e ne esegue ogni ramo che corrisponde. Per fermarsi serve break. Nelle alternative possiamo anche usare un blocco di codice tra graffe come condizione: al suo interno $_ è il valore in esame.
|
1 2 3 4 5 6 7 8 9 10 11 |
switch (5) { { $_ -gt 1 } { "maggiore di 1" } { $_ -gt 3 } { "maggiore di 3" } { $_ -gt 9 } { "maggiore di 9" } } switch (5) { { $_ -gt 1 } { "maggiore di 1"; break } { $_ -gt 3 } { "maggiore di 3" } } |

Figura 11: senza break scattano tutti i rami corrispondenti
Opzioni di switch
| Opzione | Descrizione |
| -Exact | Confronto di uguaglianza esatta (è il comportamento di default). |
| -Wildcard | Le alternative sono caratteri jolly, come per -like. |
| -Regex | Le alternative sono espressioni regolari, come per -match. |
| -CaseSensitive | Distingue tra maiuscole e minuscole (di default non le distingue). |
| -File | Invece di un valore legge le righe di un file di testo e le valuta una alla volta. |
Combiniamo switch -Wildcard con foreach per classificare i file della cartella di test:
|
1 2 3 4 5 6 7 8 9 |
foreach ($f in Get-ChildItem -File) { switch -Wildcard ($f.Name) { "*.txt" { "$($f.Name): testo" } "*.pdf" { "$($f.Name): documento" } "*.jp*g" { "$($f.Name): immagine" } default { "$($f.Name): altro" } } } |

Figura 12: switch -Wildcard applicato ai file
Condizioni e pipeline: Where-Object
Gli operatori di confronto sono anche il cuore di Where-Object, il cmdlet che filtra gli oggetti in arrivo dalla pipeline. Per ogni oggetto la condizione tra graffe viene valutata e solo quelli per cui è vera passano oltre. Possiamo scriverla in forma estesa, con $_ che rappresenta l’oggetto corrente, oppure in forma semplificata (da PowerShell 3.0) quando la condizione è un singolo confronto.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
Get-ChildItem | Where-Object { $_.Length -gt 1MB -and $_.Extension -ne ".zip" } | Select-Object Name, Length Get-ChildItem | Where-Object Length -gt 100KB | Select-Object Name $limite = (Get-Date).AddDays(-30) Get-ChildItem | Where-Object { $_.LastWriteTime -lt $limite } | Select-Object Name |

Figura 13: Where-Object con operatori di confronto e logici
Nel primo esempio abbiamo combinato due confronti con -and; nel secondo abbiamo usato la sintassi semplificata; nel terzo abbiamo confrontato la data di modifica con $limite, cioè “30 giorni fa” (le date si confrontano con gli stessi operatori dei numeri). Questo è esattamente il meccanismo che avevamo usato per cancellare i file più vecchi di 30 giorni.
Scorciatoie di PowerShell 7: ternario e ??
Dalla versione 7 PowerShell offre due scorciatoie per le decisioni più semplici. L’operatore ternario (tipo if the else in excell) condizione ? valoreSeVero : valoreSeFalso sostituisce un if/else su una riga sola, mentre l’operatore ?? (null-coalescing) restituisce il valore a sinistra e, se questo è $null, quello a destra. Attenzione: su PowerShell 5.1 non funzionano, quindi occhio!
|
1 2 3 4 5 6 |
$n = 7 $n % 2 -eq 0 ? "pari" : "dispari" $nome = $null $nome ?? "sconosciuto" |

Figura 14: operatore ternario e ??
Esempio pratico completo: classificare i file per età
Mettiamo insieme quanto visto: per ogni file calcoliamo da quanti giorni non viene modificato e, con una catena di if/elseif, lo classifichiamo. I file vuoti sono segnalati per primi, poi si guarda l’età. Il formato -f allinea le colonne.
|
1 2 3 4 5 6 7 8 9 10 |
foreach ($f in Get-ChildItem -File) { $giorni = ((Get-Date) - $f.LastWriteTime).Days if ($f.Length -eq 0) { $stato = "VUOTO" } elseif ($giorni -gt 90) { $stato = "da archiviare" } elseif ($giorni -gt 30) { $stato = "vecchio" } else { $stato = "recente" } $riga = "{0,-14} {1,9} byte {2,3} gg {3}" $riga -f $f.Name, $f.Length, $giorni, $stato } |

Figura 15: classificazione dei file per dimensione ed età
Provate ora a sostituire l’ultima riga con Remove-Item sui file “da archiviare” o con Move-Item verso una cartella di archivio: con -WhatIf prima, mi raccomando!
Consigli Utili per Principianti
- Ricordatevi che = assegna e -eq confronta: è l’errore più frequente e PowerShell non lo segnala.
- Usate sempre le parentesi con -and e -or: rendono chiaro l’ordine di valutazione e il codice più leggibile.
- Attenzione agli array a sinistra: -eq e gli altri operatori di confronto diventano filtri; per sapere se un elemento è presente usate -contains o -in.
- Occhio a maiuscole e minuscole: di default non contano; usate -ceq, -clike, -cmatch quando servono.
- Ordinate bene gli elseif: vince la prima condizione vera; quindi, mettete per prime le più specifiche.
- Ricordate break nello switch: senza, tutte le alternative che corrispondono vengono eseguite.
Riepilogo Operatori e Costrutti
| Operazione | Operatore o costrutto | Esempio |
| Uguale / diverso | -eq -ne | $a -eq 5 |
| Maggiore / minore | -gt -ge -lt -le | $a -ge 10 |
| Jolly | -like -notlike | $n -like “*.txt” |
| Espressione regolare | -match -notmatch | $n -match “^\d+” |
| Appartenenza | -contains -in | “a” -in $lista |
| Maiuscole significative | -ceq -clike -cmatch | $a -ceq “Ok” |
| Logica | -and -or -not -xor | ($a -gt 1) -and ($b -lt 9) |
| Una scelta | if / elseif / else | if ($a -gt 1) { … } |
| Molte scelte | switch | switch ($a) { 1 { … } } |
| Filtrare la pipeline | Where-Object | … | Where-Object Length -gt 1KB |
| Ternario (PS 7) | ? : | $a -gt 1 ? “sì” : “no” |
| Valore di default (PS 7) | ?? | $a ?? “n/d” |
Conclusione
Con gli operatori di confronto, quelli logici e i costrutti if e switch i nostri script smettono di essere semplici elenchi di comandi e iniziano a ragionare. Il prossimo passo naturale è fare in modo che ripetano le operazioni da soli: ci vediamo alla prossima puntata per parlare di cicli.
Ricordate: fate pratica! Sperimentate gli esempi sulla cartella di test e provate a inventarne di nuovi.