Thankfully, these days a majority of our functions and cmdlets are kept tucked away in easily-installed modules. However, on occasion I have still found myself wishing I could just pull a function directly into a remote session to use.
Often it's a fairly small function, but on occasion it'll be something larger, making it not worth the time to rewrite the function to use in the remote session. As I alluded to in my previous post, functions are objects in PowerShell, just like everything else.
With that in mind, the following chain of reasoning seems to make sense:
If we can:
- Retrieve the function as an object, and
- Store the function into a variable, then it (hopefully) follows that we can also
- Send this object into a remote session and use it!
Capturing and Invoking a Captured Function
However, we have a… slight problem. Once it's in the remote session, how can we actually use it as a function? Well, as it turns out, there are a handful of ways one can do so:
function Get-Thing {
[CmdletBinding()]
param()
[PSCustomObject]@{
Thing = 1..20 | Get-Random
}
}
# Save a reference to the function object
$GetThing = Get-Item 'Function:\Get-Thing'
# Invoke it directly using '&'
& $GetThing
# Invoke its script block
$GetThing.ScriptBlock.Invoke()
# Create a new function with the existing one and call it as normal!
New-Item -Path 'Function:\Get-OtherThing' -Value $GetThing.ScriptBlock
Get-OtherThing
Note that last example. We never actually used the function
keyword to define
the function. We simply used New-Item
to create an item in the Function:
PSDrive, and the function immediately became available to be called.
Using the Function in a Remote Session
Caveats
The rest seems fairly straightforward, right? Well, sort of. Keep in mind, before we attempt this, that if either of the following are used in the captured function, you will have significant difficulty properly transferring it to a remote session:
- External libraries or files.
- References to third-party or non-default modules and commands.
- References to other locally-defined module functions or commands.
The issue here is that the function is essentially captured as only a few
components, the primary one being its ScriptBlock
. This will be executed on
the other end just like any other piece of script, so the same rules will apply.
Note that #3 essentially means that a majority of custom module functions cannot be transferred — at least without manually transferring any helper commands the function might use.
Methods
The most straightforward method is simply to feed the function object into an
Invoke-Command
block as-is and use it from there, either by passing it in the
-ArgumentList
or else by utilising $using:
to pull the value into the remote
"scope."
This method is particularly handy for when you want to use a local function in a larger remote script, as you may need to reuse it.
$Function = Get-Item 'Function:\Get-Thing'
# With -ArgumentList
$Thing = Invoke-Command -ComputerName 'RemotePC' -ScriptBlock {
param($Fn)
& $Fn
} -ArgumentList $Function
# Or, with $using:
$Thing = Invoke-Command -ComputerName 'RemotePC' -ScriptBlock {
& $using:Function
}
A more simplistic approach is possible when you only want to execute a local function once against a remote PC and then immediately return the data:
$Thing = Invoke-Command -ComputerName 'RemotePC' -ScriptBlock ${function:Get-Thing}
This last method is definitely more code-efficient, but as mentioned it will
only execute the function once, and in addition any arguments will have
to be supplied via -ArgumentList
, which is not always ideal.
Other Possibilities
At least in theory, it ought to be possible to do a similar thing with
compiled cmdlets. However, the obstacle here is that unfortunately cmdlets are
not directly accessible or settable like functions are; one would be forced
to use the & $Command
syntax with them.
Additionally, most reference external .dll
libraries or originate from these
libraries. I have yet to really give it a go, but I'm sure someone out there
will eventually figure out a way to transfer a compiled command in full to a
remote machine for execution.
But that's all for now; thanks for reading!