Public/Invoke-CIMPowerShell.ps1

function Invoke-CIMPowerShell {
    <#
        .SYNOPSIS
            Invoke PowerShell scriptblocks over CIM
        .DESCRIPTION
            This function uses the 'Create' method of the Win32_Process class in order to remotely invoke PowerShell
            scriptblocks. In order to return the object from the remote machine, Named Pipes are used, which requires
            Port 445 to be open.
        .PARAMETER PipeName
            The name of the 'NamedPipe' that will be used. By default this is a randomly generated GUID.
        .PARAMETER ScriptBlock
            The scriptblock in which you want to invoke over CIM
        .PARAMETER FunctionsToLoad
            An array of 'Functions' you want to load into the remote command. For example, you might have a custom written
            function to interact with a COM object that you want to load into the remote command. Instead of having an entire
            scriptblock that recreates the function, you can simply specify the function in this parameter.
        .PARAMETER Timeout
            The timeout value before the connection will fail to return data over the NamedPipe
        .PARAMETER CimSession
            CimSession to invoke the remote code on
        .PARAMETER ComputerName
            Computer name to invoke the remote code on
        .EXAMPLE
            C:\PS> Invoke-CimPowerShell -Scriptblock {
                $Client = New-Object -ComObject Microsoft.SMS.Client
                $Client.GetDNSSuffix()
             } -ComputerName Workstation123
                 Return the current DNS Suffix for the MEMCM on Workstation123 using the ComObject and a scriptblock
        .EXAMPLE
            C:\PS> Invoke-CimPowerShell -Scriptblock { Get-CCMDNSSuffix } -ComputerName Workstation123 -FunctionsToLoad Get-CCMDNSSuffix
                 Return the current DNS Suffix for the MEMCM on Workstation123 by loading our existing function that does this work
        .NOTES
            FileName: Invoke-CIMPowerShell.ps1
            Author: Cody Mathis
            Contact: @CodyMathis123
            Created: 2020-01-07
            Updated: 2020-02-12
    #>

    [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
    param
    (
        [Parameter(Mandatory = $false)]
        $PipeName = ([guid]::NewGuid()).Guid.ToString(),
        [Parameter(Mandatory = $true)]
        [scriptblock]$ScriptBlock,
        [Parameter(Mandatory = $false)]
        [string[]]$FunctionsToLoad,
        [Parameter(Mandatory = $false)]
        [ValidateRange(1000, 900000)]
        [int32]$Timeout = 120000,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CimSession')]
        [Microsoft.Management.Infrastructure.CimSession[]]$CimSession,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ComputerName')]
        [Alias('Connection', 'PSComputerName', 'PSConnectionName', 'IPAddress', 'ServerName', 'HostName', 'DNSHostName')]
        [string[]]$ComputerName = $env:ComputerName
    )
    begin {
        $invokeCommandSplat = @{
            ClassName  = 'Win32_Process'
            MethodName = 'Create'
        }

        $SupportFunctions = Convert-FunctionToString -FunctionToConvert 'ConvertTo-CliXml', 'ConvertTo-Base64StringFromObject'
        $HelperFunctions = switch ($PSBoundParameters.ContainsKey('FunctionsToLoad')) {
            $true {
                Convert-FunctionToString -FunctionToConvert $FunctionsToLoad
            }
        }

        $ScriptBlockString = [string]::Format(@'
        {0}
 
        $namedPipe = New-Object System.IO.Pipes.NamedPipeServerStream "{1}", "Out"
        $namedPipe.WaitForConnection()
        $streamWriter = New-Object System.IO.StreamWriter $namedPipe
        $streamWriter.AutoFlush = $true
        $TempResultPreConversion = & {{
            {2}
 
            {3}
        }}
        $results = ConvertTo-Base64StringFromObject -inputObject $TempResultPreConversion
        $streamWriter.WriteLine("$($results)")
        $streamWriter.dispose()
        $namedPipe.dispose()
'@
 , $SupportFunctions, $PipeName, $HelperFunctions, $ScriptBlock)

        $scriptBlockPreEncoded = [scriptblock]::Create($ScriptBlockString)
        $byteCommand = [System.Text.encoding]::UTF8.GetBytes($scriptBlockPreEncoded)
        $encodedScriptBlock = [convert]::ToBase64string($byteCommand)
    }
    process {
        foreach ($Connection in (Get-Variable -Name $PSCmdlet.ParameterSetName -ValueOnly -Scope Local)) {
            $getConnectionInfoSplat = @{
                $PSCmdlet.ParameterSetName = $Connection
            }
            $ConnectionInfo = Get-CCMConnection @getConnectionInfoSplat
            $Computer = $ConnectionInfo.ComputerName
            $connectionSplat = $ConnectionInfo.connectionSplat

            $invokeCommandSplat['Arguments'] = @{
                CommandLine = [string]::Format("powershell.exe (invoke-command ([scriptblock]::Create([system.text.encoding]::UTF8.GetString([System.convert]::FromBase64string('{0}')))))", $encodedScriptBlock)
            }

            $null = Invoke-CimMethod @invokeCommandSplat @connectionSplat

            $namedPipe = New-Object System.IO.Pipes.NamedPipeClientStream $Computer, "$($PipeName)", "In"
            $namedPipe.Connect($timeout)
            $streamReader = New-Object System.IO.StreamReader $namedPipe

            while ($null -ne ($data = $streamReader.ReadLine())) {
                $tempData = $data
            }

            $streamReader.dispose()
            $namedPipe.dispose()

            if (-not [string]::IsNullOrWhiteSpace($tempData)) {
                ConvertFrom-Base64ToObject -inputString $tempData
            }
        }
    }
}