How to suppress quotes in PowerShell commands to executables - powershell

Is there a way to suppress the enclosing quotation marks around each command-line argument that PowerShell likes to generate and then pass to external executables for command-line arguments that have spaces in them?
Here's the situation:
One way to unpack many installers is a command of the form:
msiexec /a <packagename> /qn TARGETDIR="<path to folder with spaces>"
Trying to execute this from PowerShell has proven quite difficult. PowerShell likes to enclose parameters with spaces in double-quotes. The following lines:
msiexec /a somepackage.msi /qn 'TARGETDIR="c:\some path"'
msiexec /a somepackage.msi /qn $('TARGETDIR="c:\some path"')
$td = '"c:\some path"'
msiexec /a somepackage.msi /qn TARGETDIR=$td
All result in the following command line (as reported by the Win32 GetCommandLine() API):
"msiexec" /a somepackage.msi /qn "TARGETDIR="c:\some path""
This command line:
msiexec /a somepackage.msi TARGETDIR="c:\some path" /qn
results in
"msiexec" /a fooinstaller.msi "TARGETDIR=c:\some path" /qn
It seems that PowerShell likes to enclose the results of expressions meant to represent one argument in quotation marks when passing them to external executables. This works fine for most executables. However, MsiExec is very specific about the quoting rules it wants and won't accept any of the command lines PowerShell generates for paths have have spaces in them.
Is there a way to suppress this behavior?

Escape the inner quotes like this:
msiexec /a somepackage.msi TARGETDIR=`"c:\some path`" /qn

Here is a function I use to better handle multiple arguments and those with spaces and quotes. Note that to the code blocks below don't color where strings start and end correctly and you have to use ` to escape quotes you want in the parameter.
function InstallMSIClient{
$Arguments = #()
$Arguments += "/i"
$Arguments += "`"$InstallerFolder\$InstallerVerFolder\Install.msi`""
$Arguments += "RebootYesNo=`"No`""
$Arguments += "REBOOT=`"Suppress`""
$Arguments += "ALLUSERS=`"1`""
$Arguments += "/passive"
Write-Host "Installing $InstallerVerFolder."
Start-Process "msiexec.exe" -ArgumentList $Arguments -Wait }
There's a more complete example on my blog. [http://www.christowles.com]

I don't have the answer, but this guy seems to be onto something.
http://www.eggheadcafe.com/software/aspnet/33777311/problem-escaping-command.aspx
I couldn't make it work for me.
Here's someone else reporting the issue too:
_http://powershell.com/cs/forums/p/2809/3751.aspx
Here's another idea by someone:
_http://www.roelvanlisdonk.nl/?p=1135
That didn't work for me either...

