Conoscere la Pipeline di PowerShell e Creare Funzioni

Il pipeline di PowerShell è una delle caratteristiche più importanti (e utili) della shell e del linguaggio di scripting di PowerShell. Una volta comprese le basi di come funziona e di cosa è capace, puoi sfruttarne la potenza nelle tue funzioni personali. In questo tutorial, è esattamente ciò che farai!

Il pipeline di PowerShell ti consente di concatenare comandi per creare un singolo ‘pipeline’ che semplifica il codice, consente l’elaborazione parallela e altro ancora. Se sei pronto a imparare sul pipeline e a creare le tue funzioni per sfruttarlo, cominciamo!

Prerequisiti

Questo post sarà un tutorial con tutte dimostrazioni pratiche. Se vuoi seguirmi, avrai bisogno di PowerShell v3+. Questo tutorial utilizzerà Windows PowerShell v5.1.

Comprensione del PowerShell Pipeline

La maggior parte dei comandi di PowerShell riceve un input tramite un parametro. Il comando riceve un oggetto in input e fa qualcosa con esso al suo interno. Opzionalmente restituisce qualche oggetto tramite l’output.

I comandi in un pipeline agiscono come i corridori umani in una staffetta. Ogni corridore nella staffetta, tranne il primo e l’ultimo, accetta il testimone (oggetti) dal suo predecessore e lo passa al successivo.

Per esempio, il cmdlet Stop-Service ha un parametro chiamato InputObject. Questo parametro ti permette di passare un tipo specifico di oggetto a Stop-Service che rappresenta il servizio di Windows che desideri interrompere.

Per utilizzare il parametro InputObject, potresti recuperare l’oggetto servizio tramite Get-Service e poi passare l’oggetto al parametro InputObject come mostrato di seguito. Questo metodo di fornire input al cmdlet Stop-Service tramite il parametro InputObject funziona molto bene e svolge il compito.

$service = Get-Service -Name 'wuauserv'
Stop-Service -InputObject $service

Questo metodo di passaggio dell’input al comando Stop-Service richiede due passaggi distinti. PowerShell deve eseguire prima Get-Service, salvare l’output in una variabile e poi passare quel valore a Stop-Service tramite il parametro InputObject.

Ora, confronta il frammento di codice sopra con il frammento di codice sotto, che fa la stessa cosa. È molto più semplice perché non devi creare una variabile $services o utilizzare il parametro InputObject affatto. Invece, PowerShell “sa” che intendi utilizzare il parametro InputObject. Fa questo tramite un concetto chiamato associazione dei parametri.

Ora hai “concatenato” i comandi insieme con l’operatore |. Hai creato un pipeline.

Get-Service -Name 'wuauserv' | Stop-Service

Ma, non devi utilizzare solo due comandi per creare una pipeline; puoi concatenarne quanti vuoi (se i parametri del comando lo supportano). Per esempio, il frammento di codice sotto:

  1. Passa tutti gli oggetti restituiti dal cmdlet Get-Service al cmdlet Where-Object.
  2. Il cmdlet Where-Object verifica quindi la proprietà Status di ciascun oggetto e restituisce solo quelli con un valore di Running.
  3. Successivamente, ogni oggetto viene inviato a Select-Object, che restituisce solo le proprietà Name e DisplayName degli oggetti.
  4. Dato che nessun altro cmdlet accetta gli oggetti restituiti da Select-Object, il comando li restituisce direttamente alla console.

I cmdlet Where-Object e Select-Object comprendono come elaborare l’input della pipeline attraverso un concetto chiamato parameter binding, discusso nella sezione successiva.

Get-Service | Where-Object Status -eq Running | Select-Object Name, DisplayName

Per ulteriori informazioni sulle pipeline, esegui il comando Get-Help about_pipelines.

Binding dei parametri della pipeline

