Use-RawPipeline.psm1

<#
 
MIT License
Copyright © 2016 by Gee Law
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
Website: http://psguy.me/modules/Use-RawPipeline
 
#>



<#
.Synopsis
    Uses raw pipeline to invoke a native utility.
 
.Description
    The cmdlet invokes a native utility with raw pipeline enabled.
 
    It outputs a RawPipelineObject if the stdout of the utility should be piped down.
 
.Parameter Command
    Mandatory. The native utility to be invoked.
 
    Position 0.
 
.Parameter ArgumentList
    Optional. The list of arguments to be supplied to the native utility.
 
    Value from remaining parameters.
 
.Parameter AllowNewWindow
    The negation of NoNewWindow.
 
.Parameter RedirectStandardInput
    Optional, value from pipeline. The standard input for the native utility.
 
    If omitted, the standard input is PowerShell host.
 
    Piping a string into this parameter will give file redirection.
 
    This argument is intended to be piped from a prior invocation of Use-RawPipeline.
 
.Parameter RedirectStandardOutput
    Optional. Redirect the output (binarily) into the specified file.
 
    If supplied, the standard output will not be piped down unless PassThru is on.
 
.Parameter PassThru
    Instructs the cmdlet to pipe the standard output down even RedirectStandardOutput is supplied.
 
.Parameter StandardErrorHandler
    Optional. A script block to handle stderr output.
 
    If omitted, stderr output will be Written-Error.
 
.Parameter ForceStandardErrorHandler
    By default, StandardErrorHandler will not be invoked if the length of stderr is zero. If this switch is on, the handler will always be invoked.
 
.Example
    Use-RawPipeline -Command 'git' -ArgumentList @('format-patch', 'HEAD~3') -RedirectStandardOutput 'patch.patch' -PassThru;
 
    This gives the correct output of git to patch.patch and pipes the standard output down.
 
    Note that using "git format-patch HEAD~3 > patch.patch" gives corrupted patch file.
 
.Example
    $ git format-patch HEAD~3 -stdout patch.patch
 
    A succinct version of the prior example.
 
#>

