PowerShell - Set working directory on ELEVATED Start-Process command [duplicate] - powershell

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.

Related

How can I properly escape a space in ArgumentList of Start-Process command nested within an ArgumentList of Start-Process command?

For a long time I've been using a powershell script containing the following line to automatically start various processes as a different user AND with elevated permissions:
start-process powershell -credential <username> -argumentlist '-command &{start-process <executable.exe> -verb runas -argumentlist <c:\path\to\file\that\executable-should-open.ext>}' -workingdirectory c:\windows\system32
Formatting it this way lets me simply create a convenient shortcut to the script. This works fine... as long as <c:\path\to\file\that\executable-should-open.ext> contains no spaces. However that file is now located at a filepath which contains spaces and that's something I cannot change.
There's countless threads out there dealing with how to properly escape the -ArgumentList parameter argument(s) of Start-Process, but none of the solutions seem to work for this case where the space is in the nested -ArgumentList. I've tried for days to find the magic sauce combination of single and double quotes, backticks, backslashes, $variables, $($variables), #("arguments","as","arrays"), not using &, etc., etc.
Can anyone help me achieve this? I'm certainly open to other solutions that don't use Start-Process, but ideally the working solution should work in a standalone script, and once run, should leave no additional console windows open.
With most attempts that don't outright throw errors, the behavior I'm seeing is that c:\path\to\file\with spaces\file.ext is interpreted by the nested Start-Process cmdlet as two separate arguments, i.e. c:\path\to\file\with and spaces\file.ext.
I use this syntax to open several different administrative apps, but as a concrete example, here is the one I've been testing and failing to make work.
Original, working script:
start-process powershell -credential domain\username -argumentlist '-command &{start-process mmc -verb runas -argumentlist c:\custom-aduc.msc}' -workingdirectory c:\windows\system32`
New, non-working script:
start-process powershell -credential domain\username -argumentlist '-command &{start-process mmc -verb runas -argumentlist c:\new path\custom-aduc.msc}' -workingdirectory c:\windows\system32
For reference, the reason I can't change the path is because the custom-aduc.msc is now being kept on OneDrive, which infuriatingly provides no way to change its local sync location from drive:\path\OneDrive - CompanyName. This is a heavily supported feature request for OneDrive, but has not been committed to.
Thanks for any insights.
P.S. I've considered alternate solutions such as:
Having the script first download/copy the files to a local path without spaces, however, this prevents me from using the executable to modify the file, or I'd have to monitor changes and re-upload them.
Mounting the OneDrive path as a separate drive letter. But that's much less of a standalone solution, and seems heavy handed.
TL;DR: To answer the titular question: It might be possible, but it's much easier to simply split the Start-Process commands into different scripts, to avoid nesting one inside the quotes of the other.
After lots more trial and error with escape syntax, I was able to find an acceptable solution. However I had to split it up into two separate script files, so as to avoid nesting the second Start-Process (and thus its -ArgumentList arguments) inside any other quotes.
script1.ps1
#runas /user:domain\username "powershell -file \`"c:\script2.ps1\`""
# Equivalent to the above, but provides a nicer credential prompt:
Start-Process -FilePath powershell -Credential domain\username -WorkingDirectory "c:\windows\system32" -ArgumentList "-File `"c:\script2.ps1`""
script2.ps1
Start-Process -FilePath mmc -Verb runas -ArgumentList "`"C:\path\with spaces\aduc.msc`""
The important part is that this way I can use the usual escaping methods for each -ArgumentList, without them interfering with each other. It may still be possible to use combinations of cmd interpreter escapes (\), and posh escapes (`) to do this all in one line, but I don't have the patience to figure that out. It also may be possible to hack this into a single script, but I can't be bothered.
At this point, I can simply create shortcuts which call script1.ps1 to achieve the desired result, just like I used to.
I like to use this for at least 3 different executables, and with 3 different usernames (my average joe account, and two different superuser accounts). To avoid needing two scripts for each executable+username combination, I generalized the scripts like so:
script1.ps1
$app = $args[0]
$user = $args[1]
$script2 = "C:\path\to\script2.ps1"
# Dont run as different user if this is my usual Windows login
# Otherwise I have to enter a password for no good reason
if($user -eq "domain\non-su-account") {
& powershell -file "$script2" $app
}
else {
#runas /user:$user "powershell -file \`"$script2\`" $app"
# Equivalent to the above, but provides a nicer credential prompt:
Start-Process -FilePath powershell -Credential $user -WorkingDirectory "c:\windows\system32" -ArgumentList "-File `"$script2`" $app"
}
script2.ps1
$app = $args[0]
switch($app) {
"aduc" {
Start-Process -FilePath mmc -Verb runas -ArgumentList "`"C:\path\with spaces\aduc.msc`""
break
}
"posh" {
Start-Process -FilePath powershell -Verb runas -ArgumentList "-nologo -noexit -command `"&{. \`"C:\path\with spaces\custom-powershell-profile.ps1\`"}`""
break
}
"posh-noprofile" {
Start-Process -FilePath powershell -Verb runas -ArgumentList "-nologo -noexit"
break
}
"mecm" {
& "C:\Program Files (x86)\Microsoft Endpoint Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.exe"
break
}
}
So the shortcut targets look like:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -file "C:\path\to\script1.ps1" aduc domain\username
Opening powershell and forcefully dot sourcing my custom profile script isn't really necessary, but it avoids the need to touch the powershell profiles of the superuser accounts, making this more portable.
Now that I've thoroughly demonstrated the weakness with which most markdown interpreters render nested/escaped powershell quotes, I bid you a good day. Hopefully this is useful to at least one other person ever again.

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.

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.

Run powershell in new window

I would like to run new powershell window with parameters. I was trying to run following:
powershell -Command "get-date"
but everything happens in same console. Is there a simple way to do this?
To open a new PowerShell Window from PowerShell:
Start-Process PowerShell
Or my personal favorite:
Start-Process PowerShell -WindowStyle Maximized
Then you could typeGet-Datewithout having to deal with the -ArgumentList's tendency to close itself. I still haven't found a way to open a new PowerShell process with the -ArgumentList Parameter, without it immediately closing after it runs. For Instance:
Start-Process PowerShell -ArgumentList "Get-Date"
or simply
Start-Process PowerShell -ArgumentList Get-Date
Will Close Immediately after running the process.
In order to get it to wait before closing you could add:
Start-Process PowerShell -ArgumentList 'Get-Date; Read-Host "Press Enter"'
Since the -Wait Parameter seems to do nothing at all in this case.
FYI - PowerShell Suggested Syntax is actually:
Start-Process -FilePath "powershell.exe"
But since PowerShell is a standard Windows Application in the %SystemRoot%\system32 Environment Variables the command line(s) should recognize a simple
Powershell
Command
Use the start command. In a CMD prompt, try:
start powershell -noexit -command "get-date"
For Start/Run (or Win+r) prompt, try:
cmd /c start powershell -noexit -command "get-date"
The -noexit will tell Powershell to, well, not to exit. If you omit this parameter, the command will be executed and you are likely to just see a glimpse of a Powershell window. For interactive use, this is a must. For scripts it is not needed.
Edit:
start is an internal command for CMD. In Powershell it is an alias for Start-Process. These are not the same thing.
As for why the window is black, that's because the shortcut for Powershell.exe is configured to set the background blue.
To call a PowerShell (PS) script in a second terminal window without exiting, you can use a script similar to:
Start-Process PowerShell -ArgumentList "-noexit", "get-date"
or if you need to run another script from a specific location:
Start-Process PowerShell -ArgumentList "-noexit", "-command .\local_path\start_server.ps1"