Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running script-block command with param using Start-Process

Tags:

powershell

Why does powershell think $dir is null when setting the location but not when writing the output?

$command = {
    param($dir)
    Set-Location $dir
    Write-Output $dir
}

# run the command as administrator
Start-Process powershell -Verb RunAs -ArgumentList "-NoExit -Command $command 'C:\inetpub\wwwroot'"

This results in the following output:

Set-Location : Cannot process argument because the value of argument "path" is null. Change the value of argument
"path" to a non-null value.
At line:3 char:2
+  Set-Location $dir
+  ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Set-Location], PSArgumentNullException
    + FullyQualifiedErrorId : ArgumentNull,Microsoft.PowerShell.Commands.SetLocationCommand

C:\inetpub\wwwroot

I also tried:

$command = {
    param($dir)
    Set-Location $dir
    Write-Output $dir
}

$outerCommand = {
    Invoke-Command -ScriptBlock $command -ArgumentList 'C:\inetpub\wwwroot'
}

# run the command as administrator
Start-Process powershell -Verb RunAs -ArgumentList "-NoExit -Command $outerCommand"

But then I got:

Invoke-Command : Cannot validate argument on parameter 'ScriptBlock'. The argument is null. Provide a valid value for
the argument, and then try running the command again.
At line:2 char:30
+  Invoke-Command -ScriptBlock $command 'C:\inetpub\wwwroot'
+                              ~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Invoke-Command], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeCommandCommand

Possible clue: if I set a local variable instead of using a param, it works perfectly:

$command = {
    $dir = 'C:\inetpub\wwwroot'
    Set-Location $dir
    Write-Output $dir
}

# run the command as administrator
Start-Process powershell -Verb RunAs -ArgumentList "-NoExit -Command $command"

Similar Q/As that didn't quite answer my question:

  • PowerShell - Start-Process and Cmdline Switches (I'm not trying to run an exe with command line switches, I'm trying to run powershell with a script block that needs a param passed into it)
  • How to use powershell.exe with -Command using a scriptblock and parameters (doesn't use Start-Process, which I need to run as administrator)
  • Powershell Value of argument path is NULL (uses Invoke-Command rather than Start-Process)
like image 893
devuxer Avatar asked Mar 05 '19 01:03

devuxer


People also ask

How do I call a PowerShell script with parameters?

You can run scripts with parameters in any context by simply specifying them while running the PowerShell executable like powershell.exe -Parameter 'Foo' -Parameter2 'Bar' . Once you open cmd.exe, you can execute a PowerShell script like below.

How do I Run a PowerShell script block?

The call operator is another way to execute script blocks stored in a variable. Like Invoke-Command , the call operator executes the script block in a child scope. The call operator can make it easier for you to use parameters with your script blocks.

What is start-Process in PowerShell?

The Start-Process cmdlet starts one or more processes on the local computer. By default, Start-Process creates a new process that inherits all the environment variables that are defined in the current process.

How do I Run a PowerShell script from the command line?

Running a PowerShell script from the Command Prompt If you would like to run a PowerShell script in CMD, you'll need to execute it by calling the PowerShell process with the -File parameter, as shown below: PowerShell -File C:\TEMP\MyNotepadScript. ps1. PowerShell -File C:\TEMP\MyNotepadScript.


1 Answers

$command is a script block ({ ... }) and stringifying a script block results in its literal contents, excluding the enclosing { and }.

Therefore, your expandable string "-NoExit -Command $command 'C:\inetpub\wwwroot'" literally expands to the following string - note the missing { ... } around the original script block:

 -NoExit -Command 
    param($dir)
    Set-Location $dir
    Write-Output $dir
 'C:\inetpub\wwwroot'

Due to the loss of the enclosing { and }, the new powershell process spawned by Start-Process quietly ignored the orphaned param($dir) statement and, given that the new process therefore had no $dir variable (given that it isn't an automatic variable either), the command failed, because Set-Location $dir was tantamount to Set-Location $null, which fails.[1]

Note that you can never pass script blocks as such to Start-Process - all arguments must be strings, because only strings can be passed to external processes.


The simplest solution in your case is to:

  • enclose the $command reference in your expandable string in { ... } to compensate for the loss of this enclosure due to stringification

  • and prepend & to ensure invocation of the resulting script block in the new process.

Here's a working solution:

Start-Process powershell -Verb RunAs -ArgumentList `
  "-NoExit -Command & { $command } 'C:\inetpub\wwwroot'"

Important: While not needed with the specific script block at hand, any script block that has embedded " chars. wouldn't be parsed correctly as part of the overall "..." string by the target process; to prevent this, they must be escaped as \", which is what external programs expect - including PowerShell via its CLI:

# Script block with embedded " chars.
$command = {
    param($dir)
    Set-Location $dir
    "Current dir: $dir"
}

# Embed the script block with " escaped as \"
Start-Process powershell -Verb RunAs -ArgumentList `
  "-NoExit -Command & { $($command -replace '"', '\"') } 'C:\inetpub\wwwroot'"

[1] It fails in Windows PowerShell; in PowerShell Core, it is tantamount to Set-Location without arguments, which changes to the current user's home folder.

like image 170
mklement0 Avatar answered Oct 16 '22 08:10

mklement0