Create-ProcessWithToken.ps1

function Create-ProcessWithToken
{
    param (
        [Parameter(Position=0, Mandatory=$true)]
        [IntPtr]
        $hToken,

        [Parameter(Position=1, Mandatory=$true)]
        [String]
        $ProcessName,

        [Parameter(Position=2)]
        [String]
        $ProcessArgs,

        [Parameter(Position=3)]
        [Switch]
        $PassThru
    )
    Write-Verbose "Entering Create-ProcessWithToken"
    #Duplicate the token so it can be used to create a new process
    [IntPtr]$NewHToken = [IntPtr]::Zero
    $Success = $DuplicateTokenEx.Invoke($hToken, $Win32Constants.MAXIMUM_ALLOWED, [IntPtr]::Zero, 3, 1, [Ref]$NewHToken)
    if (-not $Success)
    {
        $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
        Write-Warning "DuplicateTokenEx failed. ErrorCode: $ErrorCode"
    }
    else
    {
        $StartupInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$STARTUPINFO)
        [IntPtr]$StartupInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($StartupInfoSize)
        $memset.Invoke($StartupInfoPtr, 0, $StartupInfoSize) | Out-Null
        [System.Runtime.InteropServices.Marshal]::WriteInt32($StartupInfoPtr, $StartupInfoSize) #The first parameter (cb) is a DWORD which is the size of the struct

        $ProcessInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$PROCESS_INFORMATION)
        [IntPtr]$ProcessInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ProcessInfoSize)

        $ProcessNamePtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni("$ProcessName")
        $ProcessArgsPtr = [IntPtr]::Zero
        if (-not [String]::IsNullOrEmpty($ProcessArgs))
        {
            $ProcessArgsPtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni("`"$ProcessName`" $ProcessArgs")
        }

        $FunctionName = ""
        if ([System.Diagnostics.Process]::GetCurrentProcess().SessionId -eq 0)
        {
            #Cannot use CreateProcessWithTokenW when in Session0 because CreateProcessWithTokenW throws an ACCESS_DENIED error. I believe it is because
            #this API attempts to modify the desktop ACL. I would just use this API all the time, but it requires that I enable SeAssignPrimaryTokenPrivilege
            #which is not ideal.
            Write-Verbose "Running in Session 0. Enabling SeAssignPrimaryTokenPrivilege and calling CreateProcessAsUserW to create a process with alternate token."
            Enable-Privilege -Privilege SeAssignPrimaryTokenPrivilege
            $Success = $CreateProcessAsUserW.Invoke($NewHToken, $ProcessNamePtr, $ProcessArgsPtr, [IntPtr]::Zero, [IntPtr]::Zero, $false, 0, [IntPtr]::Zero, [IntPtr]::Zero, $StartupInfoPtr, $ProcessInfoPtr)
            $FunctionName = "CreateProcessAsUserW"
        }
        else
        {
            Write-Verbose "Not running in Session 0, calling CreateProcessWithTokenW to create a process with alternate token."
            $Success = $CreateProcessWithTokenW.Invoke($NewHToken, 0x0, $ProcessNamePtr, $ProcessArgsPtr, 0, [IntPtr]::Zero, [IntPtr]::Zero, $StartupInfoPtr, $ProcessInfoPtr)
            $FunctionName = "CreateProcessWithTokenW"
        }
        if ($Success)
        {
            #Free the handles returned in the ProcessInfo structure
            $ProcessInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ProcessInfoPtr, [Type]$PROCESS_INFORMATION)
            $CloseHandle.Invoke($ProcessInfo.hProcess) | Out-Null
            $CloseHandle.Invoke($ProcessInfo.hThread) | Out-Null

    #Pass created System.Diagnostics.Process object to pipeline
    if ($PassThru) {
        #Retrieving created System.Diagnostics.Process object
        $returnProcess = Get-Process -Id $ProcessInfo.dwProcessId

        #Caching process handle so we don't lose it when the process exits
        $null = $returnProcess.Handle

        #Passing System.Diagnostics.Process object to pipeline
        $returnProcess
    }
        }
        else
        {
            $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
            Write-Warning "$FunctionName failed. Error code: $ErrorCode"
        }

        #Free StartupInfo memory and ProcessInfo memory
        [System.Runtime.InteropServices.Marshal]::FreeHGlobal($StartupInfoPtr)
        $StartupInfoPtr = [Intptr]::Zero
        [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ProcessInfoPtr)
        $ProcessInfoPtr = [IntPtr]::Zero
        [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ProcessNamePtr)
        $ProcessNamePtr = [IntPtr]::Zero

        #Close handle for the token duplicated with DuplicateTokenEx
        $Success = $CloseHandle.Invoke($NewHToken)
        $NewHToken = [IntPtr]::Zero
        if (-not $Success)
        {
            $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
            Write-Warning "CloseHandle failed to close NewHToken. ErrorCode: $ErrorCode"
        }
    }
}