Reputation: 42354
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:
Start-Process
, which I need to run as administrator)Invoke-Command
rather than Start-Process
)Upvotes: 10
Views: 6472
Reputation: 437248
$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:
-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.
Upvotes: 9
Reputation: 961
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
Upvotes: 1