Powershell One Liner with spaces from CMD - powershell

I have to create a Powershell Oneliner which will be called from CMD.
The Powershell should uninstall an .MSI first and then install another one.
The Problem is the .MSI I have to install has spaces in its name.
The first part of the command works, but obviously the second part doesn't work.
How could I solve it?
powershell.exe -noprofile -command "Start-process msiexec.exe -wait -Argumentlist '/x {aaaaaaaaaaaa} /qn';Start-Process msiexec.exe -wait -ArgumentList '/i `"the msi with spaces in it.msi`" ADV_SUPRESSDATABASETEST=1 SERVER_PROP='someserver\AB' DATABASE_PROP='DATABASE' SQL_TRUSTED_CONNECTION=1'"
I have also tried with $variables, but with single quote they aren't expanded. (it's possible to use variables in a one-liner?
How could I possibly solve this?

Leaving aside that calling msiexec.exe with start /wait directly from cmd.exe (a batch file) may be the better solution, as discussed in the comments on the question, you can make this work via the Windows PowerShell CLI (powershell.exe) as follows:
powershell.exe -noprofile -command Start-process msiexec.exe -wait -Argumentlist '/x {aaaaaaaaaaaa} /qn'; Start-Process msiexec.exe -wait -ArgumentList '/i "the msi with spaces in it.msi" ADV_SUPRESSDATABASETEST=1 SERVER_PROP="someserver\AB" DATABASE_PROP="DATABASE" SQL_TRUSTED_CONNECTION=1'
The outer "..." around the -command argument have been removed to avoid nested double quotes.
In case where you do need nested "-quoting, use \"[1] with the Windows PowerShell CLI (powershell.exe) and - more robustly "" with the PowerShell [Core] 7+ CLI (pwsh.exe).
Note: If the command for PowerShell contained unquoted characters such as & and |, they would be interpreted by cmd.exe, which can be avoided by enclosing the entire command in "..." or by individually ^-escaping such characters (e.g., ^& to pass & through to PowerShell).
What is inside the '...' strings is directly passed to msiexec and must therefore use "-quoting only.
[1] Even though PowerShell-internally it is ` that serves as the escape character.

Related

Run a PowerShell script from a cmd batch as admin

