PSCompression.psm1

using namespace System.IO
using namespace System.IO.Compression
using namespace System.Text
using namespace System.Management.Automation
using namespace System.Collections.Generic
#Region '.\private\functions.ps1' 0
#using namespace System.IO
#using namespace System.IO.Compression
#using namespace System.Text

function GzipFrameworkReader {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [FileInfo] $File,

        [Parameter()]
        [FileStream] $OutStream,

        [Parameter()]
        [switch] $Raw,

        [Parameter(Mandatory)]
        [Encoding] $Encoding
    )

    process {
        try {
            # Credits to jborean - https://github.com/jborean93 for this craziness
            $stream = $File.OpenRead()
            $marker = 0
            $outmem = [MemoryStream]::new()

            while (($b = $stream.ReadByte()) -ne -1) {
                if ($marker -eq 0 -and $b -eq 0x1F) {
                    $marker += 1
                }
                elseif ($marker -eq 1) {
                    if ($b -eq 0x8B) {
                        $marker += 1
                    }
                    else {
                        $marker = 0
                    }
                }
                elseif ($marker -eq 2) {
                    $marker = 0
                    if ($b -eq 0x08) {
                        try {
                            $subStream = $File.OpenRead()
                            $null = $subStream.Seek($stream.Position - 3, [SeekOrigin]::Begin)
                            $gzip = [GZipStream]::new($subStream, [CompressionMode]::Decompress)

                            if($PSBoundParameters.ContainsKey('OutStream')) {
                                $gzip.CopyTo($OutStream)
                                continue
                            }

                            $gzip.CopyTo($outmem)
                        }
                        finally {
                            if($gzip -is [System.IDisposable]) {
                                $gzip.Dispose()
                            }

                            if($subStream -is [System.IDisposable]) {
                                $subStream.Dispose()
                            }
                        }
                    }
                }
            }

            $null = $outmem.Seek(0, [SeekOrigin]::Begin)
            $reader = [StreamReader]::new($outmem, $Encoding)

            if($Raw.IsPresent) {
                return $reader.ReadToEnd()
            }

            while(-not $reader.EndOfStream) {
                $reader.ReadLine()
            }
        }
        finally {
            if($stream -is [IDisposable]) {
                $stream.Dispose()
            }

            if($reader -is [IDisposable]) {
                $reader.Dispose()
            }

            if($outmem -is [IDisposable]) {
                $outmem.Dispose()
            }
        }
    }
}

function GzipCoreReader {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [FileInfo] $File,

        [Parameter()]
        [FileStream] $OutStream,

        [Parameter()]
        [switch] $Raw,

        [Parameter(Mandatory)]
        [Encoding] $Encoding
    )

    process {
        try {
            $inStream = $File.OpenRead()
            $gzip = [GZipStream]::new($inStream, [CompressionMode]::Decompress)

            if($PSBoundParameters.ContainsKey('OutStream')) {
                return $gzip.CopyTo($OutStream)
            }

            $reader = [StreamReader]::new($gzip, $Encoding, $true)

            if($Raw.IsPresent) {
                return $reader.ReadToEnd()
            }

            while(-not $reader.EndOfStream) {
                $reader.ReadLine()
            }
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
        finally {
            if($gzip -is [System.IDisposable]) {
                $gzip.Dispose()
            }

            if($reader -is [System.IDisposable]) {
                $reader.Dispose()
            }

            if($inStream -is [System.IDisposable]) {
                $inStream.Dispose()
            }
        }
    }
}
#EndRegion '.\private\functions.ps1' 148
#Region '.\public\Compress-GZipArchive.ps1' 0
#using namespace System.IO
#using namespace System.IO.Compression
#using namespace System.Text
#using namespace System.Management.Automation

