Het leren kennen van de PowerShell-pijplijn en het maken van functies

De PowerShell-pijplijn is een van de belangrijkste (en handigste) functies van de PowerShell-shell en scripttaal. Zodra je de basisprincipes begrijpt van hoe het werkt en wat het kan, kun je de kracht ervan benutten in je eigen functies. In deze tutorial ga je dat precies doen!

De PowerShell-pijplijn stelt je in staat om commando’s aan elkaar te koppelen om een enkele ‘pijplijn’ te bouwen, wat de code vereenvoudigt, parallelle verwerking mogelijk maakt en meer. Als je klaar bent om meer te leren over de pijplijn en je eigen functies te bouwen om er gebruik van te maken, laten we dan beginnen!

Vereisten

Deze post zal een tutorial zijn en zal volledig bestaan uit praktijkdemonstraties. Als je wilt meedoen, heb je PowerShell v3+ nodig. Deze tutorial zal gebruikmaken van Windows PowerShell v5.1.

Begrip van de PowerShell-pijplijn

De meeste PowerShell-commando’s ontvangen invoer via een parameter. Het commando ontvangt een object als invoer en doet er iets mee. Vervolgens geeft het optioneel een object terug via de uitvoer.

Commando’s in een pijplijn gedragen zich als menselijke lopers in een estafetteloop. Elke loper in de race, behalve de eerste en de laatste, neemt de estafettestok (objecten) over van zijn voorganger en geeft het door aan de volgende.

Bijvoorbeeld, de Stop-Service cmdlet heeft een parameter genaamd InputObject. Met deze parameter kunt u een specifiek type object doorgeven aan Stop-Service dat de Windows-service vertegenwoordigt die u wilt stoppen.

Om de InputObject-parameter te gebruiken, kunt u het service-object ophalen via Get-Service en vervolgens het object doorgeven aan de InputObject-parameter zoals hieronder getoond. Deze methode om invoer te verstrekken aan de Stop-Service cmdlet via de InputObject-parameter werkt uitstekend en krijgt het werk gedaan.

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

Deze methode om invoer door te geven aan de Stop-Service-opdracht vereist twee afzonderlijke stappen. PowerShell moet eerst Get-Service uitvoeren, de uitvoer opslaan in een variabele en vervolgens die waarde doorgeven aan Stop-Service via de InputObject-parameter.

Nu, vergelijk het bovenstaande fragment met het onderstaande fragment, dat hetzelfde doet. Het is veel eenvoudiger omdat u geen $services-variabele hoeft te maken of zelfs helemaal geen gebruik hoeft te maken van de InputObject-parameter. In plaats daarvan “weet” PowerShell dat u van plan bent de InputObject-parameter te gebruiken. Dit gebeurt via een concept dat parameterbinding wordt genoemd.

U hebt nu opdrachten aan elkaar “gekoppeld” met de |-operator. U hebt een pijplijn gemaakt.

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

Maar u hoeft niet alleen twee opdrachten te gebruiken om een pijplijn te maken; u kunt er zoveel aan elkaar koppelen als u wilt (als de opdrachtparameters dit ondersteunen). Bijvoorbeeld, het onderstaande codefragment:

  1. Voert alle objecten door die de Get-Service cmdlet retourneert naar de Where-Object cmdlet.
  2. De Where-Object cmdlet bekijkt vervolgens de Status-eigenschap van elk object en retourneert alleen die objecten met een waarde van Running.
  3. Vervolgens worden al die objecten doorgestuurd naar Select-Object, die alleen de Name– en DisplayName-eigenschappen van de objecten retourneert.
  4. Aangezien geen andere cmdlet de objecten accepteert die Select-Object uitvoert, retourneert de opdracht de objecten rechtstreeks naar de console.

De cmdlets Where-Object en Select-Object begrijpen hoe ze de invoer via de pijplijn moeten verwerken door een concept genaamd parameterbinding, besproken in de volgende sectie.

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

Voor aanvullende informatie over pijplijnen, voer het commando Get-Help about_pipelines uit.

Pijplijn Parameter Binding

Op het eerste gezicht lijkt de pijplijn triviaal. Per slot van rekening geeft het alleen objecten door van de ene opdracht naar de andere. Maar in werkelijkheid is de pijplijn veel ingewikkelder. Opdrachten accepteren alleen invoer via parameters. De pijplijn moet op de een of andere manier bepalen welke parameter moet worden gebruikt, zelfs wanneer je deze niet expliciet definieert.

De taak om uit te zoeken welke parameter te gebruiken wanneer een opdracht invoer via de pijplijn ontvangt, staat bekend als parameterbinding. Om succesvol een object dat binnenkomt via de pijplijn aan een parameter te binden, moeten de parameter(s) van de binnenkomende opdracht dit ondersteunen. Commandopdrachten ondersteunen parameterbinding via de pijplijn op een van twee manieren; ByValue en/of ByPropertyName.

ByValue

