PowerShell Invoke-Command: Een Uitgebreide Gids

IT-professionals werken zelden alleen op onze lokale computer. Met behulp van de PowerShell Invoke-Command cmdlet hoeft dat ook niet! Deze cmdlet stelt ons in staat code naadloos te schrijven alsof we op onze lokale computer aan het werk waren.

Door gebruik te maken van de PowerShell Remoting-functie, is de Invoke-Command cmdlet een veelgebruikte PowerShell-cmdlet waarmee de gebruiker code kan uitvoeren binnen een PSSession. Deze PSSession kan ofwel eerder zijn gemaakt met de New-PSSession cmdlet, of het kan snel een tijdelijke sessie aanmaken en afbreken.

Gerelateerd: PowerShell Remoting: De ultieme gids

Beschouw Invoke-Command als de PowerShell-variant van psexec. Hoewel ze op verschillende manieren zijn geïmplementeerd, is het concept hetzelfde. Neem een stuk code of opdracht en voer het “lokaal” uit op de externe computer.

Om Invoke-Command te laten werken, moet PowerShell Remoting zijn ingeschakeld en beschikbaar zijn op de externe computer. Standaard hebben alle machines met Windows Server 2012 R2 of later dit ingeschakeld, samen met de juiste firewall-uitzonderingen. Als je helaas nog Server 2008-machines hebt, zijn er meerdere manieren om Remoting in te stellen, maar een eenvoudige manier is door winrm quickconfig of Enable-PSRemoting uit te voeren op de externe machine.

Om te laten zien hoe Invoke-Command werkt met een “ad-hoc commando”, wat betekent dat er geen nieuwe PSSession hoeft te worden gemaakt, laten we zeggen dat je een externe Windows Server 2012 R2 of latere computer hebt die is toegetreden tot het domein. Dingen worden een beetje rommelig wanneer je werkt aan workgroup-computers. Ik open mijn PowerShell-console, typ Invoke-Command en druk op Enter.

PS> Invoke-Command
cmdlet Invoke-Command at command pipeline position 1
Supply values for the following parameters:
ScriptBlock:

I’m immediately asked to provide a scriptblock. The scriptblock is the code that we’re going to run on the remote computer.

Zodat we kunnen bewijzen dat de code binnen de scriptblok wordt uitgevoerd op de externe computer, laten we gewoon het hostname-commando uitvoeren. Dit commando geeft de hostnaam van de computer terug waarop het wordt uitgevoerd. Het uitvoeren van hostname op mijn lokale computer levert de naam op.

PS> hostname
MACWINVM

Laten we nu een scriptblok doorgeven met dezelfde code binnen een scriptblok naar Invoke-Command. Voordat we dat doen, vergeten we een vereiste parameter: ComputerName. We moeten Invoke-Command vertellen op welke externe computer dit commando moet worden uitgevoerd.

PS> Invoke-Command -ScriptBlock { hostname } -ComputerName WEBSRV1 WEBSRV1

Merk op dat de uitvoer van hostname nu de naam is van de externe computer WEBSRV1. Je hebt wat code uitgevoerd op WEBSRV1. Het uitvoeren van eenvoudige code binnen een scriptblok en deze doorgeven aan een enkele externe machine is de eenvoudigste toepassing van Invoke-Command, maar het kan veel meer doen.

Het doorgeven van lokale variabelen naar externe scriptblokken

Je gaat waarschijnlijk geen enkele Invoke-Command referentie hebben binnen een script. Jouw script zal waarschijnlijk tientallen regels lang zijn, variabelen op verschillende plaatsen gedefinieerd hebben, functies gedefinieerd in modules, enzovoort. Ook al lijkt het misschien onschuldig om wat code tussen een paar accolades te zetten, je verandert eigenlijk de gehele scope waarin die code wordt uitgevoerd. Immers, je stuurt die code naar een externe computer. Die externe computer heeft geen idee van alle lokale code op jouw machine, behalve wat er in het scriptblock staat.

Bijvoorbeeld, misschien heb je een functie met een computernaam en een bestandspad parameter. Het doel van deze functie is om wat software-installateur op de externe computer uit te voeren. Je kunt de computernaam en het “lokale” bestandspad aan de installateur doorgeven dat al op de externe computer staat.

De onderstaande functie lijkt redelijk, toch? Laten we het uitvoeren.

