Executing Code Locally and Remotely Using Local Variables

by Oct 26, 2012

Here is a piece of code that illustrates some of the challenges when writing remotable code:

function Get-Log($LogName='System', $ComputerName=$env:computername) 
{
    $code = { Get-EventLog -LogName $LogName -EntryType Error -Newest 5 }
    $null = $PSBoundParameters.Remove('LogName')
    Invoke-Command -ScriptBlock $code @PSBoundParameters
}

The function Get-Log is supposed to return the latest 5 error events from an event log. It is designed to work both locally and remotely, using PowerShell Remoting as transport.

So it accepts an optional parameter -ComputerName. This parameter gets handed over to Invoke-Command through splatting: @PSBoundParameters binds the parameter -ComputerName to Invoke-Command only when the user really submitted it. Else, Invoke-Command runs the code locally. Note how the function removes the parameter -LogName from the variable $PSBoundParameters because this parameter should not get handed over to Invoke-Command.

Instead, $LogName is used inside the script block to select the event log to read.

And this is the problem: locally, the code runs. Once you submit -ComputerName, the code runs remotely (provided you enabled remoting on the target computer and have proper permissions). However, $LogName is empty because it is a local variable and not defined on the remote system.

In a previous tip you learned about the new prefix "using" to mark local variables in PowerShell v3. So if you changed this line…

$code = { Get-EventLog -LogName $Using:LogName -EntryType Error -Newest 5 }

…the code would run fine remotely, but now it would fail locally. The prefix "using" is allowed only in remote calls or in a workflow inlinescript statement.

One solution is to submit the local variable as argument to the script block. This piece of code works both locally and remotely:

function Get-Log($LogName='System', $ComputerName=$env:computername) 
{
    $code = { param($LogName) Get-EventLog -LogName $LogName -EntryType Error -Newest 5 
    $null = $PSBoundParameters.Remove('LogName')
    Invoke-Command -ScriptBlock $code.GetNewClosure() @PSBoundParameters -Argument $LogName
}
PS> Get-Log
PS> Get-Log -computername storage1

Twitter This Tip! ReTweet this Tip!