De opdrachtparameter accepteert het volledige binnenkomende object als de parameterwaarde. Een ByValue-parameter zoekt naar een object van een specifiek type in binnenkomende objecten. Als dat objecttype overeenkomt, gaat PowerShell ervan uit dat het object aan die parameter moet worden gebonden en accepteert het.

De Get-ChildItem-cmdlet heeft een parameter genaamd Path die een stringobjecttype accepteert en invoer via de pijplijn via ByValue. Hierdoor retourneert iets als 'C:\Windows' | Get-ChildItem alle bestanden in de map C:\Windows omdat C:\Windows een string is.

ByPropertyName

De opdrachtparameter accepteert geen volledig object maar een enkele eigenschap van dat object. Dit doet het niet door naar het objecttype te kijken, maar naar de eigenschapsnaam.

De Get-Process cmdlet heeft een Name-parameter die is ingesteld om pijplijninvoer te accepteren ByPropertyName. Wanneer u een object met een Name-eigenschap doorgeeft aan de Get-Process cmdlet zoals [pscustomobject]@{Name='firefox'} | Get-Process, koppelt PowerShell de Name-eigenschap van het binnenkomende object aan de Name-parameter en gebruikt die waarde.

Het ontdekken van commandoparameters die de pijplijn ondersteunen

Zoals eerder vermeld, ondersteunt niet elk commando pijplijninvoer. De commando-auteur moet die functionaliteit ontwikkelen. Het commando moet ten minste één parameter hebben die de pijplijn ondersteunt, zet ByValue of ByPropertyName.

Hoe weet u welke commando’s en hun parameters pijplijninvoer ondersteunen? U kunt het gewoon proberen met trial and error, maar er is een betere manier met behulp van het PowerShell help systeem met het Get-Help commando.

Get-Help <COMMAND> -Parameter <PARAMETER>

Bijvoorbeeld, kijk eens naar de Get-ChildItem cmdlet’s parameter Path hieronder. U kunt zien dat het beide soorten pijplijninvoer ondersteunt.

PowerShell pipeline input is allowed

Eenmaal je weet welke commandoparameters pijplijninvoer ondersteunen, kun je vervolgens van die functionaliteit profiteren, zoals hieronder wordt getoond.

# oproep zonder pijplijn
Get-ChildItem -Path 'C:\\Program Files', 'C:\\Windows'

# pijplijnoproep
'C:\\Program Files', 'C:\\Windows' | Get-ChildItem

Maar, aan de andere kant, ondersteunt de parameter DisplayName op Get-Service geen invoer via de pijplijn.

PowerShell pipeline input is not allowed

Het opbouwen van je eigen pijplijnfunctie

Hoewel de standaard PowerShell-cmdlets invoer via de pijplijn ondersteunen, betekent dit niet dat je geen gebruik kunt maken van die functionaliteit. Gelukkig kun je ook functies bouwen die invoer via de pijplijn accepteren.

Om dit te demonstreren, laten we beginnen met een bestaande functie genaamd Get-ConnectionStatus.

  • Deze functie heeft een enkele parameter (die geen invoer via de pijplijn accepteert) genaamd ComputerName, waarmee je één of meer strings aan het doorgeven bent. Je kunt zien dat de parameter ComputerName geen invoer via de pijplijn accepteert omdat deze niet is gedefinieerd als een parameterattribuut ([Parameter()]).
  • Vervolgens leest de functie elk van die strings en voert de Test-Connection-cmdlet uit tegen elk ervan.
  • Voor elke doorgegeven computer naam retourneert het vervolgens een object met een ComputerName en Status eigenschap.
function Get-ConnectionStatus
{
    [CmdletBinding()]
    param
    (
        [Parameter()] ## geen pipeline invoer
        [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
        }
    }
}

Vervolgens roept u de Get-ConnectionStatus aan door de ComputerName parameter één of meer hostnamen of IP-adressen door te geven zoals hieronder.

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

Stap 1: Pipeline invoer toestaan

Om deze functie in staat te stellen pipeline invoer te accepteren, moet u eerst het juiste parameterattribuut definiëren. Dit attribuut kan ofwel ValueFromPipeline zijn om pipeline invoer ByValue te accepteren, of ValueFromPipelineByPropertyName om pipeline invoer ByPropertyName te accepteren.

Voor dit voorbeeld voegt u het ValueFromPipeline parameterattribuut toe binnen de haken van de [Parameter()] definitie zoals hieronder getoond.

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

Op dit punt is dat technisch gezien alles wat je moet doen. De Get-ConnectionStatus functie zal nu elk doorgegeven stringobject binden aan de ComputerName parameter. Maar, zelfs als parameterbinding plaatsvindt, betekent dit niet dat de functie er iets zinvols mee zal doen.

Stap 2: Een Process-blok toevoegen

Wanneer u wilt dat PowerShell alle objecten verwerkt die binnenkomen via de pipeline, moet u vervolgens een Process blok toevoegen. Dit blok vertelt PowerShell om elk object dat binnenkomt via de pipeline te verwerken.