A prima vista, la pipeline può sembrare banale. In fondo, sta solo passando oggetti da un comando a un altro. Ma in realtà, la pipeline è molto più complicata. I comandi accettano input solo tramite parametri. La pipeline deve in qualche modo capire quale parametro utilizzare anche quando non lo definisci esplicitamente.

Il compito di stabilire quale parametro utilizzare quando un comando riceve input tramite il pipeline è noto come associazione dei parametri. Per associare con successo un oggetto in arrivo dalla pipeline a un parametro, il/i parametro/i del comando in arrivo deve supportarlo. I parametri del comando supportano l’associazione dei parametri della pipeline in uno dei due modi; ByValue e/o ByPropertyName.

ByValue

Il parametro del comando accetta l’intero oggetto in arrivo come valore del parametro. Un parametro ByValue cerca un oggetto di un tipo specifico negli oggetti in arrivo. Se quel tipo di oggetto è una corrispondenza, PowerShell assume che l’oggetto debba essere associato a quel parametro e lo accetta.

Il cmdlet Get-ChildItem ha un parametro chiamato Path che accetta un tipo di oggetto stringa e input tramite pipeline tramite ByValue. Per questo motivo, eseguire qualcosa come 'C:\Windows' | Get-ChildItem restituisce tutti i file nella directory C:\Windows perché C:\Windows è una stringa.

ByPropertyName

Il parametro del comando non accetta un intero oggetto ma una singola proprietà di quell’oggetto. Lo fa non guardando il tipo di oggetto ma il nome della proprietà.

Il cmdlet Get-Process ha un parametro Name configurato per accettare l’input della pipeline ByPropertyName. Quando si passa un oggetto con una proprietà Name al cmdlet Get-Process come [pscustomobject]@{Name='firefox'} | Get-Process, PowerShell associa la proprietà Name dell’oggetto in arrivo al parametro Name e utilizza quel valore.

Scoprire i Parametri del Comando che Supportano la Pipeline

Come accennato in precedenza, non tutti i comandi supportano l’input della pipeline. L’autore del comando deve creare tale funzionalità nello sviluppo. Il comando deve avere almeno un parametro che supporta la pipeline, specificare ByValue o ByPropertyName.

Come si sa quali comandi e relativi parametri supportano l’input della pipeline? Si potrebbe semplicemente provare con tentativi ed errori, ma c’è un modo migliore con il sistema di aiuto di PowerShell utilizzando il comando Get-Help.

Get-Help <COMMAND> -Parameter <PARAMETER>

Ad esempio, dai un’occhiata al parametro Path del cmdlet Get-ChildItem qui sotto. Puoi vedere che supporta entrambi i tipi di input della pipeline.

PowerShell pipeline input is allowed

Una volta che sai quali parametri di comando supportano l’input di pipeline, puoi sfruttare quella funzionalità, come mostrato di seguito.

# chiamata senza pipeline
Get-ChildItem -Path 'C:\\Program Files', 'C:\\Windows'

# chiamata con pipeline
'C:\\Program Files', 'C:\\Windows' | Get-ChildItem

Ma, d’altra parte, il parametro DisplayName su Get-Service non supporta l’input di pipeline.

PowerShell pipeline input is not allowed

Costruzione della tua funzione di pipeline

Anche se i cmdlet standard di PowerShell supportano l’input di pipeline, non significa che non puoi sfruttare quella funzionalità. Per fortuna, puoi creare anche funzioni che accettano l’input di pipeline.

Per dimostrare, cominciamo con una funzione esistente chiamata Get-ConnectionStatus.

  • Questa funzione ha un singolo parametro (che non accetta input di pipeline) chiamato ComputerName, che ti consente di passare uno o più stringhe ad esso. Puoi capire che il parametro ComputerName non accetta input di pipeline perché non è definito come attributo del parametro ([Parameter()]).
  • La funzione quindi legge ciascuna di quelle stringhe ed esegue il cmdlet Test-Connection contro ognuna di esse.
  • Per ogni stringa di nome computer passata, restituisce un oggetto con una proprietà ComputerName e Status.
