Search code examples
powershelljenkinsinvoke-command

Running Invoke-Command to Stop-Process while passing a variable not working


The syntax below absolutely works to stop a process on a remote computer:

$hostname = 'PC1'

$process = 'program1*'

$Session = New-PSSession $Hostname

Invoke-Command -Session $Session -ScriptBlock {param($process) Stop-Process -ProcessName $process -Force} -ArgumentList $process

$Session | Remove-PSSession

However, in Jenkins, I parameterized hostname and process, so the user enters the input hostname and process, and Jenkins creates the two variables $env:hostname and $env:process. This is not working well, the argument is not being passed onto Stop-Process:

$session = New-PSSession $env:hostname

Invoke-Command -Session $session -ScriptBlock {param($env:process) Stop-Process -ProcessName $env:process -Force} -ArgumentList $env:process

$Session | Remove-PSSession

The error I'm getting is

Cannot bind argument to parameter 'Name' because it is null.
At C:\Users\user.name\AppData\Local\Temp\jenkins10480966582412717483.ps1:25 char:1
+ Invoke-Command -Session $session -ScriptBlock {param($env:process) St ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Stop-Process], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.StopProcess 
   Command
    + PSComputerName        : pc1
 
Build step 'PowerShell' marked build as failure
Finished: FAILURE

I know this has something to do with quotes, please give me a hand, thank you!


Solution

  • Don't change anything about your Invoke-Command call except the input argument, i.e. what you pass to -ArgumentList:

    Invoke-Command `
      -Session $Session `
      -ScriptBlock { param($process) Stop-Process -ProcessName $process -Force } `
      -ArgumentList $env:process
    

    Don't ever use an environment-variable reference to define a parameter variable: param($env:process) In fact, PowerShell should not even allow such parameter declarations, but does as of 7.2.x - see GitHub issue #18401.

    The name of the parameter variable, as declared inside the param(...) block, is unrelated to whatever value you pass to it via -ArgumentList (i.e., it is unrelated to what the name of the variable is that you happen to be using to pass that value, and whether that variable is a regular (shell-only) variable or an environment variable), and inside the -ScriptBlock argument you must only refer to the value via the parameter variable.

    See the relevant section of the conceptual about_Functions help topic, which, with respect to the params(...) method of declaring parameters, applies equally to script blocks ({ ... }).


    Note: An alternative to passing values as arguments to a remotely executing script block, via Invoke-Command's -ArgumentList parameter, is to use the $using: scope to refer to the values of variables in the caller's scope directly in the script block, as shown in Dennis' answer.

    In PowerShell (Core) 7+, you can apply the $using: scope directly to an environment-variable reference, which - unfortunately - doesn't work in Windows PowerShell, due to a bug:[1]

    # PS 7+ alternative.
    # In Windows PowerShell, stick with -ArgumentList or use Dennis'
    # approach via an intermediate, regular variable.
    Invoke-Command `
      -Session $Session `
      -ScriptBlock { Stop-Process -ProcessName $using:env:process -Force }
    

    [1] It works with Start-Job, but curiously not with remoting Invoke-Command calls.