# .ExternalHelp PSCompression-help.xml
function Compress-GzipArchive {
    [CmdletBinding(DefaultParameterSetName = 'Path')]
    [Alias('gziptofile')]
    [OutputType([System.IO.FileInfo])]
    param(
        [Parameter(ParameterSetName = 'PathWithUpdate', Mandatory, Position = 0, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'PathWithForce', Mandatory, Position = 0, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'Path', Mandatory, Position = 0, ValueFromPipeline)]
        [string[]] $Path,

        [Parameter(ParameterSetName = 'LiteralPathWithUpdate', Mandatory, ValueFromPipelineByPropertyName)]
        [Parameter(ParameterSetName = 'LiteralPathWithForce', Mandatory, ValueFromPipelineByPropertyName)]
        [Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('PSPath')]
        [string[]] $LiteralPath,

        [Parameter(ParameterSetName = 'RawBytesWithUpdate', Mandatory, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'RawBytesWithForce', Mandatory, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'RawBytes', Mandatory, ValueFromPipeline)]
        [byte[]] $InputBytes,

        [Parameter(Position = 1, Mandatory)]
        [string] $DestinationPath,

        [Parameter()]
        [CompressionLevel] $CompressionLevel = 'Optimal',

        [Parameter(ParameterSetName = 'RawBytesWithUpdate', Mandatory)]
        [Parameter(ParameterSetName = 'PathWithUpdate', Mandatory)]
        [Parameter(ParameterSetName = 'LiteralPathWithUpdate', Mandatory)]
        [switch] $Update,

        [Parameter(ParameterSetName = 'RawBytesWithForce', Mandatory)]
        [Parameter(ParameterSetName = 'PathWithForce', Mandatory)]
        [Parameter(ParameterSetName = 'LiteralPathWithForce', Mandatory)]
        [switch] $Force,

        [Parameter()]
        [switch] $PassThru
    )

    begin {
        if($Force.IsPresent) {
            $fsMode = [FileMode]::Create
        }
        elseif($Update.IsPresent) {
            $fsMode = [FileMode]::Append
        }
        else {
            $fsMode = [FileMode]::CreateNew
        }

        $expectingInput = $null
    }
    process {
        try {
            if($withPath = -not $PSBoundParameters.ContainsKey('InputBytes')) {
                $isLiteral = $PSBoundParameters.ContainsKey('LiteralPath')
                $paths = $Path

                if($isLiteral) {
                    $paths = $LiteralPath
                }

                $items = $PSCmdlet.InvokeProvider.Item.Get($paths, $true, $isLiteral)
            }

            if(-not $expectingInput) {
                $expectingInput = $true
                $DestinationPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DestinationPath)

                if([Path]::GetExtension($DestinationPath) -ne '.gz') {
                    $DestinationPath = $DestinationPath + '.gz'
                }

                $null = [Directory]::CreateDirectory([Path]::GetDirectoryName($DestinationPath))
                $outStream = [File]::Open($DestinationPath, $fsMode)
                $gzip = [GZipStream]::new($outStream, [CompressionMode]::Compress, $CompressionLevel)

                if(-not $withPath) {
                    $inStream = [MemoryStream]::new($InputBytes)
                }
            }

            foreach($item in $items) {
                try {
                    $inStream = $item.OpenRead()
                    $inStream.CopyTo($gzip)
                }
                catch {
                    $PSCmdlet.WriteError($_)
                }
                finally {
                    if($inStream -is [IDisposable]) {
                        $inStream.Dispose()
                    }
                }
            }
        }
        catch {
            if($gzip -is [System.IDisposable]) {
                $gzip.Dispose()
            }

            if($outStream -is [System.IDisposable]) {
                $outStream.Dispose()
            }

            if($inStream -is [System.IDisposable]) {
                $inStream.Dispose()
            }

            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        try {
            if(-not $withPath) {
                $inStream.Flush()
                $inStream.CopyTo($outStream)
            }
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
        finally {
            if($gzip -is [System.IDisposable]) {
                $gzip.Dispose()
            }

            if($outStream -is [System.IDisposable]) {
                $outStream.Dispose()
            }

            if($inStream -is [System.IDisposable]) {
                $inStream.Dispose()
            }

            if($PassThru.IsPresent) {
                $outStream.Name -as [FileInfo]
            }
        }
    }
}
#EndRegion '.\public\Compress-GZipArchive.ps1' 151
#Region '.\public\Compress-ZipArchive.ps1' 0
#using namespace System.IO
#using namespace System.IO.Compression
#using namespace System.Collections.Generic

# .ExternalHelp PSCompression-help.xml
function Compress-ZipArchive {
    [CmdletBinding(DefaultParameterSetName = 'Path')]
    [Alias('zip', 'ziparchive')]
    [OutputType([System.IO.FileInfo])]
    param(
        [Parameter(ParameterSetName = 'PathWithUpdate', Mandatory, Position = 0, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'PathWithForce', Mandatory, Position = 0, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'Path', Mandatory, Position = 0, ValueFromPipeline)]
        [string[]] $Path,

        [Parameter(ParameterSetName = 'LiteralPathWithUpdate', Mandatory, ValueFromPipelineByPropertyName)]
        [Parameter(ParameterSetName = 'LiteralPathWithForce', Mandatory, ValueFromPipelineByPropertyName)]
        [Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('PSPath')]
        [string[]] $LiteralPath,

        [Parameter(Position = 1, Mandatory)]
        [string] $DestinationPath,

        [Parameter()]
        [CompressionLevel] $CompressionLevel = [CompressionLevel]::Optimal,

        [Parameter(ParameterSetName = 'PathWithUpdate', Mandatory)]
        [Parameter(ParameterSetName = 'LiteralPathWithUpdate', Mandatory)]
        [switch] $Update,

        [Parameter(ParameterSetName = 'PathWithForce', Mandatory)]
        [Parameter(ParameterSetName = 'LiteralPathWithForce', Mandatory)]
        [switch] $Force,

        [Parameter()]
        [switch] $PassThru
    )

    begin {
        $DestinationPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DestinationPath)

        if([Path]::GetExtension($DestinationPath) -ne '.zip') {
            $DestinationPath = $DestinationPath + '.zip'
        }

        $zipMode = [ZipArchiveMode]::Update

        if($Force.IsPresent) {
            $fsMode = [FileMode]::Create
        }
        elseif($Update.IsPresent) {
            $fsMode = [FileMode]::OpenOrCreate
        }
        else {
            $fsMode = [FileMode]::CreateNew
            $zipMode = [ZipArchiveMode]::Create
        }

        $ExpectingInput = $null
    }
    process {
        $isLiteral = $false
        $targetPath = $Path

        if($PSBoundParameters.ContainsKey('LiteralPath')) {
            $isLiteral = $true
            $targetPath = $LiteralPath
        }

        if(-not $ExpectingInput) {
            try {
                $null = [Directory]::CreateDirectory([Path]::GetDirectoryName($DestinationPath))
                $destfs = [File]::Open($DestinationPath, $fsMode)
                $zip = [ZipArchive]::new($destfs, $zipMode)
                $ExpectingInput = $true
            }
            catch {
                $zip, $destfs | ForEach-Object Dispose
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }

        $queue = [Queue[FileSystemInfo]]::new()

        foreach($item in $PSCmdlet.InvokeProvider.Item.Get($targetPath, $true, $isLiteral)) {
            $queue.Enqueue($item)

            $here = $item.Parent.FullName
            if($item -is [FileInfo]) {
                $here = $item.Directory.FullName
            }

            while($queue.Count) {
                try {
                    $current = $queue.Dequeue()
                    if($current -is [DirectoryInfo]) {
                        $current = $current.EnumerateFileSystemInfos()
                    }
                }
                catch {
                    $PSCmdlet.WriteError($_)
                    continue
                }

                foreach($item in $current) {
                    try {
                        if($item.FullName -eq $DestinationPath) {
                            continue
                        }

                        $relative = $item.FullName.Substring($here.Length + 1)

                        if($Update.IsPresent) {
                            $entry = $zip.GetEntry($relative)
                        }

                        if($item -is [DirectoryInfo]) {
                            $queue.Enqueue($item)
                            if(-not $Update.IsPresent -or -not $entry) {
                                $entry = $zip.CreateEntry($relative + '\', $CompressionLevel)
                            }
                            continue
                        }

                        if(-not $Update.IsPresent -or -not $entry) {
                            $entry = $zip.CreateEntry($relative, $CompressionLevel)
                        }

                        $sourcefs = $item.Open([FileMode]::Open, [FileAccess]::Read, [FileShare] 'ReadWrite, Delete')
                        $entryfs = $entry.Open()
                        $sourcefs.CopyTo($entryfs)
                    }
                    catch {
                        $PSCmdlet.WriteError($_)
                    }
                    finally {
                        if($entryfs -is [System.IDisposable]) {
                            $entryfs.Dispose()
                        }
                        if($sourcefs -is [System.IDisposable]) {
                            $sourcefs.Dispose()
                        }
                    }
                }
            }
        }
    }
    end {
        if($zip -is [System.IDisposable]) {
            $zip.Dispose()
        }

        if($destfs -is [System.IDisposable]) {
            $destfs.Dispose()
        }

        if($PassThru.IsPresent) {
            $DestinationPath -as [FileInfo]
        }
    }
}
#EndRegion '.\public\Compress-ZipArchive.ps1' 163
#Region '.\public\ConvertFrom-GzipString.ps1' 0
#using namespace System.IO
#using namespace System.IO.Compression
#using namespace System.Text

# .ExternalHelp PSCompression-help.xml
function ConvertFrom-GzipString {
    [CmdletBinding()]
    [Alias('gzipfromstring')]
    [OutputType([string])]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string[]] $InputObject,

        [Parameter()]
        [PSCompression.EncodingTransformation()]
        [ArgumentCompleter([PSCompression.EncodingCompleter])]
        [Encoding] $Encoding = [UTF8Encoding]::new(),

        [Parameter()]
        [switch] $Raw
    )

    process {
        foreach($string in $InputObject) {
            try {
                $inStream = [MemoryStream]::new([Convert]::FromBase64String($string))
                $gzip = [GZipStream]::new($inStream, [CompressionMode]::Decompress)
                $reader = [StreamReader]::new($gzip, $Encoding, $true)

                if($Raw.IsPresent) {
                    return $reader.ReadToEnd()
                }

                while(-not $reader.EndOfStream) {
                    $reader.ReadLine()
                }
            }
            catch {
                $PSCmdlet.WriteError($_)
            }
            finally {
                if($reader -is [System.IDisposable]) {
                    $reader.Dispose()
                }

                if($gzip -is [System.IDisposable]) {
                    $gzip.Dispose()
                }

                if($inStream -is [System.IDisposable]) {
                    $inStream.Dispose()
                }
            }
        }
    }
}
#EndRegion '.\public\ConvertFrom-GzipString.ps1' 57
#Region '.\public\ConvertTo-GzipString.ps1' 0
#using namespace System.IO
#using namespace System.IO.Compression
#using namespace System.Text

