I am using this code to execute remote code (MSI installs) on a server. Passing double quote through the script is just not working. I tried two variations as given below (#3 and #4) along with the outputs.
Input #1 (Simple case to test double quotes in the command)
powershell.exe -inputformat none -File client.ps1 -target 1.2.3.4 -port 5985 -password "pass" -username "user" -command "echo hello"
Output (Works)
hello
Input #2 (Understandable, this won't work)
powershell.exe -inputformat none -File client.ps1 -target 1.2.3.4 -port 5985 -password "pass" -username "user" -command "echo hello world"
Output
hello
world
Input #3
powershell.exe -inputformat none -File client.ps1 -target 1.2.3.4 -port 5985 -password "pass" -username "user" -command "echo `"hello world`""
Output (What happened to the other word?)
hello
Input #4
powershell.exe -inputformat none -File client.ps1 -target 1.2.3.4 -port 5985 -password "pass" -username "user" -command #'
>> echo "hello world"
>> '#
>>
Output (Again, the 2nd word is missing)
hello
If the echo works, I should be able to incorporate the changes to the MSI commands in the Runspace based usage I am doing.
MSI setup works fine if I use the following. Notice the single quotes.
msiexec /qn /i 'C:\setups\My Software.msi'
But, I need to pass public properties and MSI does not like single quote in it. Trying to run the following opens up the MSI arguments dialog.
msiexec /qn /i 'C:\setups\My Software.msi' MYPROP='My Value'
Running this from the local command prompt on the server works fine.
msiexec /qn /i "C:\setups\My Software.msi" MYPROP="My Value"
If you're calling this from cmd.exe, you'll have to escape the double quotes according to CMD's rules.
powershell.exe -command "echo \"hello world\""
Output
hello world
Personally, I would recommend avoiding passing the parameters in from the command line if at all possible. Maybe you could store the parameter values in a file (eg. serialized XML, JSON), and have the PowerShell script read the file?
Better yet, I would suggest doing any work with processes (eg. msiexec.exe) through the Start-Process cmdlet. That way, you can build up the value for the -ArgumentList parameter in a variable, and then be guaranteed that it will get passed through exactly the way you want it, and furthermore, you will not be restricted to the quoting rules of cmd.exe.
Consider the following:
$ArgumentList = '/package "c:\setups\My Software.msi" /passive /norestart /l*v "{0}\temp\Install My Software.log" MYPROP="My Value With Spaces"' -f $env:windir;
Start-Process -FilePath msiexec.exe -ArgumentList $ArgumentList;
Or you can encode your command as base64 strings to avoid any special characters from accidentally being interpreted, like encapsulated double quotes.
powershell.exe -EncodedCommand "ZQBjAGgAbwAgACIAaABlAGwAbABvACAAdwBvAHIAbABkACIA"
Result
hello world
ZQBjAGgAbwAgACIAaABlAGwAbABvACAAdwBvAHIAbABkACIA .... is base64 representation of this. See how I didn't need to escape anything.
echo "hello world"
Related
I would like to pass the powershell arguments to an process. The arguments may contain spaces.
Powershell Code:
$proc = Start-Process -FilePath "wsl.exe" -ArgumentList $args -NoNewWindow -PassThru
$proc | Wait-Process
Run Command
powershell -noprofile -executionpolicy bypass -file sh.ps1 bash -c "`"echo Hello World`""
No Output.
Running this command with static array works fine:
$proc = Start-Process -FilePath "wsl.exe" -ArgumentList #("bash", "-c", "`"echo Hello World`"") -NoNewWindow -PassThru
$proc | Wait-Process
Output
Hello World
What I needed todo to escape the arguments from the CLI args?
See https://github.com/PowerShell/PowerShell/issues/5576 why I have to escape spaces
The obvious answer is this, with $myargs being an array of strings. "echo Hello World" needs embedded double-quotes (or single-quotes) around it, which complicates things.
$myargs = "bash", "-c", "'echo Hello World'"
$proc = Start-Process -FilePath wsl.exe -ArgumentList $myargs -NoNewWindow -PassThru
$proc | Wait-Process
Hello World
I would just do:
.\sh.ps1 bash -c 'echo hello world'
# sh.ps1 contains:
# wsl $args
Or the wsl bash for windows:
bash -c 'echo hello world'
I happened to have pwsh installed within wsl. Without the single-quotes, each word would be put on a new line.
wsl pwsh -c "echo 'hello world'"
This works for me within another powershell, with yet another set of embedded quotes:
powershell -noprofile -executionpolicy bypass -file sh.ps1 bash -c "`"'echo Hello World'`""
There's no good reason to use Start-Process in your case (see this GitHub docs issue for guidance on when Start-Process is and isn't appropriate).
Instead, use direct execution, which is not only synchronous and runs in the same console window (for console applications such as wsl.exe), but also allows you to directly capture or redirect output.
In doing so, you'll also be bypassing the longstanding Start-Process bug you mention (GitHub issue #5576), because PowerShell double-quotes arguments passed to external programs as needed behind the scenes (namely if they contain spaces).
$exe, $exeArgs = $args # split argument array into exe and pass-thru args
wsl.exe -e $exe $exeArgs # execute synchronously, via wsl -e
Note that you then also don't need to embed escaped " in your PowerShell CLI call:
powershell -noprofile -executionpolicy bypass -file sh.ps1 bash -c "echo Hello World"
However, if your arguments truly needed embedded ", you'd run into another longstanding PowerShell bug that affects only direct invocation of external programs, necessitating manual escaping with \ - see this answer.
If you do need to use Start-Process - e.g. if you want the process to run in a new (console) window - you'll have to provide embedded quoting around those $args elements that need it, along the following lines:
$quotedArgs = foreach ($arg in $args) {
if ($arg -notmatch '[ "]') { $arg }
else { # must double-quote
'"{0}"' -f ($arg -replace '"', '\"' -replace '\\$', '\\')
}
}
$proc = Start-Process -FilePath "wsl.exe" -ArgumentList $quotedArgs -PassThru
$proc | Wait-Process
Currently, I am able to call cmd from powershell using the following command:
Start-Process cmd -ArgumentList '/K "ping 192.168.1.1"'
What I am trying to do is add multiple arguments to pass onto the command, but it is not working. For example:
Start-Process cmd -ArgumentList '/K "title test" /K "ping 192.168.1.1"'
Is there a way to do this?
Edit: My goal is to have a cmd window open, pinging the address listed, but also to pass the "title" argument so the window is titled.
Since you're calling cmd.exe, use its statement-sequencing operator, &, to pass multiple commands to cmd /K:
Start-Process cmd -ArgumentList '/K title test & ping 192.168.1.1'
Note:
Start-Process's -ArgumentList (-Args) parameter technically accepts an array of arguments. While passing 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.
cmd.exe's built-in title command inexplicably includes double quotes in the title if you enclose the argument in "..."; thus, if you want to specify a title that contains spaces, leave it unquoted and escape metacharacters other than spaces with ^; e.g., to pass test & more as the title, use title test ^& more
start-process is sometimes tricky... try to add the elements as strings to an array and pass it over to start-process.
But in your case... idk what that /K should do, but in case of ping - ping is the process to start, not cmd ;-)
start-process ping.exe -argumentlist "127.0.0.1"
start-process ping.exe -argumentlist "127.0.0.1 /t"
as you are already using PowerShell
test-connection 127.0.0.1
Here is an example where I did something simliar:
$cmdArray = #(
If ($token){
"-c"
"`"http.extraHeader=Authorization: Bearer $token`""
}
If ($clone){
'clone'
}
If ($fetch){
'fetch'
'-f'
'origin'
If ($tag){
"tags/$($tag):tags/$($tag)"
}
}
Else {
"`"$uri`""
}
If ($whatif){
'--dry-run'
}
)
$result = Start-Process $pathGitexe -ArgumentList $cmdArray -Wait -NoNewWindow -PassThru
ok, based on your comment you need this:
$ips = #("192.168.1.1","192.168.1.2")
$ips | %{
start-process ping.exe -ArgumentList $_
}
I am trying to launch an elevated CMD window from PowerShell but I am running into some issues. Below is the Code I have now. There is an admin account on the machine that has the username of "test" and a Password of "test"
$username = "test"
$password = ConvertTo-SecureString "test" -AsPlainText -Force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password
Start-Process "cmd.exe" -Credential $cred
This is all working fine for running an application from the user profile with no administrator rights that this script sits in, but when calling the cmd.exe it launches as expected with elevated rights but then immediately closes.
I have also tried calling it with the following:
Start-Process "cmd.exe" -Credential $cred -ArgumentList '/k'
This also is not working.
I tested the elevated permissions by passing in an argument as follows and this works fine.
Start-Process "cmd.exe" -Credential $cred -ArgumentList 'dir > dir.txt'
This will write out a dir.txt file to the C:\Windows\System32\WindowsPowerShell\v1.0 directory which is blocked on the user account but not for the administrator account test.
Any help on getting a persistent cmd window to show would be greatly appreciated.
Thanks
Note: SomeShinyObject came up with the fundamentals of the approach in his answer, but his parameter-passing technique is not robust (update: since corrected) - do not use script blocks in lieu of strings - see bottom.
-Verb RunAs is what makes Start-Process launch a process elevated.
However, -Verb RunAs cannot be combined with the -Credential parameter, so you cannot directly control under what user account the elevation happens - but that is generally not necessary:
If the current user is an administrator, elevation invariably happens in the context of that user, with the GUI prompt just asking for confirmation.
Otherwise, a GUI dialog is shown, asking for an administrator's username and password (with the username field blank).
Security caveats:
Storing a password as plain text is a security risk in general.
Additionally, if you let non-administrative users execute the code below with stored admin credentials, you're effectively giving them administrative rights.
If you still want to implement your script as specified, a workaround requires nesting 2 Start-Process calls:
The 1st one runs an (invariably) non-elevated command invisibly in the context of the specified user - assumed to be an administrative user - using -Credential.
Since the specified user is an administrator, it's not a concern here, but if -Credential targets a non-admin user, it is advisable to also specify a -WorkingDir argument that the specified user is known to have permissions to access - otherwise, the call may fail (the current location is retained, and the target user may not be allowed to access it).
The 2nd one, embedded in the 1st, then uses -Verb RunAs to run the target command elevated, which then happens in the context of the specified user.
Note: Even with a credentials object that includes the password, you will still get the yes/no UAC prompt to confirm the intent to elevate - unless UAC has been turned off (which is not advisable).
The working directory will invariably be $env:SYSTEMROOT\System32; -Verb RunAs ignores even a -WorkingDirectory value; if you want to change to a specific directory, embed a cd command in the command passed to cmd.exe; the bottom section of this related answer shows this technique with a powershell.exe / Set-Location call.
This command does exactly what you asked for - please note the security caveat:
# Construct the credentials object
$username = "jdoe"
# CAVEAT: Storing a password as plain text is a security risk in general.
# Additionally, if you let non-administrative users execute this
# code with a stored password, you're effectively giving them
# administrative rights.
$password = ConvertTo-SecureString "test" -AsPlainText -Force
$cred = New-Object PSCredential -Args $username, $password
# Start an elevated Command Prompt (cmd) as user $username.
Start-Process powershell.exe -Credential $cred -WindowStyle Hidden `
'-noprofile -command "Start-Process cmd.exe -Verb RunAs"'
Note that the embedded, 2nd command is passed as a single string to the (implied) -ArgumentList (a.k.a. -Args) parameter.
In this simple case, with only 1 level of embedded quoting - the " instances inside the '...' string - and no need for expansions (string interpolation), passing a single string is a viable option, but with more sophisticated commands quoting gets tricky.
-ArgumentList is defined as type [string[]], i.e., an array of string arguments. If you pass multiple, ,-separated arguments, it is PowerShell that synthesizes the command line for you:
Caveat: A long-standing bug unfortunately requires that argument with embedded spaces be enclosed in embedded double-quoting - see this answer for details.
The following command demonstrates this technique: It is a variant that passes a command for cmd.exe to execute through, and uses a variable reference in that command:
$msg = 'This is an elevated Command Prompt.'
Start-Process powershell.exe -Credential $cred -WindowStyle Hidden -Args `
'-noprofile', '-command', "Start-Process cmd.exe -Verb RunAs -Args /k, echo, '$msg'"
The cmd.exe command that is ultimately executed (with elevation) is:
cmd /k echo This is an elevated Command Prompt.
Optional Reading: Why using script blocks in lieu of strings is ill-advised
tl;dr
Do not get into the habit of using script blocks where strings are expected. While convenient, it is not robust, and remembering when and why it will fail is nontrivial.
At first glance, script blocks ({ ... }) seem like a convenient option:
Start-Process cmd -ArgumentList { /k echo hi! }
The above executes cmd /k echo hi! in a new console window, as expected.
The syntax is convenient, because the { ... } seemingly provide a context in which quoting is easy: you're free to use embedded " and ' instances to construct your command line.
However, what happens behind the scenes is that a script block is converted to a string, because that's the type of argument(s) -ArgumentList expects, and when a script block is converted to a string, its literal contents - everything between { and } - is used.
This means that no string interpolation takes place, so you cannot use variables or subexpressions.
Take this attempt to pass a command based on a variable:
Start-Process cmd -ArgumentList { /k echo Honey, I`'m $HOME! }
What this will execute is: cmd /k echo Honey, I'm $HOME! - $HOME was not expanded.
By contrast, passing either an interpolated string or the arguments individually works as intended:
# As a single string (argument list):
Start-Process cmd -ArgumentList "/k echo Honey, I'm $HOME!"
# As an array of arguments:
Start-Process cmd -ArgumentList /k, echo, "Honey, I'm $HOME!"
$HOME is expanded (interpolated) in both cases, and something like
cmd /k echo Honey, I'm C:\Users\jdoe is executed.
The best way is to double up your Start-Process first with a -Credential parameter with your admin credentials and then with a -Verb runas on your second Start-Process. After that it gets a little complicated with quoting for CMD.exe.
Overall, it should look something like this.
Start-Process PowerShell -ArgumentList {-noexit -noprofile -Command "Start-Process powershell -argumentlist {-command cmd.exe -args \"/K #yourcommands# \"}" -verb runas} -Credential $Cred
So scratch this. Read mklement0's answer as to why
There's always opportunity to learn more and I didn't know wrapping an ArgumentList in a ScriptBlock prevented variable expansion. So...don't do that.
The method stays the same, though. You'll still need two Start-Process calls, just now you have to get the quoting right.
#Both of these work
Start-Process powershell -Credential $cred -ArgumentList "-noprofile", "-command", "Start-Process cmd.exe -Verb RunAs -ArgumentList /k, echo, 'something'"
# $Something will expand into it's value rather than literally
Start-Process powershell -Credential $cred -ArgumentList "-noprofile", "-command", "Start-Process cmd.exe -Verb RunAs -ArgumentList /k, echo, '$something'"
I am trying to launch an elevated CMD window from PowerShell but I am running into some issues. Below is the Code I have now. There is an admin account on the machine that has the username of "test" and a Password of "test"
$username = "test"
$password = ConvertTo-SecureString "test" -AsPlainText -Force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password
Start-Process "cmd.exe" -Credential $cred
This is all working fine for running an application from the user profile with no administrator rights that this script sits in, but when calling the cmd.exe it launches as expected with elevated rights but then immediately closes.
I have also tried calling it with the following:
Start-Process "cmd.exe" -Credential $cred -ArgumentList '/k'
This also is not working.
I tested the elevated permissions by passing in an argument as follows and this works fine.
Start-Process "cmd.exe" -Credential $cred -ArgumentList 'dir > dir.txt'
This will write out a dir.txt file to the C:\Windows\System32\WindowsPowerShell\v1.0 directory which is blocked on the user account but not for the administrator account test.
Any help on getting a persistent cmd window to show would be greatly appreciated.
Thanks
Note: SomeShinyObject came up with the fundamentals of the approach in his answer, but his parameter-passing technique is not robust (update: since corrected) - do not use script blocks in lieu of strings - see bottom.
-Verb RunAs is what makes Start-Process launch a process elevated.
However, -Verb RunAs cannot be combined with the -Credential parameter, so you cannot directly control under what user account the elevation happens - but that is generally not necessary:
If the current user is an administrator, elevation invariably happens in the context of that user, with the GUI prompt just asking for confirmation.
Otherwise, a GUI dialog is shown, asking for an administrator's username and password (with the username field blank).
Security caveats:
Storing a password as plain text is a security risk in general.
Additionally, if you let non-administrative users execute the code below with stored admin credentials, you're effectively giving them administrative rights.
If you still want to implement your script as specified, a workaround requires nesting 2 Start-Process calls:
The 1st one runs an (invariably) non-elevated command invisibly in the context of the specified user - assumed to be an administrative user - using -Credential.
Since the specified user is an administrator, it's not a concern here, but if -Credential targets a non-admin user, it is advisable to also specify a -WorkingDir argument that the specified user is known to have permissions to access - otherwise, the call may fail (the current location is retained, and the target user may not be allowed to access it).
The 2nd one, embedded in the 1st, then uses -Verb RunAs to run the target command elevated, which then happens in the context of the specified user.
Note: Even with a credentials object that includes the password, you will still get the yes/no UAC prompt to confirm the intent to elevate - unless UAC has been turned off (which is not advisable).
The working directory will invariably be $env:SYSTEMROOT\System32; -Verb RunAs ignores even a -WorkingDirectory value; if you want to change to a specific directory, embed a cd command in the command passed to cmd.exe; the bottom section of this related answer shows this technique with a powershell.exe / Set-Location call.
This command does exactly what you asked for - please note the security caveat:
# Construct the credentials object
$username = "jdoe"
# CAVEAT: Storing a password as plain text is a security risk in general.
# Additionally, if you let non-administrative users execute this
# code with a stored password, you're effectively giving them
# administrative rights.
$password = ConvertTo-SecureString "test" -AsPlainText -Force
$cred = New-Object PSCredential -Args $username, $password
# Start an elevated Command Prompt (cmd) as user $username.
Start-Process powershell.exe -Credential $cred -WindowStyle Hidden `
'-noprofile -command "Start-Process cmd.exe -Verb RunAs"'
Note that the embedded, 2nd command is passed as a single string to the (implied) -ArgumentList (a.k.a. -Args) parameter.
In this simple case, with only 1 level of embedded quoting - the " instances inside the '...' string - and no need for expansions (string interpolation), passing a single string is a viable option, but with more sophisticated commands quoting gets tricky.
-ArgumentList is defined as type [string[]], i.e., an array of string arguments. If you pass multiple, ,-separated arguments, it is PowerShell that synthesizes the command line for you:
Caveat: A long-standing bug unfortunately requires that argument with embedded spaces be enclosed in embedded double-quoting - see this answer for details.
The following command demonstrates this technique: It is a variant that passes a command for cmd.exe to execute through, and uses a variable reference in that command:
$msg = 'This is an elevated Command Prompt.'
Start-Process powershell.exe -Credential $cred -WindowStyle Hidden -Args `
'-noprofile', '-command', "Start-Process cmd.exe -Verb RunAs -Args /k, echo, '$msg'"
The cmd.exe command that is ultimately executed (with elevation) is:
cmd /k echo This is an elevated Command Prompt.
Optional Reading: Why using script blocks in lieu of strings is ill-advised
tl;dr
Do not get into the habit of using script blocks where strings are expected. While convenient, it is not robust, and remembering when and why it will fail is nontrivial.
At first glance, script blocks ({ ... }) seem like a convenient option:
Start-Process cmd -ArgumentList { /k echo hi! }
The above executes cmd /k echo hi! in a new console window, as expected.
The syntax is convenient, because the { ... } seemingly provide a context in which quoting is easy: you're free to use embedded " and ' instances to construct your command line.
However, what happens behind the scenes is that a script block is converted to a string, because that's the type of argument(s) -ArgumentList expects, and when a script block is converted to a string, its literal contents - everything between { and } - is used.
This means that no string interpolation takes place, so you cannot use variables or subexpressions.
Take this attempt to pass a command based on a variable:
Start-Process cmd -ArgumentList { /k echo Honey, I`'m $HOME! }
What this will execute is: cmd /k echo Honey, I'm $HOME! - $HOME was not expanded.
By contrast, passing either an interpolated string or the arguments individually works as intended:
# As a single string (argument list):
Start-Process cmd -ArgumentList "/k echo Honey, I'm $HOME!"
# As an array of arguments:
Start-Process cmd -ArgumentList /k, echo, "Honey, I'm $HOME!"
$HOME is expanded (interpolated) in both cases, and something like
cmd /k echo Honey, I'm C:\Users\jdoe is executed.
The best way is to double up your Start-Process first with a -Credential parameter with your admin credentials and then with a -Verb runas on your second Start-Process. After that it gets a little complicated with quoting for CMD.exe.
Overall, it should look something like this.
Start-Process PowerShell -ArgumentList {-noexit -noprofile -Command "Start-Process powershell -argumentlist {-command cmd.exe -args \"/K #yourcommands# \"}" -verb runas} -Credential $Cred
So scratch this. Read mklement0's answer as to why
There's always opportunity to learn more and I didn't know wrapping an ArgumentList in a ScriptBlock prevented variable expansion. So...don't do that.
The method stays the same, though. You'll still need two Start-Process calls, just now you have to get the quoting right.
#Both of these work
Start-Process powershell -Credential $cred -ArgumentList "-noprofile", "-command", "Start-Process cmd.exe -Verb RunAs -ArgumentList /k, echo, 'something'"
# $Something will expand into it's value rather than literally
Start-Process powershell -Credential $cred -ArgumentList "-noprofile", "-command", "Start-Process cmd.exe -Verb RunAs -ArgumentList /k, echo, '$something'"
I have this code:
powershell -command "& { (New-Object Net.WebClient).DownloadFile('linkToMyFile.file', 'C:\my.file') }"
it's for download file.
When I execute it in cmd on remote server - everything is ok.
But when I want to execute this code from my computer on remote server using paexec, I have some troubles with escape characters.
Command in my CMD:
psexec.exe \\remoteServer.0.1 -u username -p password -dbg -lo D:\PsExec.log cmd /c "powershell -command "& { (New-Object Net.WebClient).DownloadFile('linkToMyFile.file', 'C:\my.file') }""
I try to use ^ symbol, but the same error;
Code using ^ symbol for double-quotes:
psexec.exe \\remoteServer.0.1 -u username -p password -dbg -lo D:\PsExec.log cmd /c "powershell -command ^"& { (New-Object Net.WebClient).DownloadFile('linkToMyFile.file', 'C:\my.file') }^""
Also, I tried to use \ (like in PHP) for escape, but have the same result.
Help with this or give advice how I can remotely download a file using the command line.
Unfortunately CMD uses different escape characters depending on what is escaped and where. There is no single one escape character that would be used everywhere.
In most cases the next character is escaped by prepending it with a caret (^), e.g. in for /f loops:
for /f "tokens=1" %%a in ('type file.txt ^| find "something"') do ...
But sometimes characters are escaped by doubling them, e.g. percent characters (%) in batch scripts:
#echo off
echo %%DATE%%=%DATE%
Sometimes you may even need need to put in other escape characters (like backslashes) because you need to escape something not for CMD, but for the command the string is being passed to:
mountvol | findstr /r \\\\
Your particular scenario shouldn't require additional quotes or escaping, though. Just run the PowerShell commandline directly, without cmd /c:
paexec.exe \\remoteServer.0.1 -u username -p password powershell -command "&{...}"
Is it possible to use powershell the complete way? If so, you could try the following:
New-PsDrive -Name X -Root \\127.0.0.1\c$ -PsProvider FileSystem -Credential (Get-Credential)
Copy-Item -Path X:\RequestedFile.txt -Destination C:\Temp
Remove-PsDrive -Name X
If your destination is a http address you could perform following actions:
Invoke-WebRequest -Uri http://some.uri -Method Get -OutFile C:\temp\myfile -Credential (Get-Credential)
Hope that helps
Instead Of this:
psexec.exe \\remoteServer.0.1 -u username -p password -dbg -lo D:\PsExec.log cmd /c "powershell -command "& { (New-Object Net.WebClient).DownloadFile('linkToMyFile.file', 'C:\my.file') }""
DO this:
psexec.exe \\remoteServer.0.1 -u 'username' -p 'password' powershell.exe -Command "& {(New-Object System.Net.WebClient).DownloadFile('linkToMyFile.file', 'C:\my.file')}"
This should do your work.
Hope it helps.