function Get-ConnectionStatus
{
    [CmdletBinding()]
    param
    (
        [Parameter()] ## nessun input tramite pipeline
        [string[]]$ComputerName
    )

    foreach($c in $ComputerName)
    {
        if(Test-Connection -ComputerName $c -Quiet -Count 1)
        {
            $status = 'Ok'
        }
        else
        {
            $status = 'No Connection'
        }

        [pscustomobject]@{
            ComputerName = $c
            Status = $status
        }
    }
}

Successivamente si invoca il comando Get-ConnectionStatus passando il parametro ComputerName uno o più nomi host o indirizzi IP come mostrato di seguito.

Get-ConnectionStatus -ComputerName '127.0.0.1', '192.168.1.100'

Passo 1: Consentire l’Input tramite Pipeline

Per consentire a questa funzione di accettare input tramite pipeline, è necessario prima definire l’attributo parametro appropriato. Questo attributo può essere sia ValueFromPipeline per accettare input di pipeline ByValue o ValueFromPipelineByPropertyName per accettare input di pipeline ByPropertyName.

Per questo esempio, aggiungere l’attributo parametro ValueFromPipeline tra le parentesi quadre della definizione [Parameter()] come mostrato di seguito.

 [Parameter(ValueFromPipeline)]
 [string[]]$ComputerName

A questo punto, questo è tecnicamente tutto ciò che devi fare. La funzione Get-ConnectionStatus ora assocerà qualsiasi oggetto stringa passato ad essa al parametro ComputerName. Ma, anche se si sta verificando l’associazione dei parametri, non significa che la funzione farà qualcosa di significativo con esso.

Passo 2: Aggiungere un Blocco di Processo

Quando si vuole che PowerShell elabori tutti gli oggetti provenienti dalla pipeline, è necessario aggiungere un blocco Process. Questo blocco indica a PowerShell di elaborare ogni oggetto proveniente dalla pipeline.

Senza un blocco Process, PowerShell elaborerà solo il primo oggetto proveniente dalla pipeline. Il blocco Process indica a PowerShell di continuare a elaborare gli oggetti.

italian
Aggiungi un blocco Process come mostrato di seguito, racchiudendo l’intera funzionalità della funzione.

function Get-ConnectionStatus
{
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline)]
        [string[]]$ComputerName
    )

    Process ## Nuovo blocco Process
    {
        foreach($c in $ComputerName)
        {
            if(Test-Connection -ComputerName $c -Quiet -Count 1)
            {
                $status = 'Ok'
            }
            else
            {
                $status = 'No Connection'
            }

            [pscustomobject]@{
                ComputerName = $c
                Status = $status
            }
        }
    } ## Fine blocco Process
}

Invia sempre l’output da all’interno del blocco Process. L’invio dell’output da all’interno del blocco Process “streama” gli oggetti nel pipeline, consentendo ad altri comandi di accettare tali oggetti dalla pipeline.

Passaggio di oggetti alla pipeline di PowerShell

Dopo aver definito un blocco Process nella funzione sopra, puoi chiamare la funzione passando i valori al parametro ComputerName tramite la pipeline, come mostrato di seguito.

Get-ConnectionStatus -ComputerName '127.0.0.1', '192.168.1.100'
## oppure
'127.0.0.1', '192.168.1.100' | Get-ConnectionStatus

A questo punto, puoi sfruttare la vera potenza della pipeline e iniziare a incorporare più comandi nel mix. Ad esempio, forse hai un file di testo, C:\Test\computers.txt, con una riga di indirizzi IP separati da una nuova riga come segue.

127.0.0.1
192.168.1.100

Potresti quindi utilizzare il cmdlet Get-Content per leggere ciascuno di quegli indirizzi IP nel file di testo e passarli direttamente alla funzione Get-ConnectionStatus.

Get-Content -Path C:\Test\computers.txt | Get-ConnectionStatus 