Zonder een Process blok zal PowerShell alleen het eerste object verwerken dat uit de pipeline komt. Het Process blok vertelt PowerShell om door te gaan met het verwerken van objecten.

Voeg een Process-blok toe zoals hieronder weergegeven door alle functionaliteit van de functie erin te omvatten.

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

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

            [pscustomobject]@{
                ComputerName = $c
                Status = $status
            }
        }
    } ## einde Process-blok
}

Stuur altijd uitvoer vanuit het Process-blok. Het verzenden van uitvoer vanuit het Process-blok “stroomt” de objecten uit naar de pijplijn, zodat andere commando’s die objecten vanuit de pijplijn kunnen accepteren.

Het doorgeven van objecten aan de PowerShell-pijplijn

Zodra je een Process-blok hebt gedefinieerd in de functie hierboven, kun je nu de functie oproepen door waarden door te geven aan de parameter ComputerName via de pijplijn zoals hieronder weergegeven.

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

Op dit punt kun je echt profiteren van de kracht van de pijplijn en beginnen met het opnemen van meer commando’s in de mix. Bijvoorbeeld, misschien heb je een tekstbestand, C:\Test\computers.txt, met een regel IP-adressen gescheiden door een nieuwe regel zoals hieronder.

127.0.0.1
192.168.1.100

Je zou dan de Get-Content-cmdlet kunnen gebruiken om elk van die IP-adressen in het tekstbestand te lezen en ze rechtstreeks door te geven aan de functie Get-ConnectionStatus.

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

Door deze configuratie nog een stap verder te nemen, zou je de objecten die Get-ConnectionStatus retourneert rechtstreeks naar de ForEach-Object-cmdlet kunnen leiden.

De code hieronder:

  • Leest alle computernamen in het tekstbestand en geeft ze door aan de functie Get-ConnectionStatus.
  • Get-ConnectionStatus verwerkt elke computernaam en retourneert een object met de eigenschappen ComputerName en Status.
  • Get-ConnectionStatus geeft vervolgens elk object door aan de cmdlet ForEach-Object, die dan een enkele string retourneert gekleurd als Cyan met een menselijk leesbare status.
Get-Content -Path C:\Test\computers.txt |
Get-ConnectionStatus |
ForEach-Object { Write-Host "$($_.ComputerName) connection status is: $($_.Status)" -ForegroundColor Cyan }

Als pijplijninvoer niet was ingeschakeld op de parameter ComputerName of als Get-ConnectionStatus geen object retourneerde binnen het Process-blok, zou PowerShell geen status naar de console retourneren totdat alle objecten (IP-adressen) zijn verwerkt.

Pijplijnbinding op eigenschapsnaam

Tot nu toe is de cmdlet Get-ConnectionStatus ingesteld om pijplijninvoer per waarde te accepteren (ValueFromPipeline) door een array van strings zoals '127.0.0.1', '192.168.1.100' te accepteren. Zou deze functie ook werken zoals verwacht als de invoer afkomstig was van een CSV-bestand in plaats van een tekstbestand met IP-adressen?

Misschien hebt u een CSV-bestand dat er als volgt uitziet op C:\Test\pc-list.csv.

ComputerName,Location
127.0.0.1,London
192.168.1.100,Paris

Merk op dat het veld ComputerName in het CSV-bestand dezelfde naam heeft als de ComputerName-parameter van Get-ConnectionStatus.

Als u zou proberen het CSV-bestand te importeren en door te geven via de pijplijn naar Get-ConnectionStatus, retourneert de functie een onverwacht resultaat in de kolom 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

Kun je raden wat er misging? Immers, de parameter naam komt overeen, dus waarom bond de PowerShell-pijplijn de output die Import-CSV terugkeerde niet aan de ComputerName-parameter op Get-ConnectionStatus? Omdat je het parameterattribuut ValueFromPipelineByPropertyName nodig hebt.

Vanaf dit moment heeft de ComputerName-parameter van de functie een parameterdefinitie die er zo uitziet [Parameter(ValueFromPipeline)]. Daarom moet je ValueFromPipelineByPropertyName toevoegen om de ComputerName-parameter in te stellen om invoer via eigenschapsnaam te ondersteunen, zoals hieronder wordt getoond.

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

Zodra je pijplijnondersteuning hebt via ByPropertyName, vertel je PowerShell om te beginnen met het kijken naar de objecteigenschapnamen en het objecttype. Nadat je deze wijziging hebt aangebracht, zou je de verwachte output moeten zien.

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

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

Samenvatting

In deze zelfstudie heb je geleerd hoe de PowerShell-pijplijn werkt, hoe het parameters bindt, en zelfs hoe je je eigen functie kunt maken die de PowerShell-pijplijn ondersteunt.

Hoewel functies zonder de pijplijn zullen werken, zullen ze geen objecten “streamen” van het ene commando naar het andere en de code niet vereenvoudigen.

Kun je nadenken over een functie die je hebt geschreven, of misschien gaat schrijven, die baat kan hebben bij het gereed maken voor de pijplijn?

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