powershellelevated-privileges

PowerShell: Elevated code from non-elevated script, accessing variables and functions


I am preparing new computers. After applying an image, I run a PowerShell script for some post-image deployment steps. Some steps must be run as the new (current) user, like registry settings in HCCU, while others, peppered through the script, must be run elevated.

In my script, I call the RunElevated function below for the code the requires elevation. I would like to share values and functions between elevated and non-elevated code blocks, but is that possible? I tried passing arguments when calling Start-Process powershell.exe but ran into the “Inception” problem of quotes within quotes, arguments within arguments.

function RunElevated($ScriptBlock)
{
    write-host -NoNewline "`nStarting a new window with elevated privileges. Will return here after..."

    $scriptBlockWithBefore = {
        write-host "`nSTEPS RUNNING WITH ELEVATED PRIVILEGES...`n" @mildAlertColours
    }

    $scriptBlockAfter = {
        Write-Host -nonewline "`nHit Enter to exit this mode. "
        Read-Host
    }

    $scriptBlockToUse = [scriptblock]::Create($scriptBlockWithBefore.ToString() + "`n" + $ScriptBlock.ToString() + "`n" + $scriptBlockAfter)

    $proc = Start-Process "powershell.exe" -Verb runas -ArgumentList "-command `"$scriptBlockToUse`"" -PassThru -WorkingDirectory $pwd.ToString()

    $proc.WaitForExit()

    if($proc.ExitCode -ne 0) {
        write-host "ran into a problem."
    }
}

Solution

  • As zett42 notes, you can use the powershell.exe, the Windows PowerShell CLI's -EncodedCommand parameter to safely pass arbitrary code to a PowerShell child process.

    To also pass arguments through safely, you need the (currently undocumented) -EncodedArguments parameter.

    This Base64-encoding-based approach:

    Here's self-contained sample code that demonstrates the technique:

    # Sample script block to execute in the elevated child process.
    $scriptBlock = {
      # Parameters
      param([string] $Foo, [int] $Bar, [hashtable] $Hash)
      # Embedded function
      function Get-Foo { "hi: " + $args }
        
      # Show the arguments passed.
      $PSBoundParameters | Out-Host
    
      # Call the embedded function
      Get-Foo $Bar
    
      Read-Host 'Press Enter to exit.'
    }
    
    # List of sample arguments to pass to the child process.
    $passThruArgs = 'foo!', 42, @{ SomeKey = 'Some Value' }
    
    # Call via `Start-Process -Verb RunAs` to achieve elevation, and pass the 
    # Base64-encoded values to `-EncodedCommand` and `-EncodedArgument`
    Start-Process -Wait -Verb RunAs powershell.exe -ArgumentList (
      '-EncodedCommand', (
        [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($scriptBlock))
      ),
      '-EncodedArguments', (
        [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes(
            [System.Management.Automation.PSSerializer]::Serialize($passThruArgs)
        ))
      )
    )
    

    Note:


    Optional reading: Why selective elevation may be preferred / necessary:


    [1] See this answer for details.