Commands/Invoke-eaRunAsScriptBlock.ps1

Function Invoke-eaRunAsScriptBlock {
    <#
    .SYNOPSIS
    Run scriptblock as another user using CreateProcessWithLogonW so it will run with net only credentials
     
    .DESCRIPTION
    This will create another process of PowerShell using the method CreateProcessWithLogonW
    and then run the supplied scriptblock in this separate process. This will allow you to run
    network commands with the supplied credentials but without using up the first hop. Note, local
    resources will NOT use the supplied credentials, only remote calls to other computers will.
     
    .PARAMETER ScriptBlock
    Scriptblock to run
     
    .PARAMETER Credential
    Credentials to run the scriptblock as
     
    .PARAMETER Parameters
    Parameters to pass the scriptblock
     
    .PARAMETER ImportModules
    Modules to import into the scriptblock. Just supply the module name and make sure the current
    session can see the modules
     
    .PARAMETER ImportVariables
    Variables to import. Stored as a Hashtable so the key will be the name of the variable and the value will be
    what you want the variable to be set as
     
    .EXAMPLE
    Invoke-eaRunAsScriptBlock -Credential $MyCredential -ScriptBlock { Get-WMIObject -ComputerName "MyRemoteComputer" -Class Win32_OperatingSystem }
    This will run the Get-WMIObject command on the remote computer.
     
    .NOTES
    .Author: Ryan Ephgrave
     
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [ScriptBlock]$ScriptBlock,
        [Parameter(Mandatory=$true)]
        [pscredential]$Credential,
        [Parameter(Mandatory=$false)]
        [hashtable]$Parameters,
        [Parameter(Mandatory=$false)]
        [string[]]$ImportModules,
        [Parameter(Mandatory=$false)]
        [hashtable]$ImportVariables = @{}
    )
    
    $Runspace = $null
    try{
        $CreateProcessID = Invoke-eaCreateProcessAsUserW -Credential $Credential -FullExePath (Get-Process -Id $PID).Path -Arguments '-NoExit'
        $Runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace(
            (New-Object -TypeName System.Management.Automation.Runspaces.NamedPipeConnectionInfo -ArgumentList @($CreateProcessID)), 
            $Host, 
            ([System.Management.Automation.Runspaces.TypeTable]::LoadDefaultTypeFiles())
        )
        $null = $Runspace.Open()
    }
    catch {
        Write-Warning 'Was not able to create and open the runspace!'
        throw
    }
    $PowerShell = [powershell]::Create()
    $PowerShell.Runspace = $Runspace

    $ModulePaths = @()
    Foreach($ImportModule in $ImportModules){
        if($ModuleToImport = Get-Module -Name $ImportModule -ErrorAction SilentlyContinue){
            $ModulePaths += $ModuleToImport.ModuleBase
        }
    }

    $ImportVariables['eaScriptBlock'] = $ScriptBlock
    $ImportVariables['eaParameters'] = $Parameters
    $ImportVariables['ErrorActionPreference'] = $ErrorActionPreference
    $ImportVariables['VerbosePreference'] = $VerbosePreference
    $ImportVariables['DebugPreference'] = $DebugPreference
    $ImportVariables['ProgressPreference'] = $ProgressPreference
    $ImportVariables['WarningPreference'] = $WarningPreference
    if($PSBoundParameters['Verbose']) {
        $ImportVariables['VerbosePreference'] = [System.Management.Automation.ActionPreference]::Continue
    }
    if($PSBoundParameters['Debug']) {
        $ImportVariables['DebugPreference'] = [System.Management.Automation.ActionPreference]::Continue
    }
    # In the "Process" method, I cannot set the InitialSessionState, so instead both methods use this scriptblock to set initial
    # Modules and variables
    $InitialSessionScriptBlock = {
        Param(
            [string[]]$ModulePaths,
            [hashtable]$ImportVariables
        )
        if($null -ne $ModulePaths){
            foreach($ModulePath in $ModulePaths){
                Import-Module $ModulePath -Force -ErrorAction SilentlyContinue
            }
        }
        if($null -ne $ImportVariables){
            Foreach($key in $ImportVariables.Keys){
                Set-Variable -Name $Key -Value $ImportVariables[$key] -ErrorAction SilentlyContinue
            }
        }
    }
    
    $null = $PowerShell.AddScript($InitialSessionScriptBlock.ToString())
    $null = $PowerShell.AddArgument($ModulePaths)
    $null = $PowerShell.AddArgument($ImportVariables)
    $null = $PowerShell.Invoke()
    
    #endregion

    $ScriptBlockString = $ScriptBlock.ToString()
    $null = $PowerShell.AddScript($ScriptBlockString)
    if($null -ne $Parameters){
        foreach($key in $Parameters.Keys){
            $null = $PowerShell.AddParameter($key, $Parameters[$key])
        }
    }
    try{
        $PSObject = New-Object 'System.Management.Automation.PSDataCollection[psobject]'
        $BeginInvoke = $PowerShell.BeginInvoke($PSObject, $PSObject)
        while($false -eq $BeginInvoke.IsCompleted){
            Start-sleep -Milliseconds 50
            if($PSObject.Count -gt 0){
                $PSObject.ReadAll()
            }
        }
        if($PSObject.Count -gt 0){
            $PSObject.ReadAll()
        }
        if($PowerShell.InvocationStateInfo.State -eq 'Failed'){
            if($null -ne $PowerShell.InvocationStateInfo.Reason) {
                throw $PowerShell.InvocationStateInfo.Reason
            }
        }
    }
    catch [System.Management.Automation.RemoteException] {
        if($PowerShell.InvocationStateInfo.State -eq 'Failed'){
            if($null -ne $PowerShell.InvocationStateInfo.Reason) {
                if($null -ne $PowerShell.InvocationStateInfo.Reason.SerializedRemoteInvocationInfo) {
                    $WarningMessage = ''
                    $MemberProperties = $PowerShell.InvocationStateInfo.Reason.SerializedRemoteInvocationInfo | Get-Member -MemberType Property
                    foreach($Property in $MemberProperties.Name){
                        $WarningMessage += "`"$($Property)`": $($PowerShell.InvocationStateInfo.Reason.SerializedRemoteInvocationInfo.$Property)`n"
                    }
                    Write-Warning -Message "Detailed Error Message:`n$($WarningMessage)"
                }
                throw $PowerShell.InvocationStateInfo.Reason
            }
        }
        else {
            throw
        }
    }
    finally {
        $null = $Runspace.Dispose()
        $null = $PowerShell.Dispose()
        if(Get-Process -Id $CreateProcessID -ErrorAction SilentlyContinue) {
            $null = Stop-Process -Id $CreateProcessID -Force -ErrorAction SilentlyContinue
        }
    }
}