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 functionremove-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&
.- This surprising behavior is discussed in GitHub issue #11698.
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.
- A parsing bug breaks the call if the function name happens to contain
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