PowerShell Parameters: Ontgrendel de Kracht van Scripting

Alle PowerShell-commando’s kunnen één of meer parameters hebben, soms genoemd argumenten. Als u geen PowerShell-parameters gebruikt in uw PowerShell-functies, schrijft u geen goede PowerShell-code!

In dit artikel ga je bijna elk facet leren van het maken en gebruiken van PowerShell-parameters of argumenten!

Dit is een voorbeeld uit mijn boek PowerShell voor SysAdmins. Als je PowerShell wilt leren of wat trucjes van het vak wilt leren, bekijk het dan!

Waarom heb je een parameter nodig?

Wanneer u begint functies te maken, hebt u de mogelijkheid om parameters al dan niet op te nemen en hoe die parameters werken.

Laten we zeggen dat u een functie hebt die Microsoft Office installeert. Misschien roept het de Office-installateur stil binnen de functie aan. Wat de functie doet, doet er niet toe voor onze doeleinden. De basisfunctie ziet er als volgt uit met de naam van de functie en het scriptblok.

function Install-Office {
    ## voer hier de stille installateur uit
}

In dit voorbeeld hebt u zojuist Install-Office uitgevoerd zonder parameters en het doet zijn ding.

Het maakte niet uit of de functie Install-Office parameters had of niet. Het had blijkbaar geen verplichte parameters; anders zou PowerShell ons niet hebben toegestaan het uit te voeren zonder een parameter te gebruiken.

Wanneer een PowerShell-parameter te gebruiken

Kantoor heeft veel verschillende versies. Misschien moet je Office 2013 en 2016 installeren. Op dit moment heb je geen manier om dit aan te geven. Je zou telkens de code van de functie moeten wijzigen als je het gedrag wilt veranderen.

Bijvoorbeeld, je zou twee aparte functies kunnen maken om verschillende versies te installeren.

function Install-Office2013 {
    Write-Host 'I installed Office 2013. Yippee!'
}

function Install-Office2016 {
    Write-Host 'I installed Office 2016. Yippee!'
}

Dit werkt, maar het is niet schaalbaar. Het dwingt je om voor elke versie van Office die uitkomt een aparte functie te maken. Je zult veel code moeten dupliceren wanneer dat eigenlijk niet nodig is.

In plaats daarvan heb je een manier nodig om verschillende waarden tijdens runtime door te geven om het gedrag van de functie te veranderen. Hoe doe je dat?

Jazeker! Parameters of wat sommige mensen argumenten noemen.

Aangezien we verschillende versies van Office willen installeren zonder telkens de code te wijzigen, moet je ten minste één parameter aan deze functie toevoegen.

Voordat je snel een PowerShell-parameter bedenkt om te gebruiken, is het essentieel om jezelf eerst een vraag te stellen; “Wat is de kleinste verandering of veranderingen die je verwacht nodig te hebben in deze functie?”.

Onthoud dat je deze functie opnieuw moet uitvoeren zonder iets van de code binnenin de functie te wijzigen. In dit voorbeeld is de parameter waarschijnlijk duidelijk voor je; je moet een Versie-parameter toevoegen. Maar, wanneer je een functie hebt met tientallen regels code, zal het antwoord niet al te duidelijk zijn. Zolang je die vraag zo precies mogelijk beantwoordt, zal het altijd helpen.

Dus je weet dat je een Versie-parameter nodig hebt. En nu? Nu kun je er een toevoegen, maar zoals bij elke geweldige programmeertaal, zijn er meerdere manieren om dat te doen.

In deze tutorial laat ik je de “beste” manier zien om parameters te maken, gebaseerd op mijn bijna tien jaar ervaring met PowerShell. Houd er echter rekening mee dat dit niet de enige manier is om een parameter te maken.

Er bestaan ook positionele parameters. Deze parameters stellen je in staat waarden door te geven aan parameters zonder de parameter naam te specificeren. Positionele parameters werken, maar worden niet beschouwd als “beste praktijk”. Waarom? Omdat ze moeilijker te lezen zijn, vooral wanneer je veel parameters hebt gedefinieerd in een functie.

Het maken van een eenvoudige PowerShell-parameter

Het maken van een parameter in een functie vereist twee belangrijke onderdelen; een param-blok en de parameter zelf. Een param-blok wordt gedefinieerd door het sleutelwoord param gevolgd door een set haakjes.