I have a PowerShell setup which I want to execute on a computer where perhaps the execution policy is restricted and requires admin rights.
Ideally, I could wrap it in a cmd batch like follows:
powershell -Command "Start-Process powershell -Verb runAs -ArgumentList '-noexit','-ExecutionPolicy','bypass','-File','C:\path\setup.ps1'"
The problem is that I can't make it to work when C:\path\setup.ps1 contains spaces, and also the path does not work if relative (with cd C:\path).
Any help?
While passing the pass-through arguments individually to the Start-Process cmdlet's -ArgumentList parameter may be conceptually preferable, a long-standing bug unfortunately makes it better to encode all arguments in a single string - see this answer.
Using -Verb RunAs to launch a command with elevation (as admin), invariably uses the SYSTEM32 directory as the working directory - even a -WorkingDirectory argument, if present, is quietly ignored. Thus, in order to set a custom working directory and to invoke , the -Command CLI parameter must be used, and a Set-Location (cd) call must precede a call to a script specified by relative path.
Doing all this from cmd.exe, via powershell.exe, the Windows PowerShell CLI, complicates matters due to escaping requirements.
Applied to your powershell.exe CLI call (assuming dir. C:\path 1 and script file setup 1.ps1):
powershell -Command "Start-Process -Verb RunAs powershell '-NoExit -ExecutionPolicy Bypass -Command "^"" cd \\"^""C:\path 1\\"^""; & \\"^"".\setup 1.ps1\\"^"" "^""'"
Note:
From cmd.exe, "^"" (sic) is the most robust way to pass " that are embedded in an overall "..." string to powershell.exe (from a shell-free context, such as a scheduled task, use """ or, more simply, \".
For simplicity, for the doubly nested " chars. the \-escaping technique is used above, with the \ chars. themselves requiring escaping as \\.
Note: From the PowerShell CLI perspective - including in PowerShell (Core) 7+ (see below) - \" always works, but its use is problematic from cmd.exe, which doesn't understand \" as an escaped " char. and therefore treats it as a regular string delimiter, which can cause it to misinterpret what's been \"...\" as being part of an unquoted strings, where metacharacters such as & can then break the command, because they're interpreted by cmd.exe itself, up front; e.g., powershell -c " \"Abbot & Costello\" " breaks from cmd.exe, requiring either ^& instead of " or, as shown above, escaping embedded " as "^"" instead:
powershell -c " "^""Abbot & Costello"^"" "
When you call pwsh.exe instead - the PowerShell (Core) 7+ CLI - two simplifications are possible:
In addition to \", pwsh.exe more simply supports "" for embedding " chars. in "..." strings; the latter works robustly from cmd.exe
pwsh.exe now has a separate -WorkingDirectory parameter, which therefore allows invoking the script with the -File parameter - do note, however, that the file path is resolved before the working directory is set, so the full path is used below.
pwsh.exe -Command "Start-Process -Verb RunAs pwsh.exe '-NoExit -ExecutionPolicy Bypass -WorkingDirectory ""C:\path 1"" -File ""C:\path 1\setup 1.ps1""'"
Here you have an example of a script that checks if the process is running elevated and if it's not it attempts to start a new process elevated. There is no need to nest files or use CMD in this case.
This obviously comes with the caveat of an UAC prompt, as any other process that is started with elevated permissions.
$isAdmin = [System.Security.Principal.WindowsPrincipal]::new(
[System.Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole('Administrators')
if(-not $isAdmin)
{
$params = #{
FilePath = 'powershell' # or pwsh if Core
Verb = 'RunAs'
ArgumentList = #(
"-NoExit"
"-ExecutionPolicy ByPass"
"-File `"$PSCommandPath`""
)
}
Start-Process #params
Exit
}
"I'm elevated"
# Code goes here

Powershell start-process issue when path contains R character after parenthesis

I am trying to install msi from commandline using powershell command. My msi path contains R character after parenthesis. Due to this powershell throws below error.
PowerShell.exe -ExecutionPolicy Bypass Start-Process -Wait -FilePath msiexec -ArgumentList /i, `"c:\program files\xxx (R) xxx.msi`"
If my path contain no parenthesis, it is working fine. Can someone help me resolve this issue.
Do not suggest some other method to do it. I would like to know the path issue as it works when my msi path doesnt have parenthesis.
From PowerShell:
# Note that parameter -FilePath is implied for `msiexec`, and
# -ArgumentList for `'/i ...'`
Start-Process -Wait msiexec '/i "c:\program files\xxx (R) xxx.msi"'
The most robust approach is to use a single -ArgumentList (-Args) argument that encodes all arguments, using embedded quoting, rather than passing arguments individually, due to a long-standing bug detailed in this answer.
From cmd.exe, there's no reason to involve PowerShell in this case; the built-in start command will do:
start /wait msiexec /i "c:\program files\xxx (R) xxx.msi"

Powershell / cmd - Redirecting embedded script's output streams to file

I have a situation in which a cmd script must launch a powershell script (install.ps1), elevating to admin if the cmd is not already. The line that launches the powershell looks like this:
powershell -WindowStyle Hidden "Start-Process powershell \"-NoP -Exec Bypass -File `\"%~dp0install.ps1`\" %args%\" -Verb runAs -Wait"
Or this also works:
powershell -WindowStyle Hidden "Start-Process powershell \"-NoP -Exec Bypass invoke-command { %~dp0install.ps1 %args% } \" -Verb runAs -Wait"
I would like to redirect the output from the install.ps1 script to a file for logging purposes, but having trouble doing this. Something like the following will generate the log.txt file, but output will still be shown in the console and the resulting log.txt file will be empty:
powershell -WindowStyle Hidden "Start-Process powershell \"-NoP -Exec Bypass invoke-command { %~dp0install.ps1 %args% } \" *> log.txt -Verb runAs -Wait"
Moving the *> log.txt portion to inside the Start-Process block (just after the invoke-command block), which I thought would be the key, seems to not even run the script at all (or it's flashing an error in the console too quick to see because it closes immediately).
Is it possible to achieve this logging behavior when the data I want is buried in a couple layers of powershell, executed by a cmd file?
We've technically gotten this to work by creating a powershell wrapper script that is called/elevated by the cmd, then within the wrapper calling the install.ps1 script and assigning logging in that call. Unfortunately the extra script layer causes a bunch of other tricky / more critical problems regarding getting arguments passed at the command line all the way through to the actual install script correctly, so we're really trying to avoid that route.
EDIT
Thanks to #mklement0 for the pointer that the redirect needed to be escaped, which was my problem. Follow-up question - The following command works great to log to file, but is there any way to get this same behavior using -File rather than -Command when invoking the PS script ("-Command %~dp0pg.ps1")?
powershell -Command "Start-Process -WindowStyle Hidden -Verb RunAs -Wait powershell \"-NoProfile -ExecutionPolicy Bypass -Command %~dp0pg.ps1 *^> %CD%\log.txt\""
Moving the *>log.txt redirection into the Invoke-Command block works in principle, but your problem is that in Windows PowerShell (as opposed to PowerShell Core) a process invoked with elevation (as admin), via -Verb RunAs, defaults to C:\Windows\System32 as the working directory, not the caller's working dir.
Aside from the fact that you probably didn't mean to create a log file in C:\Windows\System32, the command will fail, because writing to that location requires the caller to already be elevated.
The simplest solution is to make *> redirect to a file specified with a full path instead:
powershell -Command "Start-Process -WindowStyle Hidden -Verb RunAs -Wait powershell \"-NoProfile -ExecutionPolicy Bypass -Command %~dp0pg.ps1 *^> %CD%\log.txt\""
Note:
There is no need for Invoke-Command - just invoke the *.ps1 file directly; however, I've added -Command to make it more obvious that the remainder of the command line is to be interpreted as PowerShell code (not a script-file path with arguments only).
Because > is a cmd.exe metacharacter, it must be escaped as ^> in order to be passed through to PowerShell - perhaps surprisingly, cmd.exe considers the > to be unquoted, because it doesn't recognize the \" sequences as embedded double quotes - only PowerShell does.
As in your original command, the assumption is that neither %~dp0 - the batch file's folder dir. path - nor %CD% - the caller's working dir. path - contain spaces or other special chars. that would need additional quoting / escaping.

Powershell Removing Quotes Argument

I'm using Start-Process to start another instance of Powershell as an administrator but when I try to pass the argument list, whether as a variable or as a plain string, Powershell removes the quotes. Below is the command I'm using:
$argu = '-noexit "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat"';
powershell Start-Process -Verb RunAs -FilePath powershell -ArgumentList $argu
This is the error I get:
x86 : The term 'x86' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included,
verify that the path is correct and try again.
At line:1 char:88
+ ... Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\v ...
+ ~~~
+ CategoryInfo : ObjectNotFound: (x86:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Thank you in advance for any help.
Update:
$argu = '''-noexit ""C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat""''';
powershell Start-Process -Verb RunAs -FilePath powershell -ArgumentList $argu
This almost fixes it but now I'm getting the error above in the second window instead of the first.
(A) From inside PowerShell:
$argu = '-noexit -command & \"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat\"'
Start-Process -Verb RunAs -FilePath powershell -ArgumentList $argu
Note: I'm not calling Start-Process via powershell.exe, as there is generally no need for that.
The embedded " are \-escaped, which is what PowerShell requires when you call its CLI (perhaps surprisingly, given that PowerShell-internally it is ` that acts as the escape character).
That said given that the " are embedded inside '...' here, they shouldn't require extra escaping - see below.
The file path to execute is prefixed with call operator &, because you need it in order to execute files that are specified in quoted form.
Note that I've added -Command, which is not strictly necessary in Windows PowerShell, but would be if you ran your command from PowerShell Core (which now defaults to -File).
Alternatively, you could also specify your arguments individually, as part of an array, which is arguably the cleaner solution:
$argu = '-noexit', '-command', '&', 'de',
'\"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat\"'
Start-Process -Verb RunAs -FilePath powershell -ArgumentList $argu
Sadly, even in this case you need the extra, embedded quoting for arguments that contain spaces, which is a known Start-Process problem being tracked on GitHub.
PowerShell's handling of quoting when calling external programs is generally problematic; the current issues are summarized in this GitHub issue.
(B) From outside PowerShell (cmd.exe, a custom File Explorer context menu):
powershell -command Start-Process -Verb RunAs -FilePath powershell -ArgumentList '-noexit -command . ''C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat'''
single-quoting is now employed (with nested single quotes escaped as ''), because double-quoting would substantially complicate the escaping.
. is used instead of & to execute the .bat file, which avoids a problem with how the & is parsed; while . generally serves a different purpose than &, the two operators behave the same when calling external programs.
If you also want to set the working directory for the PowerShell session that ultimately opens elevated, you need to incorporate an explicit Set-Location (cd) call into the command string, because Start-Process -Verb RunAs always defaults to the SYSTEM32 folder (even the -WorkingDirectory parameter doesn't help in that case).
For that to work safely, however, you must quote the directory path using double-quoting, given that file names may contain single quotes; with %V as the directory path (which File Explorer supplies to commands invoked via custom context menus), the properly escaped Set-Location call looks like this (I wish I were kidding):
Set-Location \"\"\"%V%\"\"\"
Integrated into the full command (using Set-Location's built-in alias cd for brevity):
powershell -command Start-Process -Verb RunAs -FilePath powershell -ArgumentList '-noexit -command cd \"\"\"%V%\"\"\"; . ''C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat'''
As an aside: PowerShell Core now has a -WorkingDirectory (-wd) CLI parameter that allows you to control the startup directory more robustly (pwsh -wd "c:\path\to\dir" ...); in fact, it was precisely the File Explorer custom context-menu use case that prompted the introduction of this parameter.

Run schtasks.exe with PowerShell command from command prompt

I want to run the schtasks.exe as administrator using PowerShell -Command with Start-Process to create a task.
This is the code of my .bat file I've got so far.
set mydir=%~dp0
set mydir2=%mydir%prim.exe
set mys='`"C:\workspace\prim.exe`"'
Powershell -Command "&{Start-Process -FilePath schtasks -ArgumentList '/Create', '/SC ONLOGON', '/TN CoUpdater', '/IT', '/RL HIGHEST', '/TR', '\"%mydir2%\"' -verb RunAs}"
At first I set up my path to the program I want to run. The problem is that my path name contains some white spaces. After some research I was able to determine that the path is in the task scheduler but without quotes (argument after /TR ):
The next thing I did was adding of quotes to a string and put it as the argument after /TR
set mys='`"C:\workspace\prim.exe`"'
which results in:
But now I don't know how I should add the quotes to my pathname. I hope anybody can help me with this problem. Thank you!
Using a call operator is unnecessary, here's a way that should work:
SET "PRIM=""%~dp0prim.exe"""
powershell -Command "Start-Process -FilePath schtasks -ArgumentList '/Create','/SC','ONLOGON','/TN','CoUpdater','/IT','/RL','HIGHEST','/TR','%PRIM%' -Verb runas"
Note I embedded the quotes into the environment variable. As long as there are matching sets of quotes, the cmd parser shouldn't complain.
By try and error I found a solution:
set mys='`\"C:\works pace\prim.exe`\"'
Powershell -Command "Start-Process -FilePath schtasks -ArgumentList '/Create', '/SC ONLOGON', '/TN CoUpdater', '/IT', '/RL HIGHEST', '/TR', \"%mys%\" -verb RunAs"
This worked for me and in the task scheduler it is displayed correctly: