windowspowershellwindows-terminal

How to start another PowerShell session in a separate window while keeping the environment?


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:

First window

C:\test> $x = 123
C:\test> Start-Process wt

New window

C:\test> $x
123

Solution

  • 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:

    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:


    [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.