'How can I elevate Powershell while keeping the current working directory AND maintain all parameters passed to the script?

function Test-IsAdministrator
{
    $Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Principal = New-Object System.Security.Principal.WindowsPrincipal($Identity)
    $Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Test-IsUacEnabled
{
    (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System).EnableLua -ne 0
}

if (!(Test-IsAdministrator))
{
    if (Test-IsUacEnabled)
    {
        [string[]]$argList = @('-NoProfile', '-NoExit', '-File', $MyInvocation.MyCommand.Path)
        $argList += $MyInvocation.BoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key)", "$($_.Value)"}
        $argList += $MyInvocation.UnboundArguments
        Start-Process PowerShell.exe -Verb Runas -WorkingDirectory $pwd -ArgumentList $argList 
        return
    }
    else
    {
        throw "You must be an administrator to run this script."
    }
}

If I run the script above, it successfully spawns another PowerShell instance with elevated privileges but the current working directory is lost and automatically set to C:\Windows\System32. Bound Parameters are also lost or incorrectly parsed.

After reading similar questions I learned that when using Start-Process with -Verb RunAs, the -WorkingDirectory argument is only honored if the target executable is a .NET executable. For some reason PowerShell 5 doesn't honor it:

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

Quote from this related question:

In practice - and the docs do not mention that - the -WorkingDirectory parameter is not respected if you start a process elevated (with administrative privileges, which is what -Verb RunAs - somewhat obscurely - does): the location defaults to $env:SYSTEMROOT\system32 (typically, C:\Windows\System32).

So the most common solution I've seen involves using -Command instead of -File. I.E:

Start-Process -FilePath powershell.exe -Verb Runas -ArgumentList '-Command', 'cd C:\ws; & .\script.ps1'

This looks really hack-ish but works. The only problem is I can't manage to get an implementation that can pass both bound and unbound parameters to the script being called via -Command.

I am trying my hardest to find the most robust implementation of self-elevation possible so that I can nicely wrap it into a function (and eventually into a module I'm working on) such as Request-AdminRights which can then be cleanly called immediately in new scripts that require admin privileges and/or escalation. Pasting the same auto-elevation code at the beginning of every script that needs admin rights feels really sloppy.

I'm also concerned I might be overthinking things, and to just leave elevation to the script level instead of wrapping it into a function.

Any input at all is greatly appreciated.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source