function Install-Stuff {
    param(
    	[Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$InstallerFilePath
	)
    Invoke-Command -ComputerName $ComputerName -ScriptBlock { & $InstallerFilePath }
}

PS> Install-Stuff -ComputerName websrv1 -InstallerFilePath 'C:\install.exe'
The expression after '&' in a pipeline element produced an object that was not valid. It must result in a command name, a script block, or a CommandInfo object.
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : BadExpression
+ PSComputerName        : websrv1

Het mislukt met een obscure foutmelding als gevolg van mijn gebruik van de ampersand-operator. De code was niet fout, maar het mislukte omdat $InstallerFilePath leeg was, ook al heb je een waarde doorgegeven met de functieparameter. We kunnen dit testen door de ampersand te vervangen door Write-Host.

Nieuwe functieregel: Invoke-Command -ComputerName $ComputerName -ScriptBlock { Write-Host "Installatiepad is: $InstallerFilePath" }

PS> Install-Stuff -ComputerName websrv1 -InstallerFilePath 'C:\install.exe'
Installer path is:
PS>

Let op dat de waarde van $InstallerFilePath niets is. De variabele is niet uitgebreid omdat deze niet naar de externe machine is doorgegeven. Om lokaal gedefinieerde variabelen naar het externe scriptblok door te geven, hebben we twee opties; we kunnen de variabelenaam voorafgaan met $using: binnen het scriptblok, of we kunnen de ArgumentList-parameter van Invoke-Command gebruiken. Laten we beide opties bekijken.

De ArgumentList-parameter

Één manier om lokale variabelen naar een extern scriptblok door te geven, is door de ArgumentList-parameter van Invoke-Command te gebruiken. Deze parameter stelt je in staat om lokale variabelen naar de parameter te sturen en lokale variabelen in het scriptblok te vervangen door aanduidingen.

Het doorgeven van de lokale variabelen naar de ArgumentList-parameter is eenvoudig.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { & $InstallerFilePath } -ArgumentList $InstallerFilePath

Wat sommige mensen in de war brengt, is hoe ze de variabelen binnen het scriptblok moeten structureren. In plaats van { & $InstallerPath } te gebruiken, moeten we het veranderen naar { & $args[0] } of {param($foo) & $foo }. Beide manieren werken hetzelfde, maar welke moet je gebruiken?

De ArgumentList-parameter is een objectverzameling. Objectverzamelingen stellen je in staat om één of meer objecten tegelijk door te geven. In dit geval geef ik er slechts één door.

Wanneer uitgevoerd, neemt het Invoke-Command cmdlet die verzameling en injecteert deze vervolgens in het scriptblock, waardoor het in feite wordt omgezet in een array genaamd `$args`. Onthoud dat `$args -eq ArgumentList`. Op dit punt zou je elk index van de verzameling refereren zoals je dat zou doen met een array. In ons geval hadden we slechts één element in de verzameling (`$InstallerFilePath`) wat “vertaald” werd naar `$args[0]`, wat betekent dat het eerste index in die verzameling. Als je echter meer had, zou je ze refereren met `$args[1]`, `$args[2]` enzovoort.

Bovendien, als je liever betere variabelenamen toewijst aan scriptblock-variabelen, kun je ook parameters toevoegen aan het scriptblock, net als bij een functie. Uiteindelijk is een scriptblock gewoon een anonieme functie. Om scriptblock parameters te maken, maak je een param blok met de naam van de parameter. Zodra deze is gemaakt, kun je vervolgens naar die parameter verwijzen in het scriptblock zoals hieronder.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { param($foo) & $foo } -ArgumentList $InstallerFilePath

In dit geval worden de elementen in de verzameling `ArgumentList` “gemapt” naar de gedefinieerde parameters in volgorde. De parameter namen doen er niet toe; de volgorde is belangrijk. `Invoke-Command` zal het eerste element in de `ArgumentList` verzameling nemen, zoeken naar de eerste parameter en die waarden mappen, hetzelfde doen voor de tweede, de derde enzovoort.

De $Using Construct

De $using constructie is een andere populaire manier om lokale variabelen door te geven aan een externe scriptblock. Met deze constructie kunt u de bestaande lokale variabelen hergebruiken door eenvoudigweg de variabelenaam vooraf te laten gaan door $using:. U hoeft zich geen zorgen te maken over een $args collectie noch over het toevoegen van een parameterblok.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { & $using:InstallerFilePath }

De PowerShell $using constructie is veel eenvoudiger, maar als je ooit bezig bent met het leren van Pester, zul je zien dat ArgumentList je vriend zal zijn.

Invoke-Command en New-PSSession

Technisch gezien gaat deze post alleen over Invoke-Command, maar om de bruikbaarheid te demonstreren, moeten we kort ingaan op het New-PSSession commando. Herinner je je eerder dat ik heb vermeld dat Invoke-Command “ad-hoc” commando’s kan gebruiken of bestaande sessies kan gebruiken.

Gedurende deze post hebben we alleen “ad-hoc” commando’s uitgevoerd op externe computers. We hebben een nieuwe sessie opgezet, code uitgevoerd en afgebroken. Dit is prima voor incidentele situaties, maar niet zozeer voor wanneer je tientallen commando’s op dezelfde computer uitvoert. In dit geval is het beter om een bestaande PSSession opnieuw te gebruiken door er van tevoren een aan te maken met New-PSSession.

Voor het uitvoeren van enige commando’s, moet je eerst een PSSession maken met New-PSSession. Dit kunnen we doen door eenvoudigweg $session = New-PSSession -ComputerName WEBSRV1 uit te voeren. Hiermee wordt een externe sessie op de server gemaakt, evenals een referentie naar die sessie op mijn lokale machine. Op dit punt kan ik mijn ComputerName-verwijzingen vervangen door Session en Session wijzen naar mijn opgeslagen $session-variabele.

Invoke-Command -Session $session -ScriptBlock { & $using:InstallerFilePath }

Wanneer uitgevoerd, zul je merken dat de prestaties sneller zijn omdat de sessie al is opgebouwd. Wanneer deze echter is voltooid, is het belangrijk om de open sessie te verwijderen met Remove-PSSession.

Samenvatting

De Invoke-Command PowerShell-cmdlet is een van de meest voorkomende en krachtige cmdlets die er zijn. Het is er een die ik persoonlijk het meest gebruik van bijna allemaal. De gebruiksvriendelijkheid en het vermogen om willekeurige code op externe computers uit te voeren, zijn uiterst krachtig en het is een commando dat ik aanbeveel om van boven naar beneden te leren!

Source:
https://adamtheautomator.com/invoke-command/