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
Related
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 try do download a file via a powershell command. The command I use is simple:
Start-BitsTransfer -Source 'https://download.com/file.zip' -Destination 'E:\test\file.zip'
I can run the command in PS succesfully. But now I want to run it with elevated rights. So I gooogled and found this solution:
There it says the command should be:
Start-Process powershell.exe -Verb Runas -ArgumentList "-Command & {get-process}"
So I tried adjusting it for my use case:
Start-Process powershell.exe -Verb Runas -ArgumentList "-Command & {Start-BitsTransfer -Source 'https://download.com/file.zip' -Destination 'E:\test\file.zip'}"
But all is does is open a new PS-Window and closing it right after. Where is my mistake?
You can change to this
Start-Process powershell.exe -Verb Runas -ArgumentList "& {Start-BitsTransfer -Source 'https://download.com/file.zip' -Destination 'E:\test\file.zip'}"
Note the window will close after the execution completes. If you would like to see the output/errors (such as what would be shown in your non working example) just add another command to pause.
Start-Process powershell.exe -Verb Runas -ArgumentList "& {Start-BitsTransfer -Source 'https://download.com/file.zip' -Destination 'E:\test\file.zip';pause}"
& is used to invoke a command. It's useful for executing strings or scriptblocks. It runs in a child runspace.
& 'Get-Host'
& 'Write-Host' Hello -Fore Green
& {Write-Host Goodbye -Fore Cyan}
; is used to separate different commands on the same line.
& {$name = 'Doug';Write-Host Hello $name}
You can also use a period to invoke a scriptblock in the current runspace. In the previous command the $name variable would be empty in the callers scope where the following command would leave the variable defined.
& {$name = 'Doug';Write-Host Hello $name}
$name # empty as it all happens in the child scope
vs
. {$name = 'Doug';Write-Host Hello $name}
$name # populated because it's brought into the caller's scope
I execute the following lines in powershell :
$argList = "-NoExit -NoProfile -Command {Write-Host 'hello world'}";
Start-Process PowerShell -ArgumentList $argList;
My desired output is to create a new powershell windows and output the hello world.
But what I get is opening a new powershell windows and writing the command itself like Write-Host 'hello world'. So the Write-Host actually is not executed in the new window. how to solve this?
The problem is in the quoting of the $argList string in which you insert the scriptblock as text.
Try any of these:
$argList = "-NoExit -NoProfile `"Write-Host 'hello world'`"" # works
$argList = "-NoExit -NoProfile -Command `"Write-Host 'hello world'`"" # works
$arglist = '-NoExit', '-NoProfile', '-Command', 'Write-Host "hello world"' # works
$arglist = '-NoExit', '-NoProfile', '-Command', {Write-Host "hello world"} # works
$argList = '-NoExit -NoProfile -Command', {Write-Host "hello world"} # works
$argList = '-NoExit -NoProfile -Command', 'Write-Host "hello world"' # works
Start-Process PowerShell -ArgumentList $argList
Theo's helpful answer provides effective solutions; to add some background information:
It is only from inside a PowerShell session, in direct invocation of powershell.exe, the Windows PowerShell CLI[1], that you can use a script block ({ ... }) by itself ; e.g.:
# Works, but only from inside a PowerShell session.
PS> powershell -NoProfile -Command { Write-Host 'hello world' }
hello world
By contrast, calling the CLI via Start-Process invariably behaves like an invocation from the outside, where everything following -Command is parsed as strings, and, after removing outer " quoting, the resulting tokens are joined with spaces and then interpreted as PowerShell source code, as if you had submitted that code from inside PowerShell.
Submitting a script block by itself from inside PowerShell simply outputs its verbatim content (except { and }), and that is what you saw:
# Submitting a script block *definition* without *calling it* prints
# its verbatim content as a string, it is the equivalent of calling .ToString() on it.
PS> {Write-Host 'hello world'}
Write-Host 'hello world'
Therefore, you could have made your solution work simply by prepending &, the call operator to the embedded script block in order to ensure its execution:
# Note the `&` before the script block to ensure that it is *called*.
$argList = "-NoExit -NoProfile -Command & {Write-Host 'hello world'}"
Start-Process pwsh -ArgumentList $argList
[1] This applies analogously to pwsh, the PowerShell (Core) 7+ CLI.
I am having trouble getting the --% parameter to work as expected. My $TaskParams variable has the character '<' which is interpreted as a redirection by powershell, therefore needs to be escaped.
However the following does not work:
$CreateTask = Start-Process PowerShell.exe "$ScriptLocation --% $TaskParams" -Wait -PassThru
Without the --%, and when I manually remove any '<' characters, it works:
$CreateTask = Start-Process PowerShell.exe "$ScriptLocation $TaskParams" -Wait -PassThru
error received:
Start-Process : A positional parameter cannot be found that accepts argument
'--%'.
note: I am using PS 5.0
Am I using the --% parameter wrong? Any help or pointers is appreciated. Thanks
The stop-parsing symbol --% only works when calling executables directly or with the call operator &; it's not for use when calling PowerShell scripts / functions / cmdlets.
You do not need to spin up a new copy of powershell.exe or use Start-Process to run a script from within another script. Just put the script command and its parameters as a line from within the other script. For example, suppose you have script2.ps1:
param(
[String] $Name
)
Write-Host "Hello, $Name"
Now suppose you also have script1.ps1:
Write-Host "This is script1.ps1"
.\Script2.ps1 -Name "Bill Stewart"
Write-Host "script1.ps1 is finished"
If you now run script1.ps1:
PS C:\> .\Script1.ps1
This is script1.ps1
Hello, Bill Stewart
script1.ps1 is finished
If you really want to use Start-Process you could encode the argument, and run it as such. I use something similar to this when elevating past UAC:
$Code = ". '$ScriptLocation' $TaskParams"
$Encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($code))
Start-Process PowerShell.exe -ArgumentList "-EncodedCommand",$Encoded -Wait -PassThru
I'm fairly certain that would accomplish what you're looking for.
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"