An example of a param block
function Install-Office { [CmdletBinding()] param() Write-Host 'I installed Office 2016. Yippee!' }

Op dit punt is de feitelijke functionaliteit van de functie niet veranderd. We hebben alleen wat basiswerk gedaan om ons voor te bereiden op de eerste parameter.

Zodra we het param-blok hebben geplaatst, maak je nu de parameter aan. De methode die ik voorstel om een parameter aan te maken, omvat het Parameter-blok, gevolgd door het type van de parameter, en daaronder de naam van de parameter variabele.

An example of a param block
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host 'I installed Office 2016. Yippee!' }

We hebben nu een functieparameter gemaakt in PowerShell, maar wat is er precies gebeurd?

Het Parameter-blok is een optioneel maar aanbevolen onderdeel van elke parameter. Net als het param-blok is het “functieplumbing” die de parameter voorbereidt op het toevoegen van extra functionaliteit. De tweede regel is waar je het type parameter definieert.

In dit geval hebben we ervoor gekozen om de Version-parameter naar een tekenreeks te casten. Het expliciet definiëren van een type betekent dat elke waarde die aan deze parameter wordt doorgegeven, altijd zal proberen te worden “geconverteerd” naar een tekenreeks als dat nog niet het geval is.

Het type is niet verplicht, maar wordt sterk aangemoedigd. Door expliciet het type van de parameter te definiëren, worden veel ongewenste situaties aanzienlijk verminderd. Geloof me.

Nu je de parameter hebt gedefinieerd, kun je de Install-Office-opdracht uitvoeren met de Version-parameter door er een versie-tekenreeks aan door te geven, zoals 2013. De waarde die aan de Version-parameter wordt doorgegeven, wordt soms aangeduid als argumenten of waarden van parameters.

Passing a Parameter to the Function
PS> Install-Office -Version 2013 I installed Office 2016. Yippee!

Wat gebeurt hier eigenlijk? Je hebt aangegeven dat je versie 2013 wilt installeren, maar het zegt nog steeds dat het 2016 heeft geïnstalleerd. Wanneer je een parameter toevoegt, moet je dan onthouden om de code van de functie naar een variabele te wijzigen. Wanneer de parameter aan de functie wordt doorgegeven, wordt die variabele uitgebreid naar welke waarde dan ook is doorgegeven.

Verander de statische tekst van 2016 en vervang deze door de Version-parameter variabele en zet de enkele aanhalingstekens om naar dubbele aanhalingstekens, zodat de variabele wordt uitgebreid.

Modifying function code account for a parameter
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" }

Nu kun je zien dat welke waarde je ook doorgeeft aan de Version-parameter, deze dan in de functie wordt doorgegeven als de $Version-variabele.

De Mandatory Parameter Attribute

Onthoud dat ik al eerder heb gezegd dat de regel [Parameter()] slechts “functie-installatie” was en nodig was om de functie voor te bereiden op verder werk? Het toevoegen van parameterattributen aan een parameter is het extra werk waar ik het eerder over had.

A parameter doesn’t have to be a placeholder for a variable. PowerShell has a concept called parameter attributes and parameter validation. Parameter attributes change the behavior of the parameter in a lot of different ways.

Bijvoorbeeld, een van de meest voorkomende parameterattributen die je zult instellen is het sleutelwoord Verplicht. Standaard zou je de functie Install-Office kunnen aanroepen zonder de Versie-parameter te gebruiken en deze zou gewoon goed uitvoeren. De Versie-parameter zou optioneel zijn. Toegegeven, het zou de $Versie variabele binnen de functie niet uitbreiden omdat er geen waarde was, maar het zou toch worden uitgevoerd.

Vaak wanneer je een parameter maakt, wil je dat de gebruiker altijd die parameter gebruikt. Je zult afhankelijk zijn van de waarde van de parameter ergens in de code van de functie, en als de parameter niet wordt doorgegeven, zal de functie mislukken. In deze gevallen wil je dat de gebruiker deze parameterwaarde aan je functie doorgeeft. Je wilt dat die parameter verplicht wordt.

Gebruikers dwingen om een parameter te gebruiken is eenvoudig zodra je het basisframework hebt gebouwd zoals je hier hebt. Je moet het sleutelwoord Verplicht opnemen binnen de haakjes van de parameter. Zodra je dat hebt, zal het uitvoeren van de functie zonder de parameter de uitvoering stoppen totdat er een waarde is ingevoerd.

