ComPrS.psm1

### --- PUBLIC FUNCTIONS --- ###
#Region - Compress-String.ps1
function Compress-String {
    <#
    .SYNOPSIS
    Accepts string input, compresses it using the DEFLATE algorithm, and returns the bytes encoded in base64
    .DESCRIPTION
    Provides little benefit against short strings but could be useful for large string values, multi-line files, or script blocks.
    .PARAMETER String
    String input for compressing. If multiple strings are passed through the pipeline they will be included in the compression together and output as a single base64 string.
    If piping file content from Get-Content make sure to use the -Raw parameter with Get-Content to avoid sending empty strings.
    .PARAMETER Algorithm
    Optionally specify a compression algorithm. Choices are DEFLATE or Brotli however Brotli is only available on PS Version 6.1 or newer.
    .PARAMETER NoPreserve
    By default when given an array of strings Compress-String will attempt to preserve formatting by joining each string together before compressing it.
    This will bring over new line characters and blank lines so that when later expanded it will look the same. If you'd prefer not to do that supply the -NoPreserve switch
    and each string will be streamed in to the compression writer.
    #>

    [Cmdletbinding()]
    Param (
        [Parameter(ValueFromPipeline)]
        [String[]]$String,
        [ValidateSet('Deflate','Brotli')]
        [String]$Algorithm = $(Set-CompressionAlgorithm),
        [Switch]$NoPreserve
    )

    begin {
        if ($PSVersionTable.PSVersion -lt [Version]'6.1' -and $Algorithm -eq 'Brotli') {
            Write-Warning "PSVersion is <6.1. Brotli compression not available. Reverting to Deflate"
            $Algorithm = 'Deflate'
        } 
        
        Write-Verbose "Algorithm: $Algorithm" 
        $MemoryStream = [System.IO.MemoryStream]::new()
        $CompressionStream = switch ($Algorithm) {
            'Deflate' {
                [System.IO.Compression.DeflateStream]::new($MemoryStream, [System.IO.Compression.CompressionMode]::Compress)
            }
            'Brotli' {
                [System.IO.Compression.BrotliStream]::new($MemoryStream, [System.IO.Compression.BrotliCompressionOptions]@{Quality=11})
            }
        }
        $StreamWriter = [System.IO.StreamWriter]::new($CompressionStream)
        $StringLength = 0
        if ($MyInvocation.ExpectingInput) {
            $PipeLineStrings = [System.Collections.ArrayList]::new()
        }
    }

    process {
        if ($NoPreserve) {
            foreach ($InputString in $String) {
                try {
                    $StreamWriter.Write($InputString)
                    $StringLength += $InputString.Length
                } catch {
                    Write-Error $_
                }
            }
        } else {
            if ($MyInvocation.ExpectingInput) {
                [Void]$PipeLineStrings.Add($String)
            } else {
                try {
                    $InputString = $String | Out-String
                    $StreamWriter.Write($InputString)
                    $StringLength += $InputString.Length
                } catch {
                    Write-Error $_
                }

            }
        }
        
    }

    end {
        if ($MyInvocation.ExpectingInput) {
            try {
                $InputString = $PipelineStrings | Out-String
                $StreamWriter.Write($InputString)
                $StringLength += $InputString.Length
            } catch {
                Write-Error $_
            }
        }
        try {
            $StreamWriter.Close()
            $Base64String = [System.Convert]::ToBase64String($MemoryStream.ToArray())
            $Base64String
        } catch {
            Write-Error $_
        }
        $MemoryStream.Dispose()
        Write-Verbose "Total string input length: $StringLength"
        Write-Verbose "Total string output length: $($Base64String.Length)"
    }
}
#Region - ConvertTo-HereString.ps1
function ConvertTo-HereString {
    <#
    .SYNOPSIS
    Convert a long single string in to a multi line here-string.
    .DESCRIPTION
    When working with Compress-String the output can be very long depending on what you're compressing. If you'd like to store this compressed string in a script it might look nicer
    if it ran vertically instead of just horizontally. This function will turn a single tring in to a multi line here-string of a given width.
    .PARAMETER String
    The long string to convert in to a here string.
    .PARAMETER Width
    optionally specify the character width for the here string. Defaults to 160.
    #>

    [Cmdletbinding()]
    param (
        [String]$String,
        [Int64]$Width = 160
    )

    $Regex = [Regex]"(.{$Width})"

    $StringChunks = $String -split $Regex | Where-Object {$_}
    Write-Verbose "Original string length: $($String.Length)"
    Write-Verbose "Here-string lines: $($StringChunks.count)"
    $StringChunks | Out-String
}
#Region - Expand-String.ps1
function Expand-String {
    <#
    .SYNOPSIS
    Accepts string input, expands it using the DEFLATE algorithm, and returns the bytes converted to string
    .DESCRIPTION
    For expanding the base64 encoded output from the Compress-String function.
    .PARAMETER String
    String input for expanding.
    .PARAMETER Algorithm
    Optionally specify a compression algorithm. Choices are DEFLATE or Brotli however Brotli is only available on PS Version 6.1 or newer.
    #>

    Param (
        [Parameter(Mandatory)]
        [String]$String,
        [ValidateSet('Deflate','Brotli')]
        [String]$Algorithm = $(Set-CompressionAlgorithm)
    )

    if ($PSVersionTable.PSVersion -lt [Version]'6.1' -and $Algorithm -eq 'Brotli') {
        Write-Warning "PSVersion is <6.1. Brotli compression not available. Reverting to Deflate"
        $Algorithm = 'Deflate'
    }

    try {
        $Bytes = [System.Convert]::FromBase64String($String)
        $MemoryStream = [System.IO.MemoryStream]::new()
        $MemoryStream.Write($Bytes, 0, $Bytes.Length)
        [Void]$MemoryStream.Seek(0, 0)
        $DecompressionStream = switch ($Algorithm) {
            'Deflate' {
                [System.IO.Compression.DeflateStream]::new($MemoryStream, [System.IO.Compression.CompressionMode]::Decompress)
            }
            'Brotli' {
                [System.IO.Compression.BrotliStream]::new($MemoryStream, [System.IO.Compression.CompressionMode]::Decompress)
            }
        }
        $StreamReader = [System.IO.StreamReader]::new($DecompressionStream)
        $StreamReader.ReadToEnd()
    } catch {
        Write-Error $_
    }

    $MemoryStream.Dispose()
    $DecompressionStream.Dispose()
    $StreamReader.Dispose() 
}

### --- PRIVATE FUNCTIONS --- ###
#Region - Set-CompressionAlgorithm.ps1
function Set-CompressionAlgorithm {
    param ()

    # detect which version of Powershell, and therefore .NET, to set a default compression algorithm
    if ($PSVersionTable.PSVersion -lt [Version]'6.1') {
        return "Deflate"
    } else {
        return "Brotli"
    }
}