PSAlternativeRemoting.psm1

# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# + Library - Enum
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Enum ARClass {
    ARCimSession = 1
}
Enum ARSessionType {
    CIM = 1
}
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# + Library - Class
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class ARCimSession {

    [System.String]$Computername
    [pscredential]$Credential
    [CimSession]$CimSession

    [System.Void]Create([System.String]$ComputerName, [System.Management.Automation.PSCredential]$Credential) {
        
        $this.ComputerName = $ComputerName
        $this.Credential = $Credential
        
        $WSMan = Test-WSMan -ComputerName $this.ComputerName -ErrorAction SilentlyContinue
        If (($WSMan -ne $null) -and ($WSMan.ProductVersion -match 'Stack: ([3-9]|[1-9][0-9]+)\.[0-9]+')) {
            $splats = @{
                Credential    = $Credential
                ComputerName  = $ComputerName
                SessionOption = (New-CimSessionOption -Protocol Wsman)
            }
        }
        else {
            $splats = @{
                Credential    = $this.Credential
                ComputerName  = $this.ComputerName
                SessionOption = (New-CimSessionOption -Protocol Dcom)
            }
        }
        $this.CimSession = New-CimSession @splats
    }

    [System.Boolean] Test() {
        if ($this.CimSession) {
            if (Get-CimSession -Id $this.CimSession.Id -ErrorAction SilentlyContinue) {
                return $true
            }
            else {
                return $false
            }
        }
        else {
            return $false
        }
    }

    [System.Void] Remove() {
        if ($this.CimSession) {
            Remove-CimSession -Id $this.CimSession.Id
        }
    }

    [System.Void] Execute([System.String]$encodedScriptBlock) {
        if ($this.Test() -eq $true) {
            $splats = @{
                CimSession = $this.CimSession
                ClassName  = "Win32_Process"
                MethodName = "Create"
                Arguments  = @{ CommandLine = "powershell.exe (invoke-command ([scriptblock]::Create([system.text.encoding]::UTF8.GetString([System.convert]::FromBase64string('$($encodedScriptBlock)')))))" }
            }
            Invoke-CimMethod @splats
        }
        {
            throw 'Please create a CIM session before running a command'
        }
    }
}
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# + Function - Private
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
function ConvertFrom-ARBase64ToObject {
    [CmdletBinding()]
    param
    (
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('string')]
        [System.String]$inputString
    )
    $data = [System.convert]::FromBase64String($inputString)
    $memoryStream = [System.Io.MemoryStream]::new()
    $memoryStream.write($data, 0, $data.length)
    $memoryStream.seek(0, 0) | Out-Null
    $streamReader = [System.IO.StreamReader]::new([System.IO.Compression.GZipStream]::new($memoryStream, [System.IO.Compression.CompressionMode]::Decompress))
    $decompressedData = ConvertFrom-ARCliXml ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($($streamReader.readtoend()))))
    return $decompressedData
}
function ConvertFrom-ARCliXml {
    param (
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]$InputObject
    )
    begin {
        $OFS = "`n"
        [String]$xmlString = ""
    }
    process {
        $xmlString += $InputObject
    }
    end {
        $type = [PSObject].Assembly.GetType('System.Management.Automation.Deserializer')
        $ctor = $type.GetConstructor('instance,nonpublic', $null, @([xml.xmlreader]), $null)
        $sr = New-Object System.IO.StringReader $xmlString
        $xr = New-Object System.Xml.XmlTextReader $sr
        $deserializer = $ctor.Invoke($xr)
        $done = $type.GetMethod('Done', [System.Reflection.BindingFlags]'nonpublic,instance')
        while (!$type.InvokeMember("Done", "InvokeMethod,NonPublic,Instance", $null, $deserializer, @())) {
            try {
                $type.InvokeMember("Deserialize", "InvokeMethod,NonPublic,Instance", $null, $deserializer, @())
            }
            catch {
                Write-Warning "Could not deserialize ${string}: $_"
            }
        }
        $xr.Close()
        $sr.Dispose()
    }
}
function ConvertTo-ARBase64StringFromObject {
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('object', 'data', 'input')]
        [psobject]$inputObject
    )
    $tempString = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([management.automation.psserializer]::Serialize($inputObject)))
    $memoryStream = [System.IO.MemoryStream]::new()
    $compressionStream = [System.IO.Compression.GZipStream]::new($memoryStream, [System.io.compression.compressionmode]::Compress)
    $streamWriter = [System.IO.streamwriter]::new($compressionStream)
    $streamWriter.write($tempString)
    $streamWriter.close()
    $compressedData = [System.convert]::ToBase64String($memoryStream.ToArray())
    return $compressedData
}
function ConvertTo-ARCliXml {
    param (
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [PSObject[]]$InputObject
    )
    return [management.automation.psserializer]::Serialize($InputObject)
}
function Get-ARClass {
    [CmdletBinding()]
    [OutputType('System.Object')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ARClass]
        $Name,

        [Parameter(
            Position = 1,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.Boolean]
        $Cache = $true
    )

    begin {
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"
    }

    process {
        if ($Cache -eq $true) {
            $classObject = Get-Variable -Name $Name -Scope 'Script' -ValueOnly -ErrorAction SilentlyContinue
            if ($null -eq $classObject) {
                $classObject = New-ARClass -Name $Name
                Set-Variable -Name $Name -Value $classObject -Scope 'Script'
            }
            return $classObject
        }
        else {
            $classObject = New-ARClass -Name $Name
            return $classObject
        }
    }

    end {
        Write-Verbose "[${functionName}] Complete"
    }
}
function New-ARClass {
    [CmdletBinding()]
    [OutputType('System.Object')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ARClass]
        $Name
    )

    begin {
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"
    }

    process {
        $classObject = New-Object -TypeName $Name
        return $classObject
    }

    end {
        Write-Verbose "[${functionName}] Complete"
    }
}
function New-ARScriptBlockPreEncoded {
    [CmdletBinding()]
    [OutputType('System.String')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [System.Guid]$pipename,

        [Parameter(
            Position = 1,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.ScriptBlock]$ScriptBlock
    )

    begin {
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"
    }

    process {
        $scriptBlockPreEncoded = [scriptblock]{
            #region support functions
            function ConvertTo-ARCliXml {
                param (
                    [Parameter(
                        Position = 0,
                        Mandatory = $true,
                        ValueFromPipeline = $true,
                        ValueFromPipelineByPropertyName = $true
                    )]
                    [ValidateNotNullOrEmpty()]
                    [PSObject[]]$InputObject
                )
                return [management.automation.psserializer]::Serialize($InputObject)
            }
    
            function ConvertTo-ARBase64StringFromObject {
                [CmdletBinding()]
                [OutputType([System.String])]
                param
                (
                    [Parameter(
                        Position = 0,
                        Mandatory = $true,
                        ValueFromPipeline = $true,
                        ValueFromPipelineByPropertyName = $true
                    )]
                    [ValidateNotNullOrEmpty()]
                    [Alias('object', 'data', 'input')]
                    [psobject]$inputObject
                )
                $tempString = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([management.automation.psserializer]::Serialize($inputObject)))
                $memoryStream = [System.IO.MemoryStream]::new()
                $compressionStream = [System.IO.Compression.GZipStream]::new($memoryStream, [System.io.compression.compressionmode]::Compress)
                $streamWriter = [System.IO.streamwriter]::new($compressionStream)
                $streamWriter.write($tempString)
                $streamWriter.close()
                $compressedData = [System.convert]::ToBase64String($memoryStream.ToArray())
                return $compressedData
            }
            #endregion
            
            #region open pipe server to send result
            $namedPipe = [System.IO.Pipes.NamedPipeServerStream]::new("<pipename>", "Out")
            $namedPipe.WaitForConnection()
            $streamWriter = [System.IO.StreamWriter]::new($namedPipe)
            $streamWriter.AutoFlush = $true
            $TempResultPreConversion = Invoke-Command -ScriptBlock { <scriptBlock> } -ErrorAction Stop
            $results = ConvertTo-ARBase64StringFromObject -inputObject $TempResultPreConversion
            $streamWriter.WriteLine("$($results)")
            $streamWriter.dispose()
            $namedPipe.dispose()
            #endregion
        }
        $scriptBlockPreEncoded = $scriptBlockPreEncoded -replace "<pipename>", $PipeName
        $scriptBlockPreEncoded = $scriptBlockPreEncoded -replace "<scriptBlock>", $ScriptBlock
        $byteCommand = [System.Text.encoding]::UTF8.GetBytes($scriptBlockPreEncoded)
        $encodedScriptBlock = [convert]::ToBase64string($byteCommand)
        return $encodedScriptBlock
    }

    end {
        Write-Verbose "[${functionName}] Complete"
    }
}
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# + Function - Public
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
function Connect-ARSession {
    [CmdletBinding()]
    [OutputType('System.Void')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [System.String]$ComputerName,

        [Parameter(
            Position = 1,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]$Credential
    )

    begin {
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"
    }

    process {
        $classObject = Get-ARClass -Name "ARCimSession"
        $classObject.Create($ComputerName, $Credential)
    }

    end {
        Write-Verbose "[${functionName}] Complete"
    }
}
function Disconnect-ARSession {
    [CmdletBinding()]
    [OutputType('System.Void')]
    Param()

    begin {
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"
    }

    process {
        $classObject = Get-ARClass -Name "ARCimSession" 
        $classObject.Remove()
    }

    end {
        Write-Verbose "[${functionName}] Complete"
    }
}
function Invoke-ARCommand {
    [CmdletBinding()]
    [OutputType('System.Void')]
    Param(
        [ValidateNotNullOrEmpty()]
        [Parameter(
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.Guid]$PipeName = [System.Guid]::NewGuid(),

        [Parameter(
            Position = 1,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.ScriptBlock]$ScriptBlock,

        [Parameter(
            Position = 4,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateRange(1000, 900000)]
        [System.UInt32]$Timeout = 120000
    )

    begin {
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"
    }

    process {
        $classObject = Get-ARClass -Name "ARCimSession"
        $encodedScriptBlock = New-ARScriptBlockPreEncoded -pipename $PipeName -ScriptBlock $ScriptBlock
        $classObject.Execute($encodedScriptBlock)

        $namedPipe = New-Object System.IO.Pipes.NamedPipeClientStream $classObject.Computername, "$($PipeName)", "In"
        $namedPipe.connect($timeout)
        $streamReader = New-Object System.IO.StreamReader $namedPipe
        while ($null -ne ($data = $streamReader.ReadLine()))
        {
            $tempData = $data
        }
        
        $streamReader.dispose()
        $namedPipe.dispose()
        ConvertFrom-ARBase64ToObject -inputString $tempData
    }

    end {
        Write-Verbose "[${functionName}] Complete"
    }
}