Using a mandatory parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" } PS> Install-Office cmdlet Install-Office at command pipeline position 1 Supply values for the following parameters: Version:

De functie zal wachten totdat je een waarde voor de Versie-parameter opgeeft. Zodra je dit doet en op Enter drukt, zal PowerShell de functie uitvoeren en doorgaan. Als je een waarde voor de parameter opgeeft, zal PowerShell je niet telkens om de parameter vragen.

PowerShell Parameter Validatieattributen

Het verplicht maken van een parameter is een van de meest voorkomende attributen die je kunt toevoegen, maar je kunt ook gebruikmaken van parameter validatie attributen. In programmeren is het altijd essentieel om gebruikersinvoer zo strak mogelijk te beperken. Het beperken van de informatie die gebruikers (of zelfs jijzelf!) aan je functies of scripts kunnen doorgeven, elimineert onnodige code binnen je functie die rekening moet houden met allerlei situaties.

Leren aan de hand van voorbeelden

Bijvoorbeeld, in de functie Install-Office heb ik gedemonstreerd dat het doorgeven van de waarde 2013 zou werken omdat ik de code heb geschreven! Ik ga ervan uit (doe dit nooit in code!) dat het vanzelfsprekend is dat iedereen die iets weet, de versie zou specificeren als 2013 of 2016. Nou, wat voor jou vanzelfsprekend is, is misschien niet zo voor andere mensen.

Als je technisch wilt worden over versies, zou het waarschijnlijk nauwkeuriger zijn om de versie 2013 aan te geven als 15.0 en 2016 als 16.0 als Microsoft nog steeds het versienummeringsschema zou gebruiken dat ze in het verleden hebben gebruikt. Maar stel dat, omdat je ervan uitgaat dat ze een versie van 2013 of 2016 zullen specificeren, je de code binnen de functie hebt die zoekt naar mappen met die versies erin, of iets anders?

Hieronder is een voorbeeld waar je mogelijk de string $Versie gebruikt in een bestandspad. Als iemand een waarde doorgeeft die geen mapnaam van Office2013 of Office2016 voltooit, zal het mislukken of iets ergers doen, zoals onverwachte mappen verwijderen of dingen wijzigen waar je geen rekening mee hebt gehouden.

Assuming Parameter Values
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version '15.0' Get-ChildItem : Cannot find path '\SRV1\Installers\Office15.0' because it does not exist. At line:7 char:5 Get-ChildItem -Path "\\SRV1\Installers\Office$Version" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CategoryInfo : ObjectNotFound: (\SRV1\Installers\Office15.0:String) [Get-ChildItem], ItemNotFoundExcep tion FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

Om de gebruiker te beperken tot wat je van hen verwacht dat ze invoeren, kun je enkele PowerShell-parametervalidatie toevoegen.

Gebruik de ValidateSet-parametervalidatie-attribuut

Er zijn verschillende soorten parametervalidatie die je kunt gebruiken. Voor een volledige lijst, voer Get-Help about_Functions_Advanced_Parameters uit. In dit voorbeeld is het ValidateSet-attribuut waarschijnlijk het beste.

Het ValidateSet validatie-attribuut stelt je in staat om een lijst met waarden op te geven die zijn toegestaan als de parameterwaarde. Aangezien we alleen rekening houden met de string 2013 of 2016, wil ik ervoor zorgen dat de gebruiker alleen deze waarden kan opgeven. Anders zal de functie onmiddellijk mislukken en hen op de hoogte stellen van de reden.

Je kunt parametervalidatie-attributen toevoegen direct onder het oorspronkelijke Parameter-trefwoord. In dit voorbeeld heb je binnen de haakjes van het parameterattribuut een array van items; 2013 en 2016. Een parametervalidatie-attribuut vertelt PowerShell dat de enige geldige waarden voor Versie 2013 of 2016 zijn. Als je iets probeert door te geven dat niet in de set staat, ontvang je een foutmelding waarin staat dat je slechts een beperkt aantal opties hebt.

Using the ValidateSet parameter validation attribute
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version 15.0 Install-Office : Cannot validate argument on parameter 'Version'. The argument "15.0" does not belong to the set "2013,2016" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again. At line:1 char:25 Install-Office -Version 15.0 ~~~~ CategoryInfo : InvalidData: (:) [Install-Office], ParameterBindingValidationException FullyQualifiedErrorId : ParameterArgumentValidationError,Install-Office

