10

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:

2
  • Is there any reason why you are passing $command into it specifically and not just specifying the location in an argument variable like so $arguments = "-NoExit Set-Location 'C:\inetpub\wwwroot'" Start-Process powershell -Verb RunAs -ArgumentList $arguments Commented Mar 5, 2019 at 1:47
  • @OwainEsau, This is a stripped down example. My real script is too large and complex to be easily put into a string. Commented Mar 5, 2019 at 2:04

2 Answers 2

9

$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 -Command argument at hand, because of how the PowerShell CLI parses its process command line when -Command is used, embedded " chars. must be escaped as \",[2] as shown in the following example:
# 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.

[2] See this answer for details.

Sign up to request clarification or add additional context in comments.

Comments

1

I had a similar issue, but I wanted to place all of the commands needed for Start-Process -ArgumentList into a variable. Thanks to mklement0's answer above, I managed to find the solution.

Here's a slightly alternative answer, which includes powershell.exe's -Command (and any other parameters), in a Here-String [link 1] [link 2]:

$commandString = @'
    -NoExit -Command & {
        Write-Host -ForegroundColor Green \"Hello, new window!\"
        Set-Location -Path \"c:\\\"
        Get-ChildItem
        Start-Sleep -Seconds 3
    }
'@

Start-Process -FilePath "powershell.exe" -ArgumentList $commandString

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.