powershell.exe start-process -About "\" when using the Verb option - powershell

Thank you for your help.
I use Powershell in the Windows 10 command prompt and try to execute commands with administrative rights.
When I try the following three, a, b and c, I cannot start with "RunAs" as I think.
Does anyone know how to solve this problem?
(a) It can be executed successfully. The arguments are also passed correctly.
powershell.exe start-process -FilePath 'TestEnv.cmd' -ArgumentList '\"a\" \"B C\" \"d\"' -Verb Open
(b) Does not start after UAC confirmation.
powershell.exe start-process -FilePath 'TestEnv.cmd' -ArgumentList '\"a\" \"B C\" \"d\"' -Verb RunAs
(c) It is possible to invoke it, but the "B C" argument is not reflected in the "double quotes" argument, and "B C" is recognized separately.
powershell.exe start-process -FilePath 'TestEnv.cmd' -ArgumentList '"a" "B C" "d"' -Verb RunAs
Translated with www.DeepL.com/Translator (free version)

tl;dr
REM From cmd.exe (Command Prompt) / a batch file:
powershell.exe -c Start-Process cmd.exe -Verb RunAs ('/k {0} \"a\" \"B C\" \"d\"' -f ((Convert-Path 'TestEnv.cmd') -replace ' ', '^^ '))
Replace /k with /c to make the new window close automatically when the batch file exits.
The working directory for the batch file is C:\Windows\System32.
If you also want to ensure that the batch file executes in the caller's current directory:
REM From cmd.exe (Command Prompt) / a batch file:
powershell.exe -c Start-Process cmd.exe -Verb RunAs ('/k cd \"{0}\" ^&^& {1} \"a\" \"B C\" \"d\"' -f $PWD.ProviderPath, ((Convert-Path 'TestEnv.cmd') -replace ' ', '^^ '))
Read on for an explanation.
The main problem is:
When you pass a batch-file path directly - as the executable - to Start-Process -Verb RunAs, there seems to be a problem with passing an argument list that contains double quotes (") - I don't know the reason, but the problem occurs in a layer below PowerShell, either in the underlying System.Diagnostics.ProcessStartInfo .NET API or possibly even in the underlying ShellExecuteExe WinAPI function.
(If you either need no arguments or none of them require ", you can pass the batch-file path directly as the executable; by default, because the underlying executable is cmd.exe, which is located in C:\Windows\System32, C:\Windows\System32 becomes the working directory, which you can override with -WorkingDirectory, but note that you must then refer to the batch file with a (possibly relative) path, if it is located elsewhere.)
Workaround:
Instead of using the batch-file path directly as the executable to launch, use cmd.exe as the executable, and pass the batch-file path and all its arguments as arguments to the /c option (or /k, if you want to keep the new window open).
Unfortunately, with -Verb Runas, cmd.exe invariably uses C:\Windows\System32 as the new process' working directory - passing a startup directory with the -WorkingDirectory parameter is then ignored.
The same applies to powershell.exe, but, curiously, not to pwsh.exe (PowerShell [Core] v6+) and .NET-based executables, which (a) preserve the caller's working directory by default and (b) do honor a -WorkingDirectory argument.
Therefore, if your TestEnv.cmd batch file is located in the current directory - as opposed to a directory listed in the PATH environment variable ($env:PATH) - you must pass the batch file's full path to cmd.exe, which you can determine with Convert-Path.
Note: If you batch file is in the PATH, so to speak, this step isn't necessary.
Unfortunately, another workaround is then needed, in case the batch file's full path contains spaces: Because "..."-enclosing the batch-file path inexplicably fails, the individual spaces must be ^-escaped instead.
From inside PowerShell this means your command must look like this (note that I'm using /k to keep the new window open, for diagnostic purposes; use /c to auto-close the window when the batch file exits):
# From inside PowerShell:
Start-Process cmd.exe -Verb RunAs (
'/k {0} "a" "B C" "d"' -f ((Convert-Path 'TestEnv.cmd') -replace ' ', '^ ')
)
Caveat: Since cmd.exe is being invoked, C:\Windows\System32 is the working directory.
If you also want to ensure that the batch file executes in the caller's current directory, you must prepend a cd command:
# From inside PowerShell:
Start-Process cmd.exe -Verb RunAs (
'/k cd "{0}" && {1} "a" "B C" "d"' -f $PWD.ProviderPath, ((Convert-Path 'TestEnv.cmd') -replace ' ', '^ ')
)
Calling this from cmd.exe (Command Prompt) / a batch file, via Windows PowerShell's CLI, powershell.exe (note that in PowerShell [Core] v6+ it is now pwsh), adds additional complexity:
The " chars. must be escaped as \" so that PowerShell recognizes as them as part of the command to execute rather than as syntactic quoting around the CLI arguments.
^ must be escaped as ^^ and, in the second command below, & as ^& to prevent cmd.exe from interpreting these characters (up front).
REM From cmd.exe (Command Prompt) / a batch file:
powershell.exe -c Start-Process cmd.exe -Verb RunAs ('/k {0} \"a\" \"B C\" \"d\"' -f ((Convert-Path 'TestEnv.cmd') -replace ' ', '^^ '))
If you also want to ensure that the batch file executes in the caller's current directory:
REM From cmd.exe (Command Prompt) / a batch file:
powershell.exe -c Start-Process cmd.exe -Verb RunAs ('/k cd \"{0}\" ^&^& {1} \"a\" \"B C\" \"d\"' -f $PWD.ProviderPath, ((Convert-Path 'TestEnv.cmd') -replace ' ', '^^ '))

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 - Set working directory on ELEVATED Start-Process command [duplicate]

When I enter the command
Start-Process powershell -WorkingDirectory "D:\folder"
it opens new PowerShell window with D:\folder location set.
But when I enter the command
Start-Process powershell -WorkingDirectory "D:\folder" -Verb RunAs
it opens new PowerShell window with admin rights but with C:\Windows\system32 location set.
How can I open new PowerShell window with admin rights and my own location determined?
I also had the same problem and solved it with this command:
Start-Process powershell.exe -verb runAs -ArgumentList '-NoExit', '-Command', 'cd D:\folder'
Once you run the above command, Windows will launch with admin authority and the specified directory.
Here's another example which can be used for opening CMD from PowerShell as an administrator into the current folder:
Start-Process cmd -ArgumentList ("/k cd {0}" -f (Get-Location).path) -Verb RunAs
if used within a script you can use
Start-Process cmd -ArgumentList ("/k cd {0}" -f $PSScriptRoot) -Verb RunAs
If you want to open a new elevated PowerShell session from the current one which is not elevated you can use:
Start-Process powershell.exe -ArgumentList ("-NoExit",("cd {0}" -f (Get-Location).path)) -Verb RunAs
or
Start-Process powershell.exe -ArgumentList ("-NoExit",("cd {0}" -f $PSScriptRoot)) -Verb RunAs
when used inside scripts
When using Start-Process with -Verb RunAs, a -WorkingDirectory argument is honored if the target executable is a .NET executable; examples:
pwsh.exe (the PowerShell (Core) CLI) does honor it.
cmd.exe and, curiously, powershell.exe (the Windows PowerShell CLI) do not, and invariably use C:\Windows\System32.
The problem exists at the level of the .NET API that PowerShell uses behind the scenes (see System.Diagnostics.ProcessStartInfo), as of this writing (.NET 6.0.0-preview.4.21253.7).
Unless you know that you're invoking a .NET executable, a workaround that changes to the desired working folder in the new process is therefore required; to offer a more robust alternative to ふゆな's helpful answer:
$dir = $PWD.ProviderPath # use the current dir.
Start-Process -Verb RunAs powershell.exe #"
-noexit -c Set-Location -LiteralPath "$dir"
"#
The embedded "..." quoting around $dir ensures that paths with spaces are also handled correctly. (To use the current directory without an intermediate variable, replace "dir" with "$($PWD.ProviderPath)".
Using a here-string (#"<newline>...<newline>"#) isn't strictly necessary, but simplifies the embedded quoting; with a regular expandable string ("..."), the embedded " must be escaped as `" (or "").
Using $PWD's .ProviderPath property ensures that a file-system-native path is used (based on drive letters also seen in cmd.exe, for instance), given that the calling session's current location may be based on a PowerShell-only drive (see New-PSDrive) that the elevated process may not have defined (at all or not based on the same root location).
Caveat: If the native path is on a mapped network drive, this won't work, because elevated processes do not see the same drive mappings; in that event, pass the underlying UNC path.
Workaround for launching a GUI application elevated from a given working directory:
Since changing to the working directory must happen in the new, elevated process, a helper shell process is needed to perform this operation, which is best done via cmd.exe (for better performance):
$exeToLaunch = 'Notepad.exe' # may include arguments
$dir = $PWD.ProviderPath # use the current dir.
Start-Process -Verb RunAs -WindowStyle Hidden cmd.exe #"
/c cd "$dir" & $exeToLaunch
"#
Once you run Powershell as administrator;
user the push-location command like so:
Push-Location -Path C:\
or put it into your script and run the script from the elevated Powershell prompt.
I just ran your code example and it opened correctly for me at the WorkingDirectory location. Ensure the directory exists before you run the command. I tested against a drive on C and secondary drive as well and both worked.

How to store a folder with spaces inside a batch script using the SET /P command and call the stored variable while using the POWERSHELL -Command

I have a batch script that adds or removes folder paths to Windows Defender's exclusion list.
I have issues when using folder paths with spaces only.
I have tried escaping the spaced path using double quotes multiple ways.
Escape Method1: \"%FDIR%\"
Escape Method2: `"%FDIR%`"
Escape Method3: `""%FDIR%`""
The basic flow of the script is
Prompt user for input of folder path and store it in the FDIR var
The first POWERSHELL command will add the path stored in the FDIR var
The last POWERSHELL command at the bottom will show the current
exclusion list so the user can see what they are dealing with
and to verify if the first POWERSHELL command worked (it will show up if successful)
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS
COLOR 0A
:----------------------------------------------------------------------------------
:: USING SET /P COMMAND I STORE THIS PATH EXACTLY...
:: SPACES AND ALL WITH NO SURROUNDING QUOTES IN FDIR VARIABLE: C:\Program Files\ASRock Utility
SET /P "FDIR=ENTER A FOLDER PATH: "
:----------------------------------------------------------------------------------
:: EXECUTE POWERSHELL COMMAND USING THE CHOSEN PATH STORED IN THE FDIR VARIABLE
POWERSHELL -ExecutionPolicy Bypass -WindowStyle Hidden -Command "Start-Process POWERSHELL -ArgumentList '-WindowStyle Hidden -Command Add-MpPreference -ExclusionPath \"%FDIR%\"' -Verb RunAs"
:----------------------------------------------------------------------------------
:: DISPLAY THE CURRENT FOLDER EXCLUSIONS IN POWERSHELL WINDOW
POWERSHELL -ExecutionPolicy Bypass -WindowStyle Hidden -Command "Start-Process POWERSHELL -ArgumentList '-WindowStyle Maximized -Command Write-Host "Current Excluded Folders"; Write-Host; Get-MpPreference | Select-Object -ExpandProperty ExclusionPath; Write-Host; Read-Host "Press Enter To Exit"; EXIT' -Verb RunAs"

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.

Start-Process -WorkingDirectory as administrator does not set location

When I enter the command
Start-Process powershell -WorkingDirectory "D:\folder"
it opens new PowerShell window with D:\folder location set.
But when I enter the command
Start-Process powershell -WorkingDirectory "D:\folder" -Verb RunAs
it opens new PowerShell window with admin rights but with C:\Windows\system32 location set.
How can I open new PowerShell window with admin rights and my own location determined?
I also had the same problem and solved it with this command:
Start-Process powershell.exe -verb runAs -ArgumentList '-NoExit', '-Command', 'cd D:\folder'
Once you run the above command, Windows will launch with admin authority and the specified directory.
Here's another example which can be used for opening CMD from PowerShell as an administrator into the current folder:
Start-Process cmd -ArgumentList ("/k cd {0}" -f (Get-Location).path) -Verb RunAs
if used within a script you can use
Start-Process cmd -ArgumentList ("/k cd {0}" -f $PSScriptRoot) -Verb RunAs
If you want to open a new elevated PowerShell session from the current one which is not elevated you can use:
Start-Process powershell.exe -ArgumentList ("-NoExit",("cd {0}" -f (Get-Location).path)) -Verb RunAs
or
Start-Process powershell.exe -ArgumentList ("-NoExit",("cd {0}" -f $PSScriptRoot)) -Verb RunAs
when used inside scripts
When using Start-Process with -Verb RunAs, a -WorkingDirectory argument is honored if the target executable is a .NET executable; examples:
pwsh.exe (the PowerShell (Core) CLI) does honor it.
cmd.exe and, curiously, powershell.exe (the Windows PowerShell CLI) do not, and invariably use C:\Windows\System32.
The problem exists at the level of the .NET API that PowerShell uses behind the scenes (see System.Diagnostics.ProcessStartInfo), as of this writing (.NET 6.0.0-preview.4.21253.7).
Unless you know that you're invoking a .NET executable, a workaround that changes to the desired working folder in the new process is therefore required; to offer a more robust alternative to ふゆな's helpful answer:
$dir = $PWD.ProviderPath # use the current dir.
Start-Process -Verb RunAs powershell.exe #"
-noexit -c Set-Location -LiteralPath "$dir"
"#
The embedded "..." quoting around $dir ensures that paths with spaces are also handled correctly. (To use the current directory without an intermediate variable, replace "dir" with "$($PWD.ProviderPath)".
Using a here-string (#"<newline>...<newline>"#) isn't strictly necessary, but simplifies the embedded quoting; with a regular expandable string ("..."), the embedded " must be escaped as `" (or "").
Using $PWD's .ProviderPath property ensures that a file-system-native path is used (based on drive letters also seen in cmd.exe, for instance), given that the calling session's current location may be based on a PowerShell-only drive (see New-PSDrive) that the elevated process may not have defined (at all or not based on the same root location).
Caveat: If the native path is on a mapped network drive, this won't work, because elevated processes do not see the same drive mappings; in that event, pass the underlying UNC path.
Workaround for launching a GUI application elevated from a given working directory:
Since changing to the working directory must happen in the new, elevated process, a helper shell process is needed to perform this operation, which is best done via cmd.exe (for better performance):
$exeToLaunch = 'Notepad.exe' # may include arguments
$dir = $PWD.ProviderPath # use the current dir.
Start-Process -Verb RunAs -WindowStyle Hidden cmd.exe #"
/c cd "$dir" & $exeToLaunch
"#
Once you run Powershell as administrator;
user the push-location command like so:
Push-Location -Path C:\
or put it into your script and run the script from the elevated Powershell prompt.
I just ran your code example and it opened correctly for me at the WorkingDirectory location. Ensure the directory exists before you run the command. I tested against a drive on C and secondary drive as well and both worked.