I don't have the answer, but this guy seems to be onto something.
http://www.eggheadcafe.com/software/aspnet/33777311/problem-escaping-command.aspx
Yeah, it looks like they found a solution at the end:
Finally worked out how to do this using invoke-expression:
$installprop = "TARGETDIR=" + "```"" + $installpath + "```""
invoke-expression "msiexec /i $packagepath $installprop"
I would recommend using a here-string though to avoid having to do all the escaping.
$command = #'
msiexec /a <packagename> /qn TARGETDIR="<path to folder with spaces>"
'#
invoke-expression $command

Put the entire argument in quotes and escape the inner quotes. Otherwise PowerShell will try to parse it:
msiexec /a <packagename> /qn 'TARGETDIR=\"<path to folder with spaces>\"'

I've just hit the problem. The following worked for me:
&cmd /c "msiexec /i `"$appName.msi`" /l* `"$appName.msi.log`" /quiet TARGETDIR=`"D:\Program Files (x86)\$appName\`""
The key was executing cmd rather than msiexec directly. This had two benefits:
I could wrap the entire msiexec command in a string, so it would resolve the the $appName variable while still using the backtick to escape the quotes around TARGETDIR.
I was able to make use of $LastExitCode to check success/failure of the msiexec operation. For some reason $LastExitCode isn't set when calling msiexec directly (hat-tip: http://www.powergui.org/thread.jspa?threadID=13022)

Related

Use PowerShell variables in cmd command not working

I am having difficulty with executing a command line in cmd from Powershell. The problem is I can't seem to use my global variables in the command line when cmd is opened from Poweshell. Below is the code I am working with but to no avail. Can someone please provide guidance on this issue?
Start-Process -FilePath "C:\Windows\System32\cmd.exe" -verb RunAs -ArgumentList {/k set Name="$cmdname" set Item="$cmditem" setlocal EnableDelayedExpansion echo "%Name%" }
Thanks,
Roger
The syntax is awkward for sure:
Start-Process -FilePath "C:\Windows\System32\cmd.exe" -ArgumentList "/k set Name=$cmdname & set Item=$cmditem & call echo %name%"
Some of the reasoning here is:
cmd: additional commands need to be separated by &
cmd: the set command takes everything after = until special characters like &
cmd: setlocal EnableDelayedExpansion doesn't really apply here. Use call to delay instead.
delayed variable syntax is also !var! rather than %var%
Powershell: using brackets in -ArgumentList {stuff} sends as a literal string, and variables aren't expanded

Call batch file with elevated privileges via PowerShell and retrieve exit code

My Windows Batch shall be started by the user without administrator privileges. At some step, it shall call itself with elevated privileges. I have learned that this is possible using the PowerShell's runas feature (batch.bat ⭢ PowerShell ⭢ batch.bat). This works like a charm.
Unfortunately, I am not able to receive the exit code from the elevated batch execution. I always get 1, although there is not any error message. I have no idea at which return the exit code gets lost, 1st (batch back to PowerShell) or 2nd (PowerShell back to batch).
I believe, I have tried all of the plenty suggested answers from similar questions, but apparently I am unable to get it going. I need advice.
MVE which should indicate that the elevated batch returns 0:
#echo off
echo param=%~1
openfiles /local >nul 2>&1
IF %ERRORLEVEL% EQU 0 (
echo elevated, exit 0
pause
exit 0
) ELSE (
echo not elevated. trying to elevate.
powershell start-process -wait -verb runas '%0' -argumentlist /foo
echo powershell returned %errorlevel%.
)
Nota bene (edited to eliminate misunderstanding): while the non-elevated call (by the user) does not require any parameter, the elevated call introduces an additional parameter '/foo'. This makes things worse for me because I did not find a solution to not lose this parameter. However, this appears to be a rather unusual use case.
To solve the argument problem, you could use
powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'
The exit code problem:
The first problem is the line
echo powershell returned %errorlevel%.
This can't work, because it's inside a code block and %errorlevel% will be expanded even before powershell will be called and therefore it is always 1 - the result of openfiles /local ...
But even with delayed expansion, I got always 0, probably because it's the exitcode of the successful runas, that it was able to start your batch.
You could use a work around and store the exitcode in a temporary file
#echo off
setlocal EnableDelayedExpansion
echo param=%*
openfiles /local >nul 2>&1
IF %ERRORLEVEL% EQU 0 (
echo elevated, exit 13
pause
echo 13 > "%temp%\_exitcode.tmp"
rem *** using 14 here to show that it doesn't be stored in errorlevel
exit 14
) ELSE (
echo not elevated. trying to elevate.
powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'
set /p _exitcode= < "%temp%\_exitcode.tmp"
del "%temp%\_exitcode.tmp"
echo powershell returned !_exitcode!, lvl !errorlevel!.
)
You aren't putting the PowerShell commands to execute in quotes, and you would do well to use the full path as well as include any arguments to the script. A generic way to invoke this, so it could be copied across scripts, your PowerShell invocation should look like so:
powershell -c "if([bool]'%*'){ Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList ('%*' -split '\s+') } else { Start-Process -Wait -Verb runas '%~dpnx0' }"
For your needs above, this could be simplified since you know you have arguments passed into the batch file to process:
powershell -c "Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList '/foo'
%~dpnx0 - Automatic batch variable, this is the full path to the current script, including the script name
%* - Automatic batch variable, this is all arguments passed into the script.
('%*' -split '\s'): This is a PowerShell expression takes the space-delimited %* variable and splits it on continuous whitespace, returning an array. For simplicity this does have a shortcoming in that it will split on spaces in between double quotes, but the regex can be tuned to account for that if needed.
This answer is worth a read for other automatic batch variables you may find use for in the future.

Powershell Inkove-expression

Yesterday I read about the invoke-expression
from
http://technet.microsoft.com/en-us/library/dd347550.aspx
invoke-expression -command "c:\batfile.bat"
It works very well, but how can I get the results and also , how to add args for .bat file?
Invoke-Expression is to Call up the EXpression,And '.Bat' is not an expression.
So if you want to call .Bat File Justc:\batfile.bat Could Do the trick.
Or if you want to explore on the invoke-expression Copy the commands in Batch file into a variable to see the code being executed.
There was similar instance where I had to do something similar,And I had used it as
$prog="cmd.exe"
$TARGETDIR = 'd:\temp'
$x='C:\Users\v-chetak\Desktop\1.bat'
$params=#("/C";"$x";" >d:\temp\result17.txt")
Start-Process -Verb runas $prog +$params
The $params Will Hold all the arguments you want to pass.

Invoke-Expression -Command Not Finding File Even Though It Exists

all,
I am trying to run a command in Power Shell and I am told the file does not exist as it stands right now for the Power Shell code. If I do the same in DOS, it finds the file and executes correctly. The only difference is that there is an additional open and closed double quote around the file name. Can this be done in Power Shell the same way? If so, how can this be done as I am not familiar with Power Shell.
DOS:
IF EXISTS F:\lvcecstos.CSV F:\LaserVault\ContentExpress\ContentExpress.exe /CID:1 /CSV:<b>"F:\lvcecstos.CSV"</b> /ClearSubscription
Power Shell:
Invoke-Expression -Command "F:\LaserVault\ContentExpress\ContentExpress.exe /CID:1 /CSV:<b>F:\lvcecstos.csv</b> /ClearSubscription"
Thanks in advance for any suggestions. I am told by the Laservault engineers that created this software package that we use that the double quotes around the file name is required. Doesn't make sense to me as to why though, but this is something I am unable to get around.
You can add double quotes around the file path as follows:
Invoke-Expression -Command 'F:\LaserVault\ContentExpress\ContentExpress.exe /CID:1 /CSV:"F:\lvcecstos.csv" /ClearSubscription';
Notice how the command is wrapped in single quotes instead of double quotes.
Ultimately, it depends on how the ContentExpress.exe program is interpreting the file path, though. For all you know, it could be appending the valued passed to "CSV" to the "current working directory."
You can also use the Start-Process cmdlet, instead of Invoke-Expression or Invoke-Command.
$Program = 'F:\LaserVault\ContentExpress\ContentExpress.exe';
$ArgumentList = '/CID:1 /CSV:"F:\lvcecstos.csv" /ClearSubscription';
Start-Process -Wait -NoNewWindow -FilePath $Program -ArgumentList $ArgumentList;
If you have PowerShell v3 (which you should) you could use the "magic parameter" --% (see here).
& F:\LaserVault\ContentExpress\ContentExpress.exe --% /CID:1 /CSV:"F:\lvcecstos.csv" /ClearSubscription
Otherwise trying to preserve double quotes around arguments could become very, very painful. See for instance this answer to a similar question.
$lvcecstos = "F:\lvcecstos.CSV"
If(Test-Path $lvcecstos)
{
Invoke-Expression -Command "& F:\LaserVault\ContentExpress\ContentExpress.exe /CID:1 /CSV:$lvcecstos /ClearSubscription"
}

PowerShell passing parameter to executable having switches

i am tring to convert
abc.exe /u "c:/programs/abc.dll" to powershell script can anybody explain how to do it.
how can i execute the *.exe having switches with parameters??
thanks..
Sunny
It should be as straight forward as:
C:\PS> abc.exe /u c:/programs/abc.dll
However you can run into issues with quoting and other characters that get interpreted by PowerShell. Usually quouting an argument will suffice but if that still doesn't work you can use Start-Process in PowerShell 2.0 e.g.:
C:\PS> start-process abc.exe -arg #'
...
'#
If you have PowerShell Community Extensions installed you can use a utility called echoargs.exe to troubleshoot passing args to exe's. e.g.:
C:\PS> echoargs /u c:/programs/abc.dll
Arg 0 is </u>
Arg 1 is <c:/programs/abc.dll>
Echoargs display the arguments exactly as the EXE sees them.
If the normal syntax doesn't help out you could try:
$psi = New-Object System.Diagnostics.ProcessStartInfo "abc.exe"
$psi.Arguments = "/u c:/programs/abc.dll"
[System.Diagnostics.Process]::Start($psi)
See also: Documentation for System.Diagnostics.ProcessStartInfo.