I'm trying to write some PowerShell functions that do some stuff and then transparently call through to existing built-in functions. I want to pass along all the arguments untouched. I don't want to have to know any details of the arguments.
I tired using 'splat' to do this with @args
but that didn't work as I expected.
In the example below, I've written a toy function called myls
which supposed to print hello! and then call the same built-in function, Get-ChildItem
, that the built-in alias ls
calls with the rest of the argument line intact. What I have so far works pretty well:
function myls
{
Write-Output "hello!"
# $MyInvocation | Format-List # <-- uncomment this line for debug info
Invoke-Expression ("Get-ChildItem " + $MyInvocation.UnboundArguments -join " ")
}
A correct version of myls
should be able to handle being called with no arguments, with one argument, with named arguments, from a line containing multiple semi-colon delimited commands, and with variables in the arguments including string variables containing spaces. Basically, it should be a drop-in alternative to ls
.
The tests below compare myls
and the builtin ls
:
[NOTE: output elided and/or compacted to save space]
PS> md C:\p\d\x, C:\p\d\y, C:\p\d\"jay z"
PS> cd C:\p\d
PS> ls # no args
PS> myls # pass
PS> cd ..
PS> ls d # one arg
PS> myls d # pass
PS> $a="A"; $z="Z"; $y="y"; $jz="jay z"
PS> $a; ls d; $z # multiple statements
PS> $a; myls d; $z # pass
PS> $a; ls d -Exclude x; $z # named args
PS> $a; myls d -Exclude x; $z # pass
PS> $a; ls d -Exclude $y; $z # variables in arg-line
PS> $a; myls d -Exclude $y; $z # pass
PS> $a; ls d -Exclude $jz; $z # variables containing spaces in arg-line
PS> $a; myls d -Exclude $jz; $z # FAIL!
Is there a way I can re-write myls
to get the behavior I want?
Short answer: Yes, it's possible. The bad news: it requires code which knows details of the parameters and other metadata about the function one wishes to call through to. The good news: one doesn't need to write this all oneself. This metadata is available programatically and there exist modules available which one can use to auto-generate skeleton proxy code (see @Jaykul's answer below). I choose to use the module named "MetaProgramming". Once imported, generating a drop-in myls
script is dead simple:
New-ProxyCommand ls > .\myls.ps1
Then one can start customizing the newly-generated myls.ps1
script, like this:
...
begin
{
Write-Output "hello!" # <-- add this line
try {
$outBuffer = $null
...
Voila! This new version passes all the tests.
If you want a drop-in wrapper for ls, you should write a proper Proxy Command. There are a couple of versions of the generator on PoshCode.org, including the one from Lee Holmes' PowerShell Cookbook,
But the proxy command generator is built in now, so you can just write:
$CommandName = "Get-ChildItem"
$Command = Get-Command $CommandName
[System.Management.Automation.ProxyCommand]::Create($Command)