Function Use-RawPipeline
{
    [CmdletBinding(DefaultParameterSetName = 'NoStdinNoArgsPipe', HelpUri = 'https://psguy.me/modules/Use-RawPipeline')]
    [OutputType([PSGuy.UseRawPipeline.RawPipelineObject])]
    [Alias('$')]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'NoStdinPipe')]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'HasStdinPipe')]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'NoStdinRedirect')]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'HasStdinRedirect')]
        [string]$Command,
        [Parameter(ValueFromRemainingArguments = $true, ParameterSetName = 'NoStdinPipe')]
        [Parameter(ValueFromRemainingArguments = $true, ParameterSetName = 'HasStdinPipe')]
        [Parameter(ValueFromRemainingArguments = $true, ParameterSetName = 'NoStdinRedirect')]
        [Parameter(ValueFromRemainingArguments = $true, ParameterSetName = 'HasStdinRedirect')]
        [Alias('args')]
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]$ArgumentList = @(),
        [Parameter(ParameterSetName = 'NoStdinPipe')]
        [Parameter(ParameterSetName = 'HasStdinPipe')]
        [Parameter(ParameterSetName = 'NoStdinRedirect')]
        [Parameter(ParameterSetName = 'HasStdinRedirect')]
        [switch]$AllowNewWindow,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'HasStdinPipe')]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'HasStdinRedirect')]
        [Alias('stdin')]
        [PSGuy.UseRawPipeline.RawPipelineObject]$RedirectStandardInput,
        [Parameter(Mandatory = $true, ParameterSetName = 'NoStdinRedirect')]
        [Parameter(Mandatory = $true, ParameterSetName = 'HasStdinRedirect')]
        [Alias('stdout')]
        [string]$RedirectStandardOutput,
        <# This parameter is not respected if the parameter set is Pipe -- it's always thought to be true. #>
        [Parameter(ParameterSetName = 'NoStdinPipe')]
        [Parameter(ParameterSetName = 'HasStdinPipe')]
        [Parameter(ParameterSetName = 'NoStdinRedirect')]
        [Parameter(ParameterSetName = 'HasStdinRedirect')]
        [switch]$PassThru,
        [Alias('stderr')]
        [ScriptBlock]$StandardErrorHandler =
        {
            $local:stderrContent = Get-Content -LiteralPath $_ -Raw;
            If ($local:stderrContent.Length -ne 0)
            {
                Write-Error -Message $local:stderrContent `
                    -Category ([System.Management.Automation.ErrorCategory]::FromStdErr);
            }
        },
        [Parameter(ParameterSetName = 'NoStdinPipe')]
        [Parameter(ParameterSetName = 'HasStdinPipe')]
        [Parameter(ParameterSetName = 'NoStdinRedirect')]
        [Parameter(ParameterSetName = 'HasStdinRedirect')]
        [switch]$ForceStandardErrorHandler
    )
    Process
    {
        [PSGuy.UseRawPipeline.RawPipelineObject]$local:Stdout = [PSGuy.UseRawPipeline.RawPipelineObject]::new();
        [PSGuy.UseRawPipeline.RawPipelineObject]$local:Stderr = [PSGuy.UseRawPipeline.RawPipelineObject]::new();
        If ($PSCmdlet.ParameterSetName.StartsWith('HasStdin'))
        {
            If ([object]::ReferenceEquals($ArgumentList, $null) -or $ArgumentList.Length -eq 0)
            {
                Start-Process -FilePath $Command `
                    -NoNewWindow:(-not $AllowNewWindow) `
                    -RedirectStandardInput ($RedirectStandardInput.GetFileName()) `
                    -RedirectStandardOutput ($local:Stdout.GetFileName()) `
                    -RedirectStandardError ($local:Stderr.GetFileName()) `
                    -Wait | Out-Null;
            }
            Else
            {
                Start-Process -FilePath $Command -ArgumentList $ArgumentList `
                    -NoNewWindow:(-not $AllowNewWindow) `
                    -RedirectStandardInput ($RedirectStandardInput.GetFileName()) `
                    -RedirectStandardOutput ($local:Stdout.GetFileName()) `
                    -RedirectStandardError ($local:Stderr.GetFileName()) `
                    -Wait | Out-Null;
            }
        }
        Else
        {
            If ([object]::ReferenceEquals($ArgumentList, $null) -or $ArgumentList.Length -eq 0)
            {
                Start-Process -FilePath $Command `
                    -NoNewWindow:(-not $AllowNewWindow) `
                    -RedirectStandardOutput ($local:Stdout.GetFileName()) `
                    -RedirectStandardError ($local:Stderr.GetFileName()) `
                    -Wait | Out-Null;
            }
            Else
            {
                Start-Process -FilePath $Command -ArgumentList $ArgumentList `
                    -NoNewWindow:(-not $AllowNewWindow) `
                    -RedirectStandardOutput ($local:Stdout.GetFileName()) `
                    -RedirectStandardError ($local:Stderr.GetFileName()) `
                    -Wait | Out-Null;
            }
        }
        If ($PSCmdlet.ParameterSetName.EndsWith('Redirect'))
        {
            Copy-Item -LiteralPath ($local:Stdout.GetFileName()) -Destination $RedirectStandardOutput | Out-Null;
        }
        If ($PSCmdlet.ParameterSetName.EndsWith('Pipe') -or $PassThru)
        {
            $local:Stdout;
        }
        If ($ForceStandardErrorHandler -or (Get-Item -LiteralPath ($local:Stderr.GetFileName())).Length -ne 0)
        {
            @($local:Stderr.GetFileName()) | ForEach-Object -Process $StandardErrorHandler | Out-Null;
        }
    }
}

<#
.Synopsis
    Converts the raw pipeline result like Getting-Content from a file.
 
.Description
    The cmdlet converts `RawPipelineObject` obtained by, possibly a series of, invocations of `Use-RawPipeline`. It works like `Get-Content` and you can work with any encoding supported by `Get-Content`, which means `byte[]` included.
 
.Parameter InputObject
    Mandatory, value from pipeline. The raw pipeline result to be converted.
 
.Parameter Delimiter
    Optional. Equivalent to `Delimiter` parameter of `Get-Content`.
 
.Parameter Encoding
    Equivalent to `Encoding` parameter of `Get-Content`.
 
.Parameter Raw
    Equivalent to `Raw` switch of `Get-Content`.
 
.Parameter Force
    Equivalent to `Force` switch of `Get-Content`.
 
.Example
    $result = Use-RawPipeline -Command 'git' `
        -ArgumentList @('format-patch', 'HEAD~3') |
        ConvertFrom-RawPipeline -Encoding Byte;
 
    This stores the raw output of git command into $result (as a byte array).
 
.Example
    $result = $ git format-patch HEAD~3 | ~ -e byte
 
    A succinct version of the prior example.
 
#>

Function ConvertFrom-RawPipeline
{
    [CmdletBinding(DefaultParameterSetName = 'Raw', HelpUri = 'https://psguy.me/modules/Use-RawPipeline')]
    [Alias('~')]
    Param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Raw')]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Delimiter')]
        [Alias('StandardInput', 'stdin', 'StandardOutput', 'stdout')]
        [PSGuy.UseRawPipeline.RawPipelineObject]$InputObject,
        [Parameter(Mandatory = $true, ParameterSetName = 'Delimiter')]
        [AllowNull()]
        [string]$Delimiter = $null,
        [Parameter(ParameterSetName = 'Raw')]
        [Parameter(ParameterSetName = 'Delimiter')]
        [Alias('form')]
        [string]$Encoding = 'Default',
        [Parameter(ParameterSetName = 'Raw')]
        [switch]$Raw,
        [Parameter(ParameterSetName = 'Raw')]
        [Parameter(ParameterSetName = 'Delimiter')]
        [switch]$Force
    )
    Process
    {
        If ($PSCmdlet.ParameterSetName -eq 'Raw')
        {
            Get-Content -LiteralPath ($InputObject.GetFileName()) -Encoding $Encoding -Raw:$Raw -Force:$Force;
        }
        Else
        {
            Get-Content -LiteralPath ($InputObject.GetFileName()) -Delimiter $Delimiter -Encoding $Encoding -Force:$Force;
        }
    }
}

Export-ModuleMember -Function @('Use-RawPipeline', 'ConvertFrom-RawPipeline') -Cmdlet @() -Variable @() -Alias @('$', '~');