There are two major issues (leaving the obvious mistake of attempting to reference a variable inside a single-quoted string aside):
Any argument you want to pass to a new powershell
instance via -Command
must be escaped in non-obvious ways if it contains "
and/or \
chars, which is especially likely if you're passing a piece of PowerShell source code.
- The escaping issue can generally be solved by Base64-encoding the source-code string and passing it via the
-EncodedCommand
parameter - see this answer of mine to a related question for how to do that, but a more concise alternative is presented below.
If the source code being passed references any variables that only exist in the calling session, the new instance won't see them.
- The solution is not to reference session-specific variables in the source code being passed, but to pass their values as parameter values instead.
To solve the local-variable-not-seen-by-the-new-instance problem, we must rewrite the script block to accept parameters:
$scriptBlock={
param($projectFolder, $argList)
# For demonstration, simply *output* the parameter values.
"folder: [$projectFolder]; arguments: [$argList]"
}
Now we can apply the necessary escaping, using PetSerAl's sophisticated -replace
expression from his comment on the question.
We can then invoke the resulting string with & {...}
while passing it parameter values (I'm omitting the -WorkingDirectory
and -PassThru
parameters for brevity):
# Parameter values to pass.
$projectFolder = 'c:\temp'
$argList='-v -f'
Start-Process -NoNewWindow powershell -ArgumentList '-noprofile', '-command',
(('& {' + $scriptBlock.ToString() + '}') -replace '\"|\\(?=\\*("|$))', '\$&'),
"'$projectFolder'",
"'$argList'"
For an explanation of the regular expression, again see this answer.
Note how the variable values passed as parameters to the script block are enclosed in '...'
inside a "..."
-enclosed string in order to:
- pass the values as a single parameter value.
- protect them from another round of interpretation by PowerShell.
Note: If your variable values have embedded '
instances, you'll have to escape them as ''
.
The above yields:
folder: [c:\temp]; arguments: [-v -f]
Alternative with a temporary, self-deleting script file:
Using -File
with a script file has the advantage of being able to pass parameter values as literals, with no concern over additional interpretation of their contents.
Caveat: As of PowerShell Core v6-beta.3, there is a problem when passing parameter values that start with -
: they are not bound as expected; see this GitHub issue.
To work around this problem, the sample script block below accesses only the first parameter by name, and relies on all remaining ones binding via the automatic $Args
variable.
# Define the script block to be executed by the new PowerShell instance.
$scriptBlock={
param($projectFolder)
# For demonstration, simply *output* the parameter values.
"folder: [$projectFolder]; arguments: [$Args]"
}
# Parameter values to pass.
$projectFolder = 'c:\temp'
$argList='-v -f'
# Determine the temporary script path.
$tempScript = "$env:TEMP\temp-$PID.ps1"
# Create the script from the script block and append the self-removal command.
# Note that simply referencing the script-block variable inside `"..."`
# expands to the script block's *literal* content (excluding the enclosing {...})
"$scriptBlock; Remove-Item `$PSCommandPath" > $tempScript
# Now invoke the temporary script file, passing the arguments as literals.
Start-Process -NoNewWindow powershell -ArgumentList '-NoProfile', '-File', $tempScript,
$projectFolder,
$argList
Again, the above yields:
folder: [c:\temp]; arguments: [-v -f]
-ArgumentList '-Command', """$( $CommandBlock -replace '\"|\\(?=\\*("|$))', '\$&' )"""
. But still note, that each PowerShellRunspace
have its own variables, and new PowerShell process will not shareRunspace
with its parent. – Plumbism