Home > Enterprise >  Powershell invoke-command ArgumentList with optional argument
Powershell invoke-command ArgumentList with optional argument

Time:02-26

I have a function that works with the invoke-command function either without any arguments or with all arguments. What I would like to do is run the function via invoke-command with only 1 argument. Here is my code

function remove-oldfiles {
param ([String]$days = 28,
[string]$path = "c:\temp\archive")

if ( -not (test-path $path) ) {throw "path not found"}

$cmd = ((Get-ChildItem -Path $path -Recurse |
        where {$_.lastwritetime -lt (get-date).adddays(-$days) -and -not $_.psicontainer}))

$cmd
write-Host("do you want to delete the files? ") -ForegroundColor Red
$ans = (read-host).ToUpper()

if ($ans -eq 'Y') { 
    write-host("deleting files...") 
    $cmd | remove-item -Force
    }
}

Both of these commands work:

Invoke-Command -ComputerName remote_machine -ScriptBlock ${function:remove-oldfiles} -ArgumentList 90, "c:\temp\archive"
Invoke-Command -ComputerName remote_machine -ScriptBlock ${function:remove-oldfiles} 

But if I try and run it with only 1 argument it doesn't work. Tried:

Invoke-Command -ComputerName remote_machine -ScriptBlock ${function:remove-oldfiles} -ArgumentList '-path "c:\temp\archive"'
Invoke-Command -ComputerName remote_machine -ScriptBlock ${function:remove-oldfiles} -ArgumentList "c:\temp\archive"

Is it possible to use Invoke-Command with -ArgumentList with just some arguments and not all?

CodePudding user response:

No, unfortunately Invoke-Command does not support passing named arguments via -ArgumentList / -Args, only positional ones - see this answer for details - and the same goes for other cmdlets that support this parameter, notably Start-Job / Start-ThreadJob and Start-Process.

Therefore, with -ArgumentList, in order to bind the -path parameter - the 2nd positional one - you invariably must also pass an argument for the 1st positional one, -days.

Workaround:

Invoke-Command -ComputerName remote_machine -ScriptBlock {
  param($funcBody)
  # Here you can use named arguments, as usual.
  # To include values from the caller's scope, use the $using: scope, e.g.,
  # to use the value of local variable $archivePath:
  #  -path $using:archivePath
  & ([scriptblock]::Create($funcBody)) -path c:\temp\archive
} -ArgumentList ${function:remove-oldfiles}

Note:

  • Even though ${function:remove-oldfiles} contains the body of function remove-oldfiles as a script block in the caller's scope, it turns into a string when passed through the remoting de/serialization infrastructure, and must therefore be rebuilt as a script block with [scriptblock]::Create() in the remote session in order to be callable with &.

  • In principle, you should be able to include the function body directly in the remotely executing script block, via a $using: reference, namely
    ${using:function:remove-oldfiles} in your case - however, there are two separate bugs that prevent that as of PowerShell Core 7.3.0-preview.2:

    • A parsing bug breaks the call if the function name happens to contain -, such as in your case.
    • For other function names, such a reference seemingly deserializes to the empty string in the remote session.
    • Curiously, Start-Job, which also uses the remoting de/serialization infrastructure, does not exhibit these problems.

CodePudding user response:

The param() block defines what the -ArgumentList passes to the -ScriptBlock. You are passing 2 elements into the param block of your function. The variables have assignments as well, which I believe is why the command works without the switch.

In order for the the -ArgumentList to accept one argument, you could try to move $days=28 outside the param block, such that it is still in the scope of the function, such as:

  param ([string]$path = "c:\temp\archive")
  $days = 28
  • Related