De attribuut ValidateSet is een veelgebruikt validatieattribuut. Voor een volledige uitleg van alle manieren waarop parameterwaarden beperkt kunnen worden, bekijk het help-onderwerp Functions_Advanced_Parameters door Get-Help about_Functions_Advanced_Parameters uit te voeren.

Parameter Sets

Stel dat je alleen bepaalde PowerShell-parameters wilt gebruiken met andere parameters. Misschien heb je een Path-parameter toegevoegd aan de Install-Office-functie. Deze padparameter installeert de versie die de installateur is. In dit geval wil je niet dat de gebruiker de Version-parameter gebruikt.

Je hebt parameter sets nodig.

Parameters kunnen worden gegroepeerd in sets die alleen met andere parameters in dezelfde set kunnen worden gebruikt. Met de onderstaande functie kun je nu zowel de Version-parameter als de Path-parameter gebruiken om het pad naar de installateur te bepalen.

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory)]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

Dit levert echter een probleem op, omdat de gebruiker beide parameters kan gebruiken. Bovendien, aangezien beide parameters verplicht zijn, worden ze gedwongen om beide te gebruiken wanneer dat niet de bedoeling is. Om dit op te lossen, kunnen we elke parameter in een parameter set plaatsen, zoals hieronder.

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ParameterSetName = 'ByVersion')]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory, ParameterSetName = 'ByPath')]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

Door een parameter set-naam te definiëren voor elke parameter, kun je groepen parameters samen beheren.

De standaardparameter set

Wat als de gebruiker probeert Install-Office uit te voeren zonder parameters? Hiermee is geen rekening gehouden, en je zult een vriendelijke foutmelding zien.

No parameter set

Om dit op te lossen, moet je een standaard parameter set definiëren binnen het CmdletBinding()-gebied. Dit vertelt de functie om een parameter set te kiezen om te gebruiken als er geen parameters expliciet zijn gebruikt door [CmdletBinding()] te veranderen naar [CmdletBinding(DefaultParameterSetName = 'ByVersion')]

Nu, telkens wanneer je Install-Office uitvoert, zal het om de Version-parameter vragen, omdat het die parameter set zal gebruiken.

Leidinginvoer

In de voorbeelden tot nu toe, heb je functies gemaakt met een PowerShell-parameter die alleen kan worden doorgegeven met behulp van de typische syntaxis -ParameterName Value. Maar, zoals je al hebt geleerd, heeft PowerShell een intuïtieve leiding die je in staat stelt objecten naadloos door te geven van het ene commando naar het andere zonder de “typische” syntaxis te gebruiken.

Wanneer je de leiding gebruikt, “keten” je commando’s samen met het pijpsymbool |, waardoor een gebruiker de uitvoer van een commando zoals Get-Service naar Start-Service kan sturen als een snelkoppeling voor het doorgeven van de Name-parameter naar Start-Service.

De “Oude” Manier met een Lus

In de aangepaste functie waarmee je werkt, installeer je Office en heb je een Version-parameter. Laten we zeggen dat je een lijst hebt met computernamen in een CSV-bestand in één rij met de versie van Office die op hen moet worden geïnstalleerd in de tweede rij. Het CSV-bestand ziet er ongeveer zo uit:

ComputerName,Version
PC1,2016
PC2,2013
PC3,2016

Je wilt de versie van Office installeren die naast elke computer op die computer staat.

Eerst moet je een ComputerName-parameter toevoegen aan de functie om voor elke iteratie van de functie een andere computernaam door te geven. Hieronder heb ik wat pseudocode gemaakt die wat code vertegenwoordigt die in de fictieve functie kan staan en een Write-Host-instantie toegevoegd om te zien hoe de variabelen binnen de functie worden uitgebreid.

Adding the ComputerName parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version, [Parameter()] [string]$ComputerName ) <# ## Maak verbinding met de externe computer met wat code hier Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Doe iets om de versie van Office op deze computer te installeren Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

Zodra je de ComputerName-parameter aan de functie hebt toegevoegd; zou je dit kunnen laten gebeuren door het CSV-bestand te lezen en de waarden voor de computernaam en de versie door te geven aan de Install-Office-functie.

