I have a PowerShell script that edits the registry, so it needs to run as admin. To do this, I start up a new PowerShell process from my running PowerShell script, and pass in part of the registry key path using a Script Block with a function in it. When I use double quotes within that function, PowerShell tries to interpret them as commands, rather than a string. If I use single quotes though, then everything works fine.
I've created a little stripped down sample powershell script that reproduces the problem. Here's the snippet:
$ScriptBlock = {
function Test
{
$status = "This is a string"
Write-Output $status
}
}
Start-Process -FilePath PowerShell -ArgumentList "-NoExit -NoProfile -ExecutionPolicy Bypass -Command & {$ScriptBlock Test}"
So in the new PowerShell process it will first define the code in the script block and then call the Test method, and it produces this error:
This : The term 'This' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
So it's trying to treat the string as a commad, as if I had just typed This is a string
by itself on a new line in my script.
If I change the line
$status = "This is a string"
to
$status = 'This is a string'
the script works as expected and simply outputs the string This is a string
.
Another strange problem I've noticed is that if I don't use a variable and just use:
Write-Output "This is a string"
then it outputs each word on a separate line like this:
This
is
a
string
but if I use single quotes like this:
Write-Output 'This is a string'
then it outputs the entire sentence on one line as expected.
Does anybody know why PowerShell is behaving strangely in these situations?
Answer
So as TessellatingHeckler mentions, the solution is do wrap anything that is double quoted in double double quotes, single quotes, or you can use brackets.
So in my example, you would change:
$status = "This is a string"
to this:
$status = """This is a string"""
or this:
$status = '"This is a string"'
or this:
$status = {"This is a string"}
If you want to evaluate a variable in your string though (i.e. see the variable's value), then you have to go with the double double quotes method:
$status = """This is a string that evaluates $someVariable"""
Still not sure if this is a Bug or By Design, but at least we have a workaround, as this fixes both of the problems I described above.
If I change your script to be
-Command $ScriptBlock
Run it, and have it open a new shell window, then run
gci function:test | fl
to see the function definition in the new window, the code shown is
$status = This is a string
with the same test on the single quote version it shows
$status = 'This is a string'
So it's losing the double quotes. Escape them with double quotes
$status = """This is a string"""
and they come through OK. Also even though scriptblocks are compiled code, it looks to me like they are embedded as text if you expand them into a string:
> $s = { "hello" }
> "---$s---"
---"hello"---
So I think you're hitting this kind of quoting problem: PowerShell stripping double quotes from command line arguments and the answer by Droj particularly, saying "The weird thing about sending parameters to external programs is that there is additional level of quote evaluation. I don't know if this is a bug, but I'm guessing it won't be changed, because the behavior is the same when you use Start-Process and pass in arguments.".
PowerShell is expanding the script block as a string into your command, then those single quotes around the string are being reinterpreted as quoted parameters and removed somewhere in the invoking. Which is either a known problem, or a bug, or by-design, depending on how you read that linked connect article.
来源:https://stackoverflow.com/questions/22544930/powershell-executing-a-function-within-a-script-block-using-start-process-does-w