How to run a CMD task inside of PowerShell? - windows

I am trying to run a CMD task inside of a PowerShell script that will open up a new tab in Google Chrome. I am running this command inside an Azure DevOps pipeline.
The task I am attempting to run is:
start chrome --user-data-dir="ChromeProfiles\Profile$profile" --disable-default-apps --new-window "$($reportHtmlFile)"
When I run this command from my local command prompt, a new tab opens and works as expected. To run it from my PowerShell window I run:
cmd /c echo start chrome --user-data-dir="ChromeProfiles\Profile$profile" --disable-default-apps --new-window "$($reportHtmlFile)" | cmd.exe
Both the above commands work as expected however, trying to run them from Azure DevOps I am getting an error saying:
+ ... " --disable-default-apps --new-window "$($reportHtmlFile)"" | cmd.exe
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The string is missing the terminator: ".
At D:\Agent\instance01\Workspace\20\s\pbi-load-test-tool\Run_Load_Test_Only.ps1:59 char:1
+
Missing closing ')' in expression.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
I have tried the following:
"start chrome --user-data-dir="ChromeProfiles\Profile$profile" --disable-default-apps --new-window "$($reportHtmlFile)"" | cmd.exe
& "start chrome --user-data-dir="ChromeProfiles\Profile$profile" --disable-default-apps --new-window "$($reportHtmlFile)" | cmd.exe"
Is there a syntactical error or is this a shortcoming with Azure DevOps?