$computers = Import-Csv -Path 'C:\ComputerOfficeVersions.csv'
foreach ($pc in $computers) {
    Install-Office -Version $_.Version -ComputerName $_.ComputerName
}

Opbouwen van pipeline-invoer voor parameters

Deze methode om de CSV-rijen te lezen en een lus te gebruiken om de eigenschappen van elke rij door te geven aan de functie is de “oude” manier om het te doen. In dit gedeelte wil je de foreach-lus helemaal overslaan en in plaats daarvan de pipeline gebruiken.

Zoals het nu is, ondersteunt de functie de pipeline helemaal niet. Het zou intuïtief zinvol zijn om aan te nemen dat je elke computernaam en versie aan de functie zou kunnen doorgeven met behulp van de pipeline. Hieronder lezen we het CSV-bestand en geven het direct door aan Install-Office, maar dit werkt niet.

No function pipeline input defined
PS> Import-Csv -Path 'C:\ComputerOfficeVersions.csv' | Install-Office

Je kunt aannemen wat je wilt, maar dat maakt het nog niet werkend. We krijgen een prompt voor het Version-parameter wanneer je weet dat Import-Csv het als een objecteigenschap verzendt. Waarom werkt het niet? Omdat je nog geen pijplijnondersteuning hebt toegevoegd.

Er zijn twee soorten pijplijninvoer in een PowerShell-functie; ByValue (volledig object) en ByPropertyName (een enkele objecteigenschap). Welke denk je dat de beste manier is om de uitvoer van Import-Csv aan de invoer van Install-Office te koppelen?

Zonder enige refactoring kun je de ByPropertyName-methode gebruiken, aangezien Import-Csv al de eigenschappen Version en ComputerName retourneert, aangezien ze kolommen zijn in de CSV.

Om pijplijnondersteuning toe te voegen aan een aangepaste functie is veel eenvoudiger dan je misschien denkt. Het is slechts een parameterattribuut dat wordt vertegenwoordigd door een van de twee trefwoorden; ValueFromPipeline of ValueFromPipelineByPropertyName.

In het voorbeeld wil je de ComputerName– en Version-eigenschappen die zijn geretourneerd door Import-Csv binden aan de Version– en ComputerName-parameters van Install-Office, dus je zult ValueFromPipelineByPropertyName gebruiken.

Aangezien we beide parameters willen binden, voeg je dit trefwoord toe aan beide parameters zoals hieronder getoond en voer je de functie opnieuw uit met behulp van de pijplijn.

Adding Pipeline Support
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) <# ## Verbinding maken met de externe computer met wat code hier Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Handelingen uitvoeren om de versie van Office op deze computer te installeren Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Versie } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

Dat is vreemd. Het liep alleen voor de laatste rij in de CSV. Wat gebeurt er? Het heeft zojuist de functie uitgevoerd voor de laatste rij omdat je een concept hebt overgeslagen dat niet vereist is bij het bouwen van functies zonder ondersteuning voor de pijplijn.

Vergeet het Process-blok niet!

Wanneer je een functie moet maken die pijplijnondersteuning vereist, moet je (minimaal) een “ingebouwd” blok binnen je functie toevoegen genaamd. process. Dit process-blok vertelt PowerShell dat bij het ontvangen van pijplijninvoer, de functie voor elke iteratie moet worden uitgevoerd. Standaard wordt alleen de laatste uitgevoerd.

Je kunt technisch gezien ook andere blokken zoals begin en end toevoegen, maar scripters gebruiken ze lang niet zo vaak.

Om PowerShell te vertellen deze functie voor elk binnenkomend object uit te voeren, voeg ik een process-blok toe dat de code erin bevat.

Adding the process block
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) process { <# ## Verbinding maken met de externe computer met wat code hier Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Handelingen uitvoeren om de versie van Office op deze computer te installeren Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Versie } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" } }

Nu kunt u zien dat de eigenschappen Versie en ComputerNaam van elk object dat is geretourneerd van Import-Csv, werden doorgegeven aan Install-Office en gebonden aan de parameters Versie en ComputerNaam.

Resources

Om dieper in te gaan op hoe functieparameters werken, bekijk mijn blogpost over PowerShell-functies.

I also encourage you to check my Pluralsight course entitled Building Advanced PowerShell Functions and Modules for an in-depth breakdown of everything there is to know about PowerShell functions, function parameters, and PowerShell modules.

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