Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass the 'argument-line' of one PowerShell function to another?

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.

like image 561
jwfearn Avatar asked Jan 15 '11 22:01

jwfearn


People also ask

How do you pass arguments in PowerShell?

Passing arguments in PowerShell is the same as in any other shell: you just type the command name, and then each argument, separated by spaces. If you need to specify the parameter name, you prefix it with a dash like -Name and then after a space (or a colon), the value.

How do I pass two arguments in PowerShell script?

To pass multiple parameters you must use the command line syntax that includes the names of the parameters. For example, here is a sample PowerShell script that runs the Get-Service function with two parameters. The parameters are the name of the service(s) and the name of the Computer.

How do I call a function in another PowerShell script?

How to call a function: The inline approach. The easiest way to work with a PowerShell function is to include it in the script. The term for this is an inline function. Wherever the code is for the function, it must go above the first line that calls the function.


1 Answers

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)
like image 66
Jaykul Avatar answered Oct 24 '22 18:10

Jaykul