Portando questa configurazione un passo oltre, potresti instradare direttamente gli oggetti restituiti da Get-ConnectionStatus al cmdlet ForEach-Object.

  • Legge tutti i nomi dei computer nel file di testo e li passa alla funzione Get-ConnectionStatus.
  • Get-ConnectionStatus elabora ciascun nome del computer e restituisce un oggetto con le proprietà ComputerName e Status.
  • Get-ConnectionStatus passa quindi ciascun oggetto al cmdlet ForEach-Object, che restituisce una singola stringa colorata di ciano con uno stato leggibile dall’utente.
Get-Content -Path C:\Test\computers.txt |
Get-ConnectionStatus |
ForEach-Object { Write-Host "$($_.ComputerName) connection status is: $($_.Status)" -ForegroundColor Cyan }

Se l’input tramite pipeline non fosse abilitato per il parametro ComputerName o se Get-ConnectionStatus non restituisse un oggetto all’interno del blocco Process, PowerShell non restituirebbe alcuno stato alla console fino a quando tutti gli oggetti (indirizzi IP) non sono elaborati.

Associazione tramite pipeline per nome della proprietà

Fino a ora, il cmdlet Get-ConnectionStatus è configurato per accettare l’input tramite pipeline ByValue (ValueFromPipeline) accettando un array di stringhe come '127.0.0.1', '192.168.1.100'. Questa funzione funzionerebbe anche correttamente se l’input provenisse da un file CSV invece di un file di testo di indirizzi IP?

Forse hai un file CSV che assomiglia a quanto segue in C:\Test\pc-list.csv.

ComputerName,Location
127.0.0.1,London
192.168.1.100,Paris

Nota che il campo ComputerName nel file CSV ha lo stesso nome del parametro ComputerName di Get-ConnnectionStatus.

Se tentassi di importare il CSV e passarlo attraverso la pipeline a Get-ConnectionStatus, la funzione restituirebbe un risultato inaspettato nella colonna ComputerName.

Import-Csv -Path 'C:\Test\pc-list.csv' | Get-ConnectionStatus

ComputerName                                  Status       
------------                                  ------       
@{ComputerName=127.0.0.1; Location=London}    No Connection
@{ComputerName=192.168.1.100; Location=Paris} No Connection

Riesci a indovinare cosa è andato storto? Dopotutto, il nome del parametro corrisponde, quindi perché il pipeline di PowerShell non ha vincolato l’output restituito da Import-CSV al parametro ComputerName su Get-ConnectionStatus? Perché è necessario l’attributo del parametro ValueFromPipelineByPropertyName.

Finora, il parametro ComputerName della funzione ha una definizione del parametro che assomiglia a [Parameter(ValueFromPipeline)]. Pertanto, devi aggiungere ValueFromPipelineByPropertyName per impostare il parametro ComputerName per supportare l’input ByPropertyName, come mostrato di seguito.

    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

Una volta che hai il supporto del pipeline ByPropertyName, dici a PowerShell di iniziare a guardare i nomi delle proprietà dell’oggetto e il tipo di oggetto. Una volta apportata questa modifica, dovresti quindi vedere l’output previsto.

PS> Import-Csv -Path 'C:\Test\pc-list.csv' | Get-ConnectionStatus

ComputerName  Status       
------------  ------       
127.0.0.1     Ok           
192.168.1.100 No Connection

Riepilogo

In questo tutorial hai imparato come funziona il pipeline di PowerShell, come vincola i parametri e persino come creare la tua funzione che supporta il pipeline di PowerShell.

Anche se le funzioni funzioneranno senza il pipeline, non “streameranno” gli oggetti da un comando all’altro e semplificheranno il codice.

Riesci a pensare a una funzione che hai scritto, o forse stai per scriverne una, che può beneficiare dall’essere pronta per il pipeline?

Source:
https://adamtheautomator.com/ppowershell-pipeline/