While working in PowerShell I tend to quickly switch to admin mode by typing
Start-Process wt -Verb runas
When I do so, a new window appears (sadly, no sudo
in Windows). In that new session however, the environment is totally fresh. Is it possible to keep variables, aliases, working dir and all other stuff of similar matter while jumping to a new window? If not, then well, it's a valid answer.
To give some example, I am looking for this behavior:
C:\test> $x = 123
C:\test> Start-Process wt
C:\test> $x
123
By design, elevated sessions (-Verb RunAs
) do not inherit the caller's environment variables and even explicit attempts to pass an environment are categorically prevented by the underlying .NET API.[1]
Also, whether or not you use -Verb RunAs
, the state of a PowerShell session (aliases, functions, shell-only variables, ...) is never inherited when you launch another PowerShell process, such as with Start-Process
.
A workaround is to explicitly and selectively recreate the state of interest via commands executed in the elevated session, based on values from the caller's state, but that is quite cumbersome and has limitations, as the following example shows:
# Define a few things to copy to the elevated session.
$x1 = 123
$x2 = '3" of snow' # !! See the caveat re regular variables below.
$env:foo = 1
$env:foo2 = 2
Set-Alias bar Get-Date
function baz { "hello, world" }
# Note: The following only copies the definitions above.
# You could try to copy ALL such definitions, by omitting a target name / pattern:
# Get-ChildItem env:
# Get-ChildItem function:
# Get-ChildItem alias:
# CAVEAT: This will NOT generally work with *regular variables*.
Start-Process -Verb RunAs powershell @"
-NoExit -Command Set-Location -LiteralPath \"$((Get-Location -PSProvider FileSystem).ProviderPath)\"
$(Get-Variable x? | ForEach-Object { "`${$($_.Name)} = $(if ($_.Value -is [string]) { "'{0}'" -f ($_.Value -replace "'", "''" -replace '"', '\"') } else { $_.Value }); " })
$(Get-ChildItem env:foo* | ForEach-Object { "Set-Item \`"env:$($_.Name)\`" \`"$($_.Value -replace '"', '\"\"')\`"; " })
$(Get-ChildItem function:bar | ForEach-Object { "`$function:$($_.Name) = \`"$($_.Definition -replace '"', '\"\"')\`"; " })
$(Get-ChildItem alias:baz | ForEach-Object { "`$alias:$($_.Name) = \`"$($_.Definition)\`"; " })
"@
Important:
I've omitted the call to Windows Terminal (wt.exe
), as that would create another PowerShell session, which means that only the following definitions would be preserved for that session:
-d
option: wt.exe -d \"$((Get-Location -PSProvider FileSystem).ProviderPath)\"
-WindowStyle Hidden
to Start-Process
, remove -NoExit
before -Command
in the argument list, and add a wt.exe
call at the bottom.Preserving the other types of definitions requires working directly in the elevated powershell
session, which will invariably use a regular (conhost.exe
) console window, however.
In general, it's best to place definitions that should be available in both regular and elevated sessions in your $PROFILE
file.
Complementarily, see this answer for convenience function Enter-AdminPSSession
, which allows you to pass a script block to execute in the elevated session, to which you can pass values from the caller's state as arguments.
Note:
The above uses the Windows PowerShell CLI, powershell.exe
. To use PowerShell (Core) 7+ instead, substitute pwsh.exe
.
The above covers preserving the current file-system location, environment variables, aliases, and functions in a generic fashion.
Caveat: By contrast, preserving regular variables is limited to strings and numbers - in essence, instances of those data types whose stringified representation is recognized as such when interpreted as a source-code literal.
-EncodedCommand
and -EncodedArguments
parameters as shown in this answer, but the range of types that can be represented with type fidelity is fundamentally limited by PowerShell's XML-based serialization infrastructure - see this answer.[1] This limitation also surfaces in PowerShell (Core) 7, where Start-Process
now has an -Environment
parameter for modifying the inherited environment, which, however, can not be combined with -Verb RunAs
. Unfortunately, as of v7.5.0, such an invalid parameter combination is quietly ignored instead of reporting an error (which is what happens in an analogous .NET API call): see GitHub issue #20923.