What's Going on:
Your arguments are getting mangled because they contain quotes.
When you run this the way you're running it now, PowerShell will try to handle the quotes in the arguments before passing it to the exe.
The solution: Use Splatting
Splatting is a part of the PowerShell language that lets you pass structured arguments to a command. You can Splat a [Hashtable] to provide named arguments (if you're calling a function or cmdlet). You can also Splat an [Object[]] to provide arguments positionally.
Create an array out of all of your arguments:
In your case, that would be:
$startArgs = #(
# The user-data-dir probably wants double quotes
# so we use backticks to embed them.
"--user-data-dir=`"ChromeProfiles\Profile$profile`""
'--disable-default-apps'
'--new-window'
"$($reportHtmlFile)"
)
Call with splatting:
start #startArgs
Doing things this way will ensure that each argument is sent exactly the way you want it, with complete control over how arguments will be quoted.
Also: avoid $profile
$profile is the name of an automatic variable in PowerShell pointing to the PowerShell profile. I suspect you're interested in a chrome profile, not a PowerShell profile, so I would pick a different variable name that would better describe your purpose, rather than risk having an automatic variable provide a bad result.

Related

How can use a PowerShell script to run the Terraform CLI and pass a variable of type map?

The variable in the Terraform file (infrastructure.tf) is declared like this:
variable "tags" {
type = map(string)
}
This is the PowerShell code that executes the terraform command-line program with the plan command:
$command = "plan"
$options = #(
"'--var=tags={a:\`"b\`"}'"
"--out=path/to/out.tfplan"
)
Write-Host terraform,$command,$options
& terraform $command $options
The Write-Host command's output is:
terraform plan '--var=tags={a:\"b\"}' --out=path/to/out.tfplan
If I copy paste that into an interactive PowerShell 7.2 (pwsh) session then it works. But the & terraform command fails with this error:
Error: Too many command line arguments
To specify a working directory for the plan, use the global -chdir flag.
I know the Terraform documentation for the plan command warns against using PowerShell to run the command, due to quoting issues (emphasis mine):
PowerShell on Windows cannot correctly pass literal quotes to external programs, so we do not recommend using Terraform with PowerShell when you are on Windows. Use Windows Command Prompt instead.
Unfortunately they don't specify if they mean Windows PowerShell 5.1 included in Windows (powershell.exe), or also PowerShell 7.2 running on Windows (pwsh). But with pwsh it is clearly possible to run the plan command in an interactive PowerShell session (I am using macOS, but some others use Windows) and pass literal quotes to an external program. It just seems that running the same command from a .ps1 file does not work.
Since our whole dev/ops tooling is PowerShell-based, I'd like to know: is this possible?
Because if it is not, then we will have to work around this limitation.
Edit:
Some things I've tried:
Use splatting for $options (e.g. #options)
Add the stop-parsing token (e.g. --%) as the first item in $options (that token is actually "only intended for use on Windows platforms")
Many variations of single/double quotes
Remove the \ (I am not actually sure why this seems required, the map literal syntax is either {"x"="y"} or {x:"y"}), but without the \ copy-pasting the printed command line in an interactive PowerShell also does not work.
tl;dr
Omit the embedded enclosing '...' around --var=..., because they will become a literal part of your argument.
The - unfortunate - need to manually \-escape the embedded " instances, even though PowerShell itself does not need it, is the result of a long-standing bug that was finally fixed in PowerShell (Core) 7.3.0; in 7.3.0 and up to at least 7.3.1, the fix is in effect by default, which breaks the solution below, and therefore requires $PSNativeCommandArgumentPassing = 'Legacy'; however, it looks like the fix will become opt-in in the future, i.e. the old, broken behavior (Legacy) will become the default again - see this answer.
Using Write-Host to inspect the arguments isn't a valid test, because, as a PowerShell command, it isn't subject to the same rules as an external program.
For ways to troubleshoot argument-passing to external programs, see the bottom section of this answer.
$command = "plan"
$options = #(
"--var=tags={a:\`"b\`"}" # NO embedded '...' quoting
"--out=path/to/out.tfplan"
)
# No point in using Write-Host
& { # Run in a child scope to localize the change to $PSNativeCommandArgumentPassing
# Note: Only needed if you're (also) running on PowerShell 7.3+
$PSNativeCommandArgumentPassing = 'Legacy'
& terraform $command $options
}
How to control the exact process command line on Windows / pass arguments with embedded double quotes properly on Unix:
Note: The solution above relies on PowerShell's old, broken behavior, and while it works in the case at hand, a fully robust and less conceptually confusing solution requires more explicit control over how the arguments are passed, as shown below.
A cross-edition, cross-version, cross-platform solution:
Assuming that terraform must see --var=tags={a:\"b\"} on its process command line on Windows, i.e. needs to see the argument as verbatim --var=tags={a:"b"} after parsing its command line, combine --%, the stop-parsing token, with splatting, which gives you full control over how the Windows process command line is built behind the scenes:
$command = "plan"
$options = #(
'--%'
'--var=tags={a:\"b\"}'
'--out=path/to/out.tfplan'
)
& { # Run in a child scope to localize the change to $PSNativeCommandArgumentPassing
# !! Required in v7.3.0 and up to at least v7.3.1, due to a BUG.
$PSNativeCommandArgumentPassing = 'Legacy'
& terraform $command #options
}
This creates the following process command line behind the scenes on Windows (using an example terraform path):
C:\path\to\terraform.exe plan --var=tags={a:\"b\"} --out=path/to/out.tfplan
Note:
In PowerShell (Core) 7.3.0 and at least up to 7.3.1, --% is broken by default, in that its proper functioning is mistakenly tied to value of the v7.3+ $PSNativeCommandArgumentPassing preference variable; thus, (temporarily) setting $PSNativeCommandArgumentPassing = 'Legacy' is required, as shown above - see GitHub issue #18664 for the bug report.
Even though --% is primarily intended for Windows, it works on Unix-like platforms too, as long as you use the Microsoft C/C++ command-line syntax rules to formulate the arguments; specifically, this means:
only use " characters for quoting (with syntactic function)
use \ only to escape " chars.
While you can use --% without splatting, doing so comes with severe limitations - see this answer.
A simpler, but Windows-only cross-edition, cross-version solution:
Calling via cmd /c also gives you control over how the command line is constructed:
$command = "plan"
$options = #(
'--var=tags={a:\"b\"}'
'--out=path/to/out.tfplan'
)
cmd /c "terraform $command $options"
Note: This is often more convenient than --%, but suboptimal, because:
The intermediary cmd.exe call creates extra overhead.
% characters may be interpreted by cmd.exe, and, in unquoted arguments, additional metacharacters such as & and ^ - preventing that requires extra effort.
A v7.3+ cross-platform solution:
Relying on PowerShell's corrected behavior in v7.3+ (no need for manual \-escaping anymore) requires setting $PSNativeCommandArgumentPassing to 'Standard'.
Note: If you target only Unix-like platforms, that isn't necessary.
$command = "plan"
$options = #(
'--var=tags={a:"b"}' # Note: NO \-escaping of " required anymore.
'--out=path/to/out.tfplan'
)
& { # Run in a child scope to localize the change to $PSNativeCommandArgumentPassing
# Necessary on Windows only.
$PSNativeCommandArgumentPassing = 'Standard'
& terraform $command $options
}
Note: On Windows, this creates a slightly different process command line than the solutions above; notably, --var=tags={a:\"b\"} is enclosed in "..." as a whole; however, well-behaved CLIs should parse this as verbatim --var=tags={a:"b"} too, whether enclosed in "..." or not.
C:\path\to\terraform.exe plan "--var=tags={a:\"b\"}" --out=path/to/out.tfplan

Cannot execute this command “rasa run actions & rasa shell”

Using Rasa open source I tried to execute (Windows Powershell) this command rasa run action & rasa shell it generate some error like this:
At line:1 char:17
+ rasa run action & rasa shell
+ ~
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 : AmpersandNotAllowed
In PowerShell you can use a semicolon to run 2 commands (note it should be rasa run actions):
rasa shell; rasa run actions
In cmd you can still use ampersand.
However this won't work on Windows where commands are run sequentially. One solution is to have a terminal session for each command (you also don't mix up logs and can restart only one if you need to)
You are trying to run 2 different commands on the shell. shell does not know "&".
Rasa run command is used to run the rasa framework as a server on localhost:5005
whereas
rasa shell is used to run on the chatbot on the terminal, the server will be started automatically.
I would prefer to run the command in 2 different shell to reduce the debug confusion

Windows cmd START batch file in new console with quoted arguments

Note: A solution is posted below.
Problem:
When required to start a batch file (cmd script) (i.e. CALLEE.CMD) in a new console window, from within another batch file (i.e. CALLER.CMD) , the obvious choice would be to use the internal START command: START "" "%~DP0CALLEE.CMD".
However, if we need to pass quoted parameters to CALLEE.CMD, such as START "" "%~DP0CALLEE.CMD" "ARGUMENT_1", you get an error such as 'CALLEE.CMD" "ARGUMENT_1' is not recognized as an internal or external command,
operable program or batch file.
At first glance, it's not obvious why this command should fail, as the requirements for calling the START command seem to be met. This question is posted because the reason for failure is not immediately apparent, and others may find the reference useful.
So, why is this command failing, and how to resolve the issue?
What is happening here, is that when the START command detects the command to be invoked is either an internal command (internal to cmd.exe) or a batch file, it will invoke cmd.exe to handle the command (cmd.exe /K ... will be used). The additional arguments passed to START will be translated and passed to cmd.exe. When this happens, the rules change: cmd.exe expects the command and arguments to be quoted differently from START, so this has to be taken into account when writing the original command.
The quoting for cmd.exe is documented elsewhere, such as:
correct quoting for cmd.exe for multiple arguments
http://ss64.com/nt/cmd.html.
So, to start a batch file, with quoted arguments in a new console window, we could do as follows:
START cmd.exe /K ""%~DP0CALLEE.CMD" "ARGUMENT_1" "ARGUMENT_2""
Of course, this problem only occurs if you need to quote the arguments, either because a called program expects it or if there are spaces in the argument.
Also, as a plus, if you don't require the new console window to remain open after finishing the batch file, you can substitute the switch /K with /C.
The requirement for always needing to provide a window title when calling a quoted command is avoided here, but if you do require to prefix the title of the new console window, you can do as follows:
START "<Title>" cmd.exe /K ""%~DP0CALLEE.CMD" "ARGUMENT_1" "ARGUMENT_2""
Example code:
CALLEE.CMD
#ECHO %*
#PAUSE
CALLER.CMD
#START "<Title>" cmd.exe /C ""%~DP0CALLEE.CMD" "ARGUMENT_1" ARGUMENT_2 "ARGUMENT_3""

Does PSCP work with PowerShell?

I have a PowerShell script that produces a text file. At the end, I would like to copy this file to a Linux server.
From CMD.EXE, I can use PSCP (from Putty), it works and copies the file.
But from PowerShell, either interactively or from a PowerShell batch, PSCP has no visible effect: no error messages and the file is not copied.
Even if I run simply .\PSCP.EXE without arguments, on the CMD command line it displays the options, but from PowerShell it does nothing.
Can PSCP be used from inside PowerShell?
Executing a program from within PowerShell should work identically to CMD, but depending upon how that program produces its output (does it write to STDOUT, STDERR, other?) that may behave differently.
I've been using Rebex's components for FTPS & SFTP within .NET apps & PowerShell scripts; the SFTP package includes an SCP class. Yes, it costs money, but depending upon your usage it may be worthwhile.
Just attempted to automate PSCP from PowerShell. Remember to use pscp's -batch parameter so that, should you do something like enter the wrong password, you won't get asked for input.
$Cmd = "pscp -l username -pw password -batch c:\folder\file.txt server:/home/user1"
Invoke-Expression "& $( $Cmd )"
Otherwise your script will just grind to a halt.
Yes - most any executable can be called from PowerShell. There isn't anything peculiar about pscp.exe in this regard. You may need to preface it with the call operator - the ampersand - &:
PS C:\>& "C:\Program Files (x86)\Putty\pscp.exe" -V
pscp: Release 0.62
The above is direct output from my PowerShell prompt. The call operator is particularly helpful if the path to your executable contains spaces - the call operator is used to tell PowerShell to treat what would be considered a string as something it should try to execute instead.
Please include the full command your are trying to execute as it will help in providing a better answer. You may have a problem with your PATH variable or something else weird if you don't get any output.
If using pscsp from inside a script, e.g. perl
no ampersand
quote like this "my password"
e.g.
"C:\Program Files\Putty\pscp.exe" -C -p -pw "password" /local_dir/file_to_copy user#hostname:/remote_directory
in perl (beware that \ is an escape char in a "string" )
$cmd = q("C:\Program Files\Putty\pscp.exe" -C -p -pw "password" /local_dir/file_to_copy user#hostname:/remote_directory);
system($cmd);

Powershell script gets stuck, doesn't exit when called from batch file

I have a PowerShell script that connects to a web site, and parses its returned data (It's about importing a previously uploaded SQL file into the web site's data base). The PowerShell script uses wget, something I may replace by a native function later.
The import process is embedded in a script that is executed by a 3rd party program called scriptFTP.
The script runs fine when I call it from a single .bat file like so:
powershell "& "C:\data\etc\run_import_script.ps1"
exit %ERRORLEVEL%
However, when I call this .bat file from within the greater ScriptFTP context, the following happens:
The PowerShell script is executed. I confirmed this my sending myself an E-Mail each time the remote import script got called.
PowerShell doesn't seem to exit, and script execution gets stuck. I can still cancel the whole thing using Ctrl+C but the following commands never get executed.
When I change the batch file to the following:
start powershell "& "C:\data\etc\run_import_script.ps1"
exit %ERRORLEVEL%
it works, running the PowerShell script in a new console, but I can't grab the error level that PowerShell returns.
I have tried calling PowerShell from ScriptFTP directly, bypassing the batch file, but with the same result: It just gets stuck.
Any output I have the PowerShell script do using Write-Output or Write-Host is not displayed.
All programs run under the same user, me.
Does anybody have any ideas what to do?
This variant might work for you.
powershell -NoProfile -Command "C:\data\etc\run_import_script.ps1; exit $LASTEXITCODE" < NUL
Taken from http://thepowershellguy.com/blogs/posh/archive/2008/05/20/hey-powershell-guy-how-can-i-run-a-powershell-script-from-cmd-exe-and-return-an-errorlevel.aspx
From my experience, PowerShell.exe can easily run scripts from within a batch file or shell script in a predictable way using the -File switch. One does not need to use the Start command.
The important thing to do is to append
< nul
to the command line from within a batch file. My research has shown that PowerShell runs the commands in the script indicated through the -File switch and then waits for additional PowerShell commands from the standard input (my brief experimentation with the -Command switch demonstrated similar behavior). By redirecting the standard input to nul, once PowerShell finishes executing the script and "reads end-of-file" from the standard input, PowerShell exits.
When invoking PowerShell from Cygwin, use
< /dev/null
For example, I've run PowerShell scripts from Cygwin using shell variables, like this:
PowerShell.exe -ExecutionPolicy RemoteSigned -File $_powershellscriptpath $_firstscriptparameter < /dev/null
Please post a comment if your experience varied from mine.
- Gordon
Try adding the /WAIT parameter. It will keep the .bat waiting until the PowerShell script completes.
START /WAIT powershell "& "C:\data\etc\run_import_script.ps1"
We had a similar issue. We wanted to call a powershell app from a piece of software that just had a box to enter "Command" and "Parameters" but although the powershell ran successfully (I could see the affected file updated.)
Finally my coworker helped me figure it out
Command needs to be:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
And Parameters:
-ExecutionPolicy unrestricted -Command "& {C:\scripts\apps\EDI\Test.ps1; [Environment]::Exit(1)}"
In our case it was important to use [Environment]::Exit(1) rather than Exit 1. I believe Exit was simply terminating the script, not closing Powershell itself.
If you want to capture the output of the powershell.exe commands then you can also use the /B parameter to force the process to run in the same command shell.
We've just seen a very odd instance of this problem. A batch file containing the call powershell.exe -command ... ran fine locally but stalled as described above when the batch file was run in the context of an msdeploy -postSync command. After experimenting with process.Kill() to force PowerShell to quit, we lit on the use of START /WAIT /B PowerShell.exe ..
No idea why this should work, but it does ...
IF that is exactly what's in your file it's because you have mismatched quotes. Powershell is waiting for the last quote.
PowerShell has, at least what I consider, strange behavior when being invoked in this manner. In short, it doesn't treat the command line arguments being passed to powershell.exe as scripts to run. Instead, it treats them as command to run. This is because the default for powershell.exe is -command - see powershell.exe /? for additional info.
C:\>powershell "'Hello'"
Hello
What you will need to do, is construct a clever input string to "source" the script you wish to run:
C:\>powershell ". .\test.ps1"
Hello, World!
As for the output, once you get the script running correctly, it should just be a matter of capturing STDOUT or whatever ends up fitting with your script.
Full Example
test.bat
#echo off
powershell.exe ". .\test.ps1"
test.ps1
"Hello, World!"
Invoke the command:
test.bat > test.txt
And verify the output was captured:
C:\>type test.txt
Hello, World!
The problem I bet is that the Powershell process continutes to run after executing the script. This means it has not exited and therefore there is no exit code. I'm having a similar problem when trying to run a Powershell script from C# that does stuff when finished, except it never finishes...
Hacky, but the only solution I've found is to put Stop-Process -processname powershell at the end of the .ps1 script. This kills off the PowerShell process (and all PowerShell windows you have open unfortunately) but seems to work. Might work for your script as well.
I had the exact issue, we were trying to integrate power shell scripts into another system and it kept giving a timeout error. Probably because of the issue mentioned by Gordon Smith, his solution might work. But to me I prefer to have complete control of my script from within the script itself and not depend on the way in which it is called.
$pid is built in variable with the PID. The below will end the current powershell process and leave the others intact. This way anyone can call your script as normal and it will work as expected.
Stop-Process $pid
you can simply add an 'exit' command to the end of the .ps1 script (or any variant of process termination that you wish). Powershell will continue to run at the end of the script as it has not been told to terminate (when run in PS or ISE it will auto-terminate at the end of the script but not when run through the DOS shell).

Resources