# .ExternalHelp PSCompression-help.xml
function ConvertTo-GzipString {
    [CmdletBinding()]
    [Alias('gziptostring')]
    [OutputType([byte], ParameterSetName = 'ByteStream')]
    [OutputType([string])]
    param(
        [AllowEmptyString()]
        [Parameter(Mandatory, ValueFromPipeline)]
        [string[]] $InputObject,

        [Parameter()]
        [PSCompression.EncodingTransformation()]
        [ArgumentCompleter([PSCompression.EncodingCompleter])]
        [Encoding] $Encoding = [UTF8Encoding]::new(),

        [Parameter()]
        [CompressionLevel] $CompressionLevel = 'Optimal',

        [Parameter(ParameterSetName = 'ByteStream')]
        [Alias('Raw')]
        [switch] $AsByteStream,

        [Parameter()]
        [switch] $NoNewLine
    )

    begin {
        $inStream = [MemoryStream]::new()
        $newLine = $Encoding.GetBytes([Environment]::NewLine)
    }
    process {
        foreach($string in $InputObject) {
            $bytes = $Encoding.GetBytes($string)
            $inStream.Write($bytes, 0, $bytes.Length)
            if($NoNewLine.IsPresent) {
                continue
            }
            $inStream.Write($newLine, 0, $newLine.Length)
        }
    }
    end {
        try {
            $outStream = [MemoryStream]::new()
            $gzip = [GZipStream]::new($outStream, [CompressionMode]::Compress, $CompressionLevel)
            $inStream.Flush()
            $inStream.WriteTo($gzip)
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
        finally {
            if($gzip -is [System.IDisposable]) {
                $gzip.Dispose()
            }

            if($outStream -is [System.IDisposable]) {
                $outStream.Dispose()
            }

            if($inStream -is [System.IDisposable]) {
                $inStream.Dispose()
            }
        }

        try {
            if($AsByteStream.IsPresent) {
                return $PSCmdlet.WriteObject($outStream.ToArray())
            }

            [Convert]::ToBase64String($outStream.ToArray())
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
    }
}
#EndRegion '.\public\ConvertTo-GzipString.ps1' 82
#Region '.\public\Expand-GzipArchive.ps1' 0
#using namespace System.IO
#using namespace System.IO.Compression
#using namespace System.Text

# .ExternalHelp PSCompression-help.xml
function Expand-GzipArchive {
    [CmdletBinding(PositionalBinding = $false)]
    [Alias('gzipfromfile')]
    [OutputType([string], ParameterSetName = ('Path', 'LiteralPath'))]
    [OutputType([System.IO.FileInfo], ParameterSetName = ('PathDestination', 'LiteralPathDestination'))]
    param(
        [Parameter(ParameterSetName = 'Path', Mandatory, Position = 0, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'PathDestination', Mandatory, Position = 0, ValueFromPipeline)]
        [string[]] $Path,

        [Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
        [Parameter(ParameterSetName = 'LiteralPathDestination', Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('PSPath')]
        [string[]] $LiteralPath,

        [Parameter(Mandatory, ParameterSetName = 'PathDestination')]
        [Parameter(Mandatory, ParameterSetName = 'LiteralPathDestination')]
        [string] $DestinationPath,

        [Parameter(ParameterSetName = 'Path')]
        [Parameter(ParameterSetName = 'LiteralPath')]
        [PSCompression.EncodingTransformation()]
        [ArgumentCompleter([PSCompression.EncodingCompleter])]
        [Encoding] $Encoding = [UTF8Encoding]::new(),

        [Parameter(ParameterSetName = 'Path')]
        [Parameter(ParameterSetName = 'LiteralPath')]
        [switch] $Raw,

        [Parameter(ParameterSetName = 'PathDestination')]
        [Parameter(ParameterSetName = 'LiteralPathDestination')]
        [switch] $PassThru
    )

    begin {
        $ExpectingInput = $null
        $params = @{
            Raw      = $Raw.IsPresent
            Encoding = $Encoding
        }
    }
    process {
        try {
            $isLiteral = $PSBoundParameters.ContainsKey('LiteralPath')
            $paths = $Path

            if($isLiteral) {
                $paths = $LiteralPath
            }

            $items = $PSCmdlet.InvokeProvider.Item.Get($paths, $true, $isLiteral)

            if(-not $ExpectingInput -and $PSBoundParameters.ContainsKey('DestinationPath')) {
                $ExpectingInput = $true
                $DestinationPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DestinationPath)
                $null = [Directory]::CreateDirectory([Path]::GetDirectoryName($DestinationPath))
                $params['OutStream'] = [File]::Open($DestinationPath, [FileMode]::Append)
            }

            # Had to do this to read appended Gzip content in .NET Framework...
            if($IsCoreCLR) {
                return $items | GzipCoreReader @params
            }

            $items | GzipFrameworkReader @params
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
        finally {
            if($params['OutStream'] -is [IDisposable]) {
                $params['OutStream'].Dispose()
            }

            if($PassThru.IsPresent) {
                $params['OutStream'].Name -as [FileInfo]
            }
        }
    }
}
#EndRegion '.\public\Expand-GzipArchive.ps1' 86
#Region 'PREFIX' 0
[System.IO.Path]::Combine($PSScriptRoot, 'bin', 'netstandard2.0', 'PSCompression.dll') |
    Import-Module

#EndRegion 'PREFIX'