BaseEncoder.psm1

#Requires -Version 3.0
Set-StrictMode -Version Latest
Function Get-RandomByteArray() {
    <#
    .SYNOPSIS
        A PowerShell function to generate an arbitrary number of cryptographically safe random
        bytes.
 
    .DESCRIPTION
        Takes a [System.UInt32] parameter specifying the number of bytes to generate and invokes
        [Security.Cryptography.RNGCryptoServiceProvider]::GetBytes() to return a [System.Byte[]]
        object containing that number of crypto-quality random bytes.
 
    .PARAMETER NumBytes
        [System.UInt32] object that specifies the number of random bytes to generate. Must be
        between 1 and [System.UInt32]::MaxValue (4294967295).
 
    .INPUTS
        Single [System.UInt32] parameter indicating the number of bytes to generate.
 
    .OUTPUTS
        [System.Byte[]] array containing the requested number of bytes that have been randomly
        generated by [Security.Cryptography.RNGCryptoServiceProvider]::GetBytes().
 
    .EXAMPLE
        Get 64 random bytes:
            $Bytes = Get-RandomByteArray 64
 
    .NOTES
        RNGCryptoServiceProvider is used over Get-Random because - when invoked inside a loop or
        other rapid iteration like the pipeline - Get-Random tends produce duplicate output on
        fast systems. This is because by default Get-Random uses [Environment]::TickCount (number
        of milliseconds elapsed since system startup) as a seed and repeated invocation over a
        loop on a fast system (i.e. sub-1ms execution time) effectively generates multiple calls
        to the same RNG using identical seeds; hence, identical outputs. RNGCryptoServiceProvider
        does not have this problem, therefore RNGCryptoServiceProvider was chosen to make the
        script safe for situations where random bytes might be generated via a loop or through
        pipeline operations.
    #>

    [CmdletBinding(
        SupportsShouldProcess=$False,
        DefaultParameterSetName="ByteOutput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="ByteOutput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Number of random bytes to generate, between 1 and [System.UInt32]::MaxValue (4294967295).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateRange(1,[System.UInt32]::MaxValue)]
        [Alias('Value','Number')]
        [System.UInt32]$NumBytes
    )
    [System.Byte[]]$Buffer = New-Object -TypeName System.Byte[] $NumBytes
    $RNG = [Security.Cryptography.RNGCryptoServiceProvider]::Create()
    $RNG.GetBytes($Buffer)
    Return ($Buffer)
}

Function Get-CompressedByteArray {
    <#
    .SYNOPSIS
        A PowerShell function to apply GZip compression to a byte array.
 
    .DESCRIPTION
        Takes a [System.Byte[]] array as input and returns a [System.Byte[]] array containing a
        GZip-compressed version of the input bytes.
 
    .PARAMETER ByteArray
        [System.Byte[]] object containing arbitrary bytes on which to apply compression.
 
    .PARAMETER CompressionLevel
        [System.IO.Compression.CompressionLevel] enum that determines the level of compression;
        valid values are "Fastest", "NoCompression" and "Optimal". By default if no
        CompressionLevel is specified, "Optimal" will be used.
 
    .INPUTS
        [System.Byte[]] object.
 
    .OUTPUTS
        [System.Byte[]] object.
 
    .EXAMPLE
        GZip compress a byte array using Optimal compression level:
            $GZBytes = Get-CompressedByteArray($Bytes)
 
    .EXAMPLE
        GZip compress a byte array using Fastest compression level:
            $GZBytes = Get-CompressedByteArray($Bytes) -CompressionLevel Fastest
 
    .NOTES
        This function provides a simple wrapper for the .NET System.IO.Compression.GzipStream
        class - no spectacular hand-rolled compression code here.
    #>

    [CmdletBinding(
        SupportsShouldProcess=$False,
        DefaultParameterSetName="ByteInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Byte array to GZip compress'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Data','Bytes')]
            [System.Byte[]]$ByteArray,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$False,
            Position=1,
            HelpMessage='GZip compression level, valid values are "Fastest", "NoCompression" and "Optimal"; default "Optimal"'
        )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Compression.CompressionLevel]$CompressionLevel
    )
    PROCESS {
        [System.IO.MemoryStream]$OutputStream = New-Object -TypeName System.IO.MemoryStream
        [System.Object]$GZStream = New-Object -TypeName System.IO.Compression.GzipStream $OutputStream,([IO.Compression.CompressionMode]::Compress),$CompressionLevel
        Try {
            $GZStream.Write($ByteArray,0,$ByteArray.Length)
            [System.Byte[]]$ResultObject = $OutputStream.ToArray()
            Return ($ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $GZStream.Close()
            $GZStream.Dispose()
            $OutputStream.Close()
            $OutputStream.Dispose()
        }
    }
}

Function Get-DecompressedByteArray {
    <#
    .SYNOPSIS
        A PowerShell function decompress GZip-compressed byte arrays.
 
    .DESCRIPTION
        Takes a [System.Byte[]] array containing a GZip-compressed data as input and returns a
        [System.Byte[]] array of the corresponding decompressed data.
 
    .PARAMETER ByteArray
        [System.Byte[]] object containing arbitrary bytes to decompress.
 
    .INPUTS
        [System.Byte[]] object.
 
    .OUTPUTS
        [System.Byte[]] object.
 
    .EXAMPLE
        Decompress a byte array::
            $Bytes = Get-DecompressedByteArray($GZBytes)
 
    .NOTES
        The function will check the first two bytes of the input object for the GZip "magic
        number" 0x1F,0x8B - if the magic number is not present the function assumes the byte
        array is not compressed and returns the original input object without modification.
    #>

    [CmdletBinding(
        SupportsShouldProcess=$False,
        DefaultParameterSetName="ByteInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Byte array to GZip decompress'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Data','Bytes')]
        [System.Byte[]]$ByteArray
    )
    PROCESS {
        If (Compare-Object -ReferenceObject ([System.Byte[]]$ByteArray[0,1]) -DifferenceObject ([System.Byte[]](0x1F,0x8B))) {
            Write-Warning "Data stream is missing GZip magic number, bytes are probably not compressed."
            Return ($ByteArray) #TODO: determine best way to handle missing GZip header - returning the original byte array isn't elegant
        }
        [System.Object]$InputStream = New-Object -TypeName System.IO.MemoryStream(,$ByteArray)
        [System.Object]$OutputStream = New-Object -TypeName System.IO.MemoryStream
        [System.Object]$GZStream = New-Object -TypeName System.IO.Compression.GzipStream $InputStream,([IO.Compression.CompressionMode]::Decompress)
        Try {
            $GZStream.CopyTo($OutputStream)
            [System.Byte[]]$ResultObject = $OutputStream.ToArray()
            Return ($ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $GZStream.Close()
            $GZStream.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $OutputStream.Close()
            $OutputStream.Dispose()
        }
    }
}

Function ConvertTo-Ascii85() {
    <#
    .SYNOPSIS
        A PowerShell function to convert arbitrary data into an Ascii85 encoded string.
 
    .DESCRIPTION
        Takes a string, byte array or file object as input and returns a Ascii85 encoded string
        or location of the Ascii85 result output file object. The default input and output type
        if positional parameters are used is [System.String].
 
    .PARAMETER Bytes
        [System.Byte[]] object containing a byte array to be encoded as Ascii85 string. Accepts
        pipeline input.
 
    .PARAMETER String
        [System.String] object containing plain text to be encoded as Ascii85 string. Accepts
        pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be converted to
        Ascii85 string and output as a new file; output files are written as UTF-8 no BOM.
        Accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing Ascii85 encoded data from the input file. Can be used with any input
        mode (Bytes, String, or InFile).
 
    .PARAMETER Unormatted
        By default the function adds line breaks to output string every 64 characters and adds
        A85 start/end delimiters (<~ / ~>); this parameter suppresses formatting and returns
        the Ascii85 string result as a single, unbroken string object with no delimiters.
 
    .PARAMETER AutoSave
        [System.String] containing a new file extension to use to automatically generate files.
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter, for example -AutoSave
        "A85" would create the OutFile name <InFile>.A85. Useful if piping the output of
        Get-ChildItem to the function to convert files as a bulk operation. Cannot be used with
        input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw string output instead of a
        PSObject. This parameter limits the functionality of the pipeline but is convenient for
        simple encoding operations.
 
    .INPUTS
        Any single object or collection of strings, bytes, or files (such as those from
        Get-ChildItem) can be piped to the function for processing into Ascii85 encoded data.
 
    .OUTPUTS
        The output is always an ASCII string; if any input method is used with -OutFile or
        -InFile is used with -AutoSave, the output is a [System.IO.FileInfo] object containing
        details of a UTF8 no BOM text file with the Ascii85 encoded data as contents. Unless
        -Unformatted is specified, the console or file string data is formatted with start and
        end delimiters (<~ / ~>) and line breaks are added every 64 character. If -Unformatted
        is present, the output is a [System.String] with no line breaks or delimiters. If
        outputting to the console, the string is returned within a PSObject with a single
        member named Ascii85EncodedData as [System.String]; if -Raw is specified, the
        [System.String] is not wrapped in a PSObject and returned directly. This means that
        output using -Raw cannot easily use the pipeline, but makes it a useful option for
        quick encoding operations. The -Verbose parameter will return the function's total
        execution time.
 
    .EXAMPLE
        Convert a string directly into Ascii85:
            ConvertTo-Ascii85 "This is a plaintext string"
 
    .EXAMPLE
        Pipe an object (string or array of strings, byte array or array of byte arrays, file
        info or array of file info objects) to the function for encoding as Ascii85:
            $MyObject | ConvertTo-Ascii85
 
    .EXAMPLE
        Convert a byte array to Ascii85 and return the output with block formatting and not
        wrapped in a PSObject (as a raw [System.String]):
            ConvertTo-Ascii85 -ByteArray $Bytes -Raw
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Ascii85
        encoded file with block formatting for each input file:
            Get-ChildItem C:\Text\*.txt | ConvertTo-Ascii85 -AutoSave "A85"
 
    .EXAMPLE
        Use file based input to Ascii85 encode an input file and output the results as new file
        C:\Text\Ascii85.txt with no line breaks or delimiters:
            ConvertTo-Ascii85 -File C:\Text\file.txt -OutFile C:\Text\Ascii85.txt -Unformatted
 
    .NOTES
        Ascii85 is similar but slightly different than the Base85 encoding scheme from which it
        is derived. More info on Ascii85 can be found here: https://en.wikipedia.org/wiki/Ascii85
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Byte array to Ascii85 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('ByteArray','Data')]
        [System.Byte[]]$Bytes,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='String to Ascii85 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Plaintext','Text')]
        [System.String]$String,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to Ascii85 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName','File')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$False,
            Position=1,
            HelpMessage='Output result as UTF8-NoBOM encoded text to specified file instead of writing to console.'
        )]
        [Parameter(
            ParameterSetName="ByteInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Do not format output string using header, footer or line breaks.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Unformatted,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning a string instead of a file, return a raw string instead of PSObject; applies to both console and file output modes.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
            If ($Raw) {
                Write-Warning "File output mode specified; Parameter '-Raw' will be ignored."
            }
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "ByteInput" {
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,$Bytes)
                Break
            }
            "StringInput" {
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,[System.Text.Encoding]::ASCII.GetBytes($String))
                Break
            }
            "FileInput" {
                [System.IO.Stream]$InputStream  = [System.IO.File]::Open($InFile.FullName,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::ReadWrite)
                Break
            }
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        [System.Object]$BinaryReader = New-Object -TypeName System.IO.BinaryReader($InputStream)
        [System.Object]$Ascii85Output = New-Object -TypeName System.Text.StringBuilder
        If (-Not $Unformatted) {
            [void]$Ascii85Output.Append("<~")
            [System.UInt16]$LineLen = 2
        }
        Try {
            While ([System.Byte[]]$BytesRead = $BinaryReader.ReadBytes(4)) {
                [System.UInt16]$ByteLength = $BytesRead.Length
                If ($ByteLength -lt 4) {
                    [System.Byte[]]$WorkingBytes = ,0x00 * 4
                    [System.Buffer]::BlockCopy($BytesRead,0,$WorkingBytes,0,$ByteLength)
                    [System.Array]::Resize([ref]$BytesRead,4)
                    [System.Buffer]::BlockCopy($WorkingBytes,0,$BytesRead,0,4)
                }
                If ([BitConverter]::IsLittleEndian) {
                    [Array]::Reverse($BytesRead)
                }
                [System.Char[]]$A85Chars = ,0x00 * 5
                [System.UInt32]$Sum = [BitConverter]::ToUInt32($BytesRead,0)
                [System.UInt16]$ByteLen = [Math]::Ceiling(($ByteLength / 4) * 5)
                If ($ByteLength -eq 4 -And $Sum -eq 0) {
                    [System.Char[]]$A85Chunk = "z"
                } Else {
                    [System.Char[]]$A85Chunk = ,0x00 * $ByteLen
                    $A85Chars[0] = [System.Text.Encoding]::ASCII.GetChars([Math]::Floor(($Sum / [Math]::Pow(85,4)) % 85) + 33)[0]
                    $A85Chars[1] = [System.Text.Encoding]::ASCII.GetChars([Math]::Floor(($Sum / [Math]::Pow(85,3)) % 85) + 33)[0]
                    $A85Chars[2] = [System.Text.Encoding]::ASCII.GetChars([Math]::Floor(($Sum / [Math]::Pow(85,2)) % 85) + 33)[0]
                    $A85Chars[3] = [System.Text.Encoding]::ASCII.GetChars([Math]::Floor(($Sum / 85) % 85) + 33)[0]
                    $A85Chars[4] = [System.Text.Encoding]::ASCII.GetChars([Math]::Floor($Sum % 85) + 33)[0]
                    [System.Array]::Copy($A85Chars,$A85Chunk,$ByteLen)
                }
                ForEach ($A85Char in $A85Chunk) {
                    [void]$Ascii85Output.Append($A85Char)
                    If (-Not $Unformatted) {
                        If ($LineLen -eq 64) {
                            [void]$Ascii85Output.Append("`r`n")
                            $LineLen = 0
                        } Else {
                            $LineLen++
                        }
                    }
                }
            }
            If (-Not $Unformatted) {
                If ($LineLen -le 62) {
                    [void]$Ascii85Output.Append("~>")
                } Else {
                    [void]$Ascii85Output.Append("~`r`n>")
                }
            }
            [System.String]$Ascii85Result = $Ascii85Output.ToString()
            $Ascii85ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllLines($OutFile.FullName,$Ascii85Result,(New-Object -TypeName System.Text.UTF8Encoding $False))
                $Ascii85ResultObject = $OutFile
            } Else {
                If ($Raw) {
                    $Ascii85ResultObject = $Ascii85Result
                } Else {
                    Add-Member -InputObject $Ascii85ResultObject -MemberType 'NoteProperty' -Name 'Ascii85EncodedData' -Value $Ascii85Result
                }
            }
            Return ($Ascii85ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $BinaryReader.Close()
            $BinaryReader.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $Timer.Stop()
            [System.String]$TimeLapse = "Ascii85 encode completed in $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertFrom-Ascii85() {
   <#
    .SYNOPSIS
        A PowerShell function to convert an arbitrary Ascii85 encoded string into a byte array
        or binary file.
 
    .DESCRIPTION
        Takes a string of Ascii85 formatted data and decodes into the original ASCII string or
        byte array. Input includes a Ascii85 string or a file containing an Ascii85 string.
        Both formatted (line breaks) and unformatted Ascii85 data are supported. The default
        input and output type if positional parameters are used is [System.String]; it is also
        possible to write a binary file from the Ascii85 input using -OutFile.
 
    .PARAMETER Ascii85EncodedString
        [System.String] object containing Ascii85 encoded data. Accepts pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be loaded as a
    string object and decoded from Ascii85 string; accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing Ascii85 decoded data from the input file. Can be used with any input
        mode (Bytes, String, or InFile); file content will be raw decoded bytes.
 
    .PARAMETER OutBytes
        Return the decoded data as [System.Byte[]] to the console instead of the default ASCII
        string.
 
    .PARAMETER AutoSave
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter; for example,
        -AutoSave "BIN" will result in OutFile name <InFile>.bin. Useful if piping the output
        of Get-ChildItem to the function to convert files as a bulk operation. Cannot be used
        with input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw output instead of a
        PSObject. Depending on the parameters used, the return object could be of type
        [System.String] or [System.Byte[]].
 
    .INPUTS
        Any single object, array or collection of strings or files (such as those from
        Get-ChildItem) can be piped to the function for processing from Ascii85 encoded data.
        Input data from file is always processed as ASCII text regardless of source file text
        encoding.
 
    .OUTPUTS
        In the case of direct string input, a [System.String] containing the decoded data as
        ASCII text is returned within a PSObject with a single member named Ascii85DecodedData.
        If any input method is used with -OutFile or -InFile is used with -AutoSave, the output
        is a [System.IO.FileInfo] object containing details of a binary file with the Ascii85
        decoded data as contents. If -OutBytes is specified, data is returned to the console as
        [System.Byte[]] wrapped in a PSObject. If -Raw is specified, the [System.String] or
        [System.Byte[]] is not wrapped in a PSObject and is returned directly. This means that
        output using -Raw cannot easily use the pipeline. The -Verbose parameter will return
        the function's total execution time.
 
    .EXAMPLE
        Convert a Ascii85 string to a decoded byte array:
            [System.Byte[]]$Bytes = ConvertFrom-Ascii85 "6uQRXD.RU,@<?4%DBS" -OutBytes -Raw
 
    .EXAMPLE
        Decode a Ascii85 encoded string:
            ConvertFrom-Ascii85 -Ascii85EncodedString "6uQRXD.RU,@<?4%DBS"
 
    .EXAMPLE
        Pipe an object (string or array of strings, file info or array of file info objects) to
        the function for decoding from Ascii85:
            $MyObject | ConvertFrom-Ascii85
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Ascii85
        decoded file for each input file:
            Get-ChildItem C:\Text\*.A85 | ConvertFrom-Ascii85 -AutoSave "BIN"
 
    .EXAMPLE
        Use file based input to decode an input file and output the results as new file
        C:\Text\file.txt:
            ConvertFrom-Ascii85 -File C:\Text\file.A85 -OutFile C:\Text\file.txt
 
    .NOTES
        Ascii85 is similar but slightly different than the Base85 encoding scheme from which it
        is derived. More info on Ascii85 can be found here:
        https://en.wikipedia.org/wiki/Ascii85. Because Ascii85 encoded text uses some
        PowerSehll reserved characters in its output (notably ` and $), when using string input
        it is sometimes necessary to escape certain characters in order for input to be read
        correctly. File input does not suffer from this limitation.
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Input A85encoded string.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('String','Plaintext','Text','A85EncodedData')]
        [System.String]$Ascii85EncodedString,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to A85decode'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            Position=1,
            HelpMessage='Path to output file when decoding in file mode.'
        )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Output decoded data as raw bytes instead of ASCII text.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$OutBytes,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Determine the output file name from the A85encoded data header; only applies to file mode. Can be mixed with -AutoSave or -OutFile when using pipeline to extract the file name if it exists, and rely on -AutoSave / -OutFile if it does not.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Auto,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning to console, return a raw byte array instead of PSObject.'
        )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        If ($PSBoundParameters.ContainsKey("AutoSave") -and $PSCmdlet.ParameterSetName -ne "FileInput") {
            Write-Error "-AutoSave can only be used in file input mode." -ErrorAction Stop
        }
        [System.String]$NON_A85_Pattern = "[^\x21-\x75]"
        [System.String]$OFS = ""
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "StringInput" {
                [System.String]$A85String = $Ascii85EncodedString.Replace("<~","").Replace("~>","").Replace(" ","").Replace("`r`n","").Replace("`n","")
                Break
            }
            "FileInput" {
                [System.String]$A85String = ([System.IO.File]::ReadAllText($InFile.FullName)).Replace("<~","").Replace("~>","").Replace(" ","").Replace("`r`n","").Replace("`n","")
                Break
            }
        }
        If ($A85String -match $NON_A85_Pattern) {
            Throw "Invalid Ascii85 data detected in input stream."
        }
        [System.Object]$InputStream = New-Object -TypeName System.IO.MemoryStream([System.Text.Encoding]::ASCII.GetBytes($A85String),0,$A85String.Length)
        [System.Object]$BinaryReader = New-Object -TypeName System.IO.BinaryReader($InputStream)
        [System.Object]$OutputStream = New-Object -TypeName System.IO.MemoryStream
        [System.Object]$BinaryWriter = New-Object -TypeName System.IO.BinaryWriter($OutputStream)
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        Try {
            While ([System.Byte[]]$BytesRead = $BinaryReader.ReadBytes(5)) {
                [System.Boolean]$AtEnd = ($BinaryReader.BaseStream.Length -eq $BinaryReader.BaseStream.Position)
                [System.UInt16]$ByteLength = $BytesRead.Length
                If ($ByteLength -lt 5) {
                    [System.Byte[]]$WorkingBytes = ,0x75 * 5
                    [System.Buffer]::BlockCopy($BytesRead,0,$WorkingBytes,0,$ByteLength)
                    [System.Array]::Resize([ref]$BytesRead,5)
                    [System.Buffer]::BlockCopy($WorkingBytes,0,$BytesRead,0,5)
                }
                [System.UInt16]$ByteLen = [Math]::Floor(($ByteLength * 4) / 5)
                [System.Byte[]]$BinChunk = ,0x00 * $ByteLen
                If ($BytesRead[0] -eq 0x7A) {
                    $BinaryWriter.Write($BinChunk)
                    If (-Not $AtEnd) {
                        $BinaryReader.BaseStream.Position = $BinaryReader.BaseStream.Position - 4
                        Continue
                    }
                } Else {
                    [System.UInt32]$Sum = 0
                    $Sum += ($BytesRead[0] - 33) * [Math]::Pow(85,4)
                    $Sum += ($BytesRead[1] - 33) * [Math]::Pow(85,3)
                    $Sum += ($BytesRead[2] - 33) * [Math]::Pow(85,2)
                    $Sum += ($BytesRead[3] - 33) * 85
                    $Sum += ($BytesRead[4] - 33)
                    [System.Byte[]]$A85Bytes = [System.BitConverter]::GetBytes($Sum)
                    If ([BitConverter]::IsLittleEndian) {
                        [Array]::Reverse($A85Bytes)
                    }
                    [System.Buffer]::BlockCopy($A85Bytes,0,$BinChunk,0,$ByteLen)
                    $BinaryWriter.Write($BinChunk)
                }
            }
            $ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllBytes($OutFile,($OutputStream.ToArray()))
                $ResultObject = $OutFile
            } Else {
                If ($OutBytes -and $Raw) {
                    $ResultObject = $OutputStream.ToArray()
                } ElseIf ($OutBytes) {
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'Ascii85DecodedData' -Value $OutputStream.ToArray()
                } ElseIf ($Raw) {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString(($OutputStream.ToArray()))
                    $ResultObject = $Results
                } Else {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString(($OutputStream.ToArray()))
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'AsciiDecodedString' -Value $Results
                }
            }
            Return ($ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $BinaryReader.Close()
            $BinaryReader.Dispose()
            $BinaryWriter.Close()
            $BinaryWriter.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $OutputStream.Close()
            $OutputStream.Dispose()
            $Timer.Stop()
            [System.String]$TimeLapse = "Ascii85 decode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertTo-Base16() {
    <#
    .SYNOPSIS
        A PowerShell function to convert arbitrary data into a Base16 (hexadecimal) encoded
        string.
 
    .DESCRIPTION
        Takes a string, byte array or file object as input and returns a Base16 encoded string
        or location of the Base16 result output file object. The default input and output type
        if positional parameters are used is [System.String].
 
    .PARAMETER Bytes
        [System.Byte[]] object containing a byte array to be encoded as Base16 string. Accepts
        pipeline input.
 
    .PARAMETER String
        [System.String] object containing plain text to be encoded as Base16 string. Accepts
        pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be converted to
        Base16 string and output as a new file; output files are written as UTF-8 no BOM.
        Accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing Base16 encoded data from the input file. Can be used with any input mode
        (Bytes, String, or InFile).
 
    .PARAMETER Unormatted
        By default the function adds line breaks to output string every 64 characters and block
        style header / footer (-----BEGIN BASE16 ENCODED DATA-----/-----END BASE16 ENCODED
        DATA-----); this parameter suppresses formatting and returns the Base16 string result as
        a single, unbroken string object with no header or footer.
 
    .PARAMETER AutoSave
        [System.String] containing a new file extension to use to automatically generate files.
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter, for example -AutoSave
        "B16" would create the OutFile name <InFile>.B16. Useful if piping the output of
        Get-ChildItem to the function to convert files as a bulk operation. Cannot be used with
        input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw string output instead of a
        PSObject. This parameter limits the functionality of the pipeline but is convenient for
        simple encoding operations.
 
    .INPUTS
        Any single object or collection of strings, bytes, or files (such as those from
        Get-ChildItem) can be piped to the function for processing into Base16 encoded data.
 
    .OUTPUTS
        The output is always an ASCII string; if any input method is used with -OutFile or
        -InFile is used with -AutoSave, the output is a [System.IO.FileInfo] object containing
        details of a UTF8 no BOM text file with the Base16 encoded data as contents. Unless
        -Unformatted is specified, the console or file string data is formatted with block
        headers (-----BEGIN BASE16 ENCODED DATA-----/-----END BASE16 ENCODED DATA-----) and line
        breaks are added every 64 character. If -Unformatted is present, the output is a
        [System.String] with no line breaks or header / footer. If outputting to the console,
        the string is returned within a PSObject with a single member named Base16EncodedData as
        [System.String]; if -Raw is specified, the [System.String] is not wrapped in a PSObject
        and returned directly. This means that output using -Raw cannot easily use the pipeline,
        but makes it a useful option for quick encoding operations. The -Verbose parameter will
        return the function's total execution time.
 
    .EXAMPLE
        Convert a string directly into Base16:
            ConvertTo-Base16 "This is a plaintext string"
 
    .EXAMPLE
        Pipe an object (string or array of strings, byte array or array of byte arrays, file
        info or array of file info objects) to the function for encoding as Base16:
            $MyObject | ConvertTo-Base16
 
    .EXAMPLE
        Convert a byte array to Base16 and return the output with block formatting and not
        wrapped in a PSObject (as a raw [System.String]):
            ConvertTo-Base16 -ByteArray $Bytes -Raw
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Base16
        encoded file with block formatting for each input file:
            Get-ChildItem C:\Text\*.txt | ConvertTo-Base16 -AutoSave "B16"
 
    .EXAMPLE
        Use file based input to Base16 encode an input file and output the results as new file
        C:\Text\Base16.txt with no line breaks or header / footer:
            ConvertTo-Base16 -File C:\Text\file.txt -OutFile C:\Text\Base16.txt -Unformatted
 
    .NOTES
        This function uses the built-in ToString("X2") method for converting arbitrary bytes
        into hexadecimal string; more information on the Base16, Base16, and Base16 Data
        Encoding standard can be found on the IETF web site: https://tools.ietf.org/html/rfc4648
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Byte array to Base16 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('ByteArray','Data')]
        [System.Byte[]]$Bytes,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='String to Base16 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Plaintext','Text')]
        [System.String]$String,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to Base16 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName','File')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$False,
            Position=1,
            HelpMessage='Output result to specified file as UTF8-NoBOM text instead of console.'
        )]
        [Parameter(
            ParameterSetName="ByteInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Do not format output string using header/footer and line breaks.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Unformatted,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning a string instead of a file, return a raw string instead of PSObject; applies to both console and file output modes.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        [System.String]$B16Header = "-----BEGIN BASE16 ENCODED DATA-----"
        [System.String]$B16Footer = "-----END BASE16 ENCODED DATA-----"
        [System.String]$OFS = ""
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
            If ($Raw) {
                Write-Warning "File output mode specified; Parameter '-Raw' will be ignored."
            }
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "ByteInput" {
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,$Bytes)
                Break
            }
            "StringInput" {
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,[System.Text.Encoding]::ASCII.GetBytes($String))
                Break
            }
            "FileInput" {
                [System.IO.Stream]$InputStream  = [System.IO.File]::Open($InFile.FullName,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::ReadWrite)
                Break
            }
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        [System.Object]$BinaryReader = New-Object -TypeName System.IO.BinaryReader($InputStream)
        [System.Object]$Base16Output = New-Object -TypeName System.Text.StringBuilder
        If (-Not $Unformatted) {
            [void]$Base16Output.Append("$($B16Header)`r`n")
        }
        [System.String]$B16Line = [String]::Empty
        Try {
            While ([System.Byte[]]$BytesRead = $BinaryReader.ReadBytes(2)) {
                [System.Boolean]$AtEnd = ($BinaryReader.BaseStream.Length -eq $BinaryReader.BaseStream.Position)
                [System.String]$B16 = ForEach ($Byte in $BytesRead) {
                    $Byte.ToString("X2")
                }
                $B16Line += $B16
                If ($B16Line.Length -eq 64 -and -not $Unformatted) {
                    [void]$Base16Output.Append($B16Line + "`r`n")
                    [System.String]$B16Line = [String]::Empty
                } ElseIf ($AtEnd) {
                    [void]$Base16Output.Append($B16Line)
                }
            }
            If (-Not $Unformatted) {
                [void]$Base16Output.Append("`r`n$($B16Footer)")
            }
            [System.String]$Base16Result = $Base16Output.ToString()
            $Base16ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllLines($OutFile.FullName,$Base16Result,(New-Object -TypeName System.Text.UTF8Encoding $False))
                $Base16ResultObject = $OutFile
            } Else {
                If ($Raw) {
                    $Base16ResultObject = $Base16Result
                } Else {
                    Add-Member -InputObject $Base16ResultObject -MemberType 'NoteProperty' -Name 'Base16EncodedData' -Value $Base16Result
                }
            }
            Return ($Base16ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $BinaryReader.Close()
            $BinaryReader.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $Timer.Stop()
            [System.String]$TimeLapse = "Base16 encode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertFrom-Base16() {
   <#
    .SYNOPSIS
        A PowerShell function to convert an arbitrary Base16 encoded string into a byte array or
        binary file.
 
    .DESCRIPTION
        Takes a string of Base16 formatted data and decodes into the original ASCII string or
        byte array. Input includes a Base16 string or a file containing Base16 string. Both
        formatted (line breaks) and unformatted Base16 data are supported. The default input and
        output type if positional parameters are used is [System.String]; it is also possible to
        write a binary file from the Base16 input using -OutFile.
 
    .PARAMETER Base16EncodedString
        [System.String] object containing Base16 encoded data. Accepts pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be loaded as a
        string object and decoded from Base16 string; accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing Base16 decoded data from the input file. Can be used with any input mode
        (Bytes, String, or InFile); file content will be raw decoded bytes.
 
    .PARAMETER OutBytes
        Return the decoded data as [System.Byte[]] to the console instead of the default ASCII
        string.
 
    .PARAMETER AutoSave
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter; for example, -AutoSave
        "BIN" will result in OutFile name <InFile>.bin. Useful if piping the output of
        Get-ChildItem to the function to convert files as a bulk operation. Cannot be used with
        input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw output instead of a
        PSObject. Depending on the parameters used, the return object could be of type
        [System.String] or [System.Byte[]].
 
    .INPUTS
        Any single object, array or collection of strings or files (such as those from
        Get-ChildItem) can be piped to the function for processing from Base16 encoded data.
        Input data from file is always processed as ASCII text regardless of source file text
        encoding.
 
    .OUTPUTS
        In the case of direct string input, a [System.String] containing the decoded data as
        ASCII text is returned within a PSObject with a single member named Base16DecodedData.
        If any input method is used with -OutFile or -InFile is used with -AutoSave, the output
        is a [System.IO.FileInfo] object containing details of a binary file with the Base16
        decoded data as contents. If -OutBytes is specified, data is returned to the console as
        [System.Byte[]] wrapped in a PSObject. If -Raw is specified, the [System.String] or
        [System.Byte[]] is not wrapped in a PSObject and is returned directly. This means that
        output using -Raw cannot easily use the pipeline. The -Verbose parameter will return the
        function's total execution time.
 
    .EXAMPLE
        Pipe an object (string or array of strings, file info or array of file info objects) to
        the function for decoding from Base16:
            $MyObject | ConvertFrom-Base16
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Base16
        decoded file for each input file:
            Get-ChildItem C:\Text\*.B16 | ConvertFrom-Base16 -AutoSave "BIN"
 
    .EXAMPLE
        Use file based input to decode an input file and output the results as new file
        C:\Text\file.txt:
            ConvertFrom-Base16 -File C:\Text\file.B16 -OutFile C:\Text\file.txt
 
    .NOTES
        This function uses the built-in [System.Convert]::ToByte() method to convert bytes to
        Base 16 (hex); more information on the Base16, Base16, and Base16 Data Encoding standard
        can be found on the IETF web site: https://tools.ietf.org/html/rfc4648
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Base16 encoded string.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('String','Plaintext','Text','Base16EncodedData')]
        [System.String]$Base16EncodedString,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to Base16 decode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            Position=1,
            HelpMessage='Path to output file when decoding in file mode.'
        )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Output decoded data as raw bytes instead of ASCII text.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$OutBytes,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning to console, return a raw byte array instead of PSObject.'
        )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        If ($PSBoundParameters.ContainsKey("AutoSave") -and $PSCmdlet.ParameterSetName -ne "FileInput") {
            Write-Error "-AutoSave can only be used in file input mode." -ErrorAction Stop
        }
        [System.String]$B16CHARSET_Pattern = "^[A-F0-9 ]*$"
        [System.String]$B16Header = "-----BEGIN BASE16 ENCODED DATA-----"
        [System.String]$B16Footer = "-----END BASE16 ENCODED DATA-----"
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "StringInput" {
                [System.String]$Base16String = $Base16EncodedString.Replace($B16Header,"").Replace($B16Footer,"").Replace(" ","").Replace("`r`n","").Replace("`n","")
                Break
            }
            "FileInput" {
                [System.String]$Base16String = ([System.IO.File]::ReadAllText($InFile.FullName)).Replace($B16Header,"").Replace($B16Footer,"").Replace(" ","").Replace("`r`n","").Replace("`n","")
                Break
            }
        }
        If (-not ($Base16String -imatch $B16CHARSET_Pattern)) {
            Throw "Invalid Base16 data encountered."
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        Try {
            [System.Byte[]]$B16Results  = [System.Byte[]]::new($Base16String.Length / 2)
            For($i = 0; $i -lt $Base16String.Length; $i += 2){
                $B16Results[$i / 2] = [System.Convert]::ToByte($Base16String.Substring($i, 2), 16)
            }
            $ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllBytes($OutFile,$B16Results)
                $ResultObject = $OutFile
            } Else {
                If ($OutBytes -and $Raw) {
                    $ResultObject = $B16Results
                } ElseIf ($OutBytes) {
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'Base16DecodedData' -Value $B16Results
                } ElseIf ($Raw) {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString($B16Results)
                    $ResultObject = $Results
                } Else {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString($B16Results)
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'Base16DecodedString' -Value $Results
                }
            }
            Return ($ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $Timer.Stop()
            [System.String]$TimeLapse = "Base16 decode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertTo-Base32() {
    <#
    .SYNOPSIS
        A PowerShell function to convert arbitrary data into a Base32 encoded string.
 
    .DESCRIPTION
        Takes a string, byte array or file object as input and returns a Base32 encoded string
        or location of the Base32 result output file object. The default input and output type
        if positional parameters are used is [System.String].
 
    .PARAMETER Bytes
        [System.Byte[]] object containing a byte array to be encoded as Base32 string. Accepts
        pipeline input.
 
    .PARAMETER String
        [System.String] object containing plain text to be encoded as Base32 string. Accepts
        pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be converted to
        Base32 string and output as a new file; output files are written as UTF-8 no BOM.
        Accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing Base32 encoded data from the input file. Can be used with any input mode
        (Bytes, String, or InFile).
 
    .PARAMETER Unormatted
        By default the function adds line breaks to output string every 64 characters and block
        style header / footer (-----BEGIN BASE32 ENCODED DATA-----/-----END BASE32 ENCODED
        DATA-----); this parameter suppresses formatting and returns the Base32 string result as
        a single, unbroken string object with no header or footer.
 
    .PARAMETER Base32Hex
        Use the alternative charset described in RFC4648 for "Base32 Hex"
        (0123456789ABCDEFGHIJKLMNOPQRSTUV) instead of the typical Base32 charset
        (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567).
 
    .PARAMETER AutoSave
        [System.String] containing a new file extension to use to automatically generate files.
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter, for example -AutoSave
        "B32" would create the OutFile name <InFile>.b32. Useful if piping the output of
        Get-ChildItem to the function to convert files as a bulk operation. Cannot be used with
        input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw string output instead of a
        PSObject. This parameter limits the functionality of the pipeline but is convenient for
        simple encoding operations.
 
    .INPUTS
        Any single object or collection of strings, bytes, or files (such as those from
        Get-ChildItem) can be piped to the function for processing into Base32 encoded data.
 
    .OUTPUTS
        The output is always an ASCII string; if any input method is used with -OutFile or
        -InFile is used with -AutoSave, the output is a [System.IO.FileInfo] object containing
        details of a UTF8 no BOM text file with the Base32 encoded data as contents. Unless
        -Unformatted is specified, the console or file string data is formatted with block
        headers (-----BEGIN BASE32 ENCODED DATA-----/-----END BASE32 ENCODED DATA-----) and line
        breaks are added every 64 character. If -Unformatted is present, the output is a
        [System.String] with no line breaks or header / footer. If outputting to the console,
        the string is returned within a PSObject with a single member named Base32EncodedData as
        [System.String]; if -Raw is specified, the [System.String] is not wrapped in a PSObject
        and returned directly. This means that output using -Raw cannot easily use the pipeline,
        but makes it a useful option for quick encoding operations. The -Verbose parameter will
        return the function's total execution time.
 
    .EXAMPLE
        Convert a string directly into Base32:
            ConvertTo-Base32 "This is a plaintext string"
 
    .EXAMPLE
        Pipe an object (string or array of strings, byte array or array of byte arrays, file
        info or array of file info objects) to the function for encoding as Base32:
            $MyObject | ConvertTo-Base32
    .EXAMPLE
        Convert a byte array to Base32 and return the output with block formatting and not
        wrapped in a PSObject (as a raw [System.String]):
            ConvertTo-Base32 -ByteArray $Bytes -Raw
 
    .EXAMPLE
        Load the contents of a file as byte array and convert directly into Base32-Hex:
            ConvertTo-Base32 -Base32Hex -ByteArray ([System.IO.File]::ReadAllBytes('C:\File.txt'))
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Base32
        encoded file with block formatting for each input file:
            Get-ChildItem C:\Text\*.txt | ConvertTo-Base32 -AutoSave "B32"
 
    .EXAMPLE
        Use file based input to Base32 encode an input file and output the results as new file
        C:\Text\base32.txt with no line breaks or header / footer:
            ConvertTo-Base32 -File C:\Text\file.txt -OutFile C:\Text\base32.txt -Unformatted
 
    .NOTES
        More information on the Base16, Base32, and Base64 Data Encoding standard can be found
        on the IETF web site: https://tools.ietf.org/html/rfc4648
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Byte array to Base32 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('ByteArray','Data')]
        [System.Byte[]]$Bytes,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='String to Base32 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Plaintext','Text')]
        [System.String]$String,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to Base32 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName','File')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$False,
            Position=1,
            HelpMessage='Output result to specified file as UTF8-NoBOM text instead of console.'
        )]
        [Parameter(
            ParameterSetName="ByteInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Do not format output string using header/footer and line breaks.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Unformatted,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Use extended Base32 Hex charset instead of standard Base32 charset.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Base32Hex,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning a string instead of a file, return a raw string instead of PSObject; applies to both console and file output modes.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        If ($Base32Hex) {
            [System.String]$B32CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUV"
            [System.String]$B32Name = "Base32-Hex"
        } Else {
            [System.String]$B32CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
            [System.String]$B32Name = "Base32"
        }
        [System.String]$B32Header = "-----BEGIN $($B32Name.ToUpper()) ENCODED DATA-----"
        [System.String]$B32Footer = "-----END $($B32Name.ToUpper()) ENCODED DATA-----"
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
            If ($Raw) {
                Write-Warning "File output mode specified; Parameter '-Raw' will be ignored."
            }
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "ByteInput" {
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,$Bytes)
                Break
            }
            "StringInput" {
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,[System.Text.Encoding]::ASCII.GetBytes($String))
                Break
            }
            "FileInput" {
                [System.IO.Stream]$InputStream  = [System.IO.File]::Open($InFile.FullName,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::ReadWrite)
                Break
            }
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        [System.Object]$BinaryReader = New-Object -TypeName System.IO.BinaryReader($InputStream)
        [System.Object]$Base32Output = New-Object -TypeName System.Text.StringBuilder
        If (-Not $Unformatted) {
            [void]$Base32Output.Append("$($B32Header)`r`n")
        }
        Try {
            While ([System.Byte[]]$BytesRead = $BinaryReader.ReadBytes(5)) {
                [System.Boolean]$AtEnd = ($BinaryReader.BaseStream.Length -eq $BinaryReader.BaseStream.Position)
                [System.UInt16]$ByteLength = $BytesRead.Length
                If ($ByteLength -lt 5) {
                    [System.Byte[]]$WorkingBytes = ,0x00 * 5
                    [System.Buffer]::BlockCopy($BytesRead,0,$WorkingBytes,0,$ByteLength)
                    [System.Array]::Resize([ref]$BytesRead,5)
                    [System.Buffer]::BlockCopy($WorkingBytes,0,$BytesRead,0,5)
                }
                [System.Char[]]$B32Chars = ,0x00 * 8
                [System.Char[]]$B32Chunk = ,"=" * 8
                $B32Chars[0] = ($B32CHARSET[($BytesRead[0] -band 0xF8) -shr 3])
                $B32Chars[1] = ($B32CHARSET[(($BytesRead[0] -band 0x07) -shl 2) -bor (($BytesRead[1] -band 0xC0) -shr 6)])
                $B32Chars[2] = ($B32CHARSET[($BytesRead[1] -band 0x3E) -shr 1])
                $B32Chars[3] = ($B32CHARSET[(($BytesRead[1] -band 0x01) -shl 4) -bor (($BytesRead[2] -band 0xF0) -shr 4)])
                $B32Chars[4] = ($B32CHARSET[(($BytesRead[2] -band 0x0F) -shl 1) -bor (($BytesRead[3] -band 0x80) -shr 7)])
                $B32Chars[5] = ($B32CHARSET[($BytesRead[3] -band 0x7C) -shr 2])
                $B32Chars[6] = ($B32CHARSET[(($BytesRead[3] -band 0x03) -shl 3) -bor (($BytesRead[4] -band 0xE0) -shr 5)])
                $B32Chars[7] = ($B32CHARSET[$BytesRead[4] -band 0x1F])
                [System.Array]::Copy($B32Chars,$B32Chunk,([Math]::Ceiling(($ByteLength / 5) * 8)))
                If ($BinaryReader.BaseStream.Position % 8 -eq 0 -and -Not $Unformatted -and -not $AtEnd) {
                    [void]$Base32Output.Append($B32Chunk)
                    [void]$Base32Output.Append("`r`n")
                } Else {
                    [void]$Base32Output.Append($B32Chunk)
                }
            }
            If (-Not $Unformatted) {
                [void]$Base32Output.Append("`r`n$($B32Footer)")
            }
            [System.String]$Base32Result = $Base32Output.ToString()
            $Base32ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllLines($OutFile.FullName,$Base32Result,(New-Object -TypeName System.Text.UTF8Encoding $False))
                $Base32ResultObject = $OutFile
            } Else {
                If ($Raw) {
                    $Base32ResultObject = $Base32Result
                } Else {
                    Add-Member -InputObject $Base32ResultObject -MemberType 'NoteProperty' -Name 'Base32EncodedData' -Value $Base32Result
                }
            }
            Return ($Base32ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $BinaryReader.Close()
            $BinaryReader.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $Timer.Stop()
            [System.String]$TimeLapse = "Base32 encode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertFrom-Base32() {
   <#
    .SYNOPSIS
        A PowerShell function to convert an arbitrary Base32 encoded string into a byte array or
        binary file.
 
    .DESCRIPTION
        Takes a string of Base32 formatted data and decodes into the original ASCII string or
        byte array. Input includes a Base32 string or a file containing Base32 string. Both
        formatted (line breaks) and unformatted Base32 data are supported. The default input and
        output type if positional parameters are used is [System.String]; it is also possible to
        write a binary file from the Base32 input using -OutFile.
 
    .PARAMETER Base32EncodedString
        [System.String] object containing Base32 encoded data. Accepts pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be loaded as a
        string object and decoded from Base32 string; accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing Base32 decoded data from the input file. Can be used with any input mode
        (Bytes, String, or InFile); file content will be raw decoded bytes.
 
    .PARAMETER Base32Hex
        Use the alternative charset described in RFC4648 for "Base32 Hex"
        (0123456789ABCDEFGHIJKLMNOPQRSTUV) instead of the typical Base32 charset
        (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567) when decoding.
 
    .PARAMETER OutBytes
        Return the decoded data as [System.Byte[]] to the console instead of the default ASCII
        string.
 
    .PARAMETER AutoSave
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter; for example, -AutoSave
        "BIN" will result in OutFile name <InFile>.bin. Useful if piping the output of
        Get-ChildItem to the function to convert files as a bulk operation. Cannot be used with
        input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw output instead of a
        PSObject. Depending on the parameters used, the return object could be of type
        [System.String] or [System.Byte[]].
 
    .INPUTS
        Any single object, array or collection of strings or files (such as those from
        Get-ChildItem) can be piped to the function for processing from Base32 encoded data.
        Input data from file is always processed as ASCII text regardless of source file text
        encoding.
 
    .OUTPUTS
        In the case of direct string input, a [System.String] containing the decoded data as
        ASCII text is returned within a PSObject with a single member named Base32DecodedData.
        If any input method is used with -OutFile or -InFile is used with -AutoSave, the output
        is a [System.IO.FileInfo] object containing details of a binary file with the Base32
        decoded data as contents. If -OutBytes is specified, data is returned to the console as
        [System.Byte[]] wrapped in a PSObject. If -Raw is specified, the [System.String] or
        [System.Byte[]] is not wrapped in a PSObject and is returned directly. This means that
        output using -Raw cannot easily use the pipeline. The -Verbose parameter will return the
        function's total execution time.
 
    .EXAMPLE
        Convert a Base32 string to a decoded byte array:
            [System.Byte[]]$Bytes = ConvertFrom-Base32 "IIAGCADTABSQAMYAGIAA====" -OutBytes -Raw
 
    .EXAMPLE
        Decode a Base32 encoded string:
            ConvertFrom-Base32 -Base32EncodedString "IIAGCADTABSQAMYAGIAA===="
 
    .EXAMPLE
        Pipe an object (string or array of strings, file info or array of file info objects) to
        the function for decoding from Base32:
            $MyObject | ConvertFrom-Base32
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Base32
        decoded file for each input file:
            Get-ChildItem C:\Text\*.b32 | ConvertFrom-Base32 -AutoSave "BIN"
 
    .EXAMPLE
        Use file based input to decode an input file and output the results as new file
        C:\Text\file.txt:
            ConvertFrom-Base32 -File C:\Text\file.b32 -OutFile C:\Text\file.txt
 
    .NOTES
        More information on the Base16, Base32, and Base64 Data Encoding standard can be found
        on the IETF web site: https://tools.ietf.org/html/rfc4648
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Base32 encoded string.'
        )]
            [ValidateNotNullOrEmpty()]
            [Alias('String','Plaintext','Text','Base32EncodedData')]
            [System.String]$Base32EncodedString,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to Base32 decode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            Position=1,
            HelpMessage='Path to output file when decoding in file mode.'
        )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Use extended Base32 Hex charset instead of standard Base32 charset.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Base32Hex,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Output decoded data as raw bytes instead of ASCII text.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$OutBytes,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning to console, return a raw byte array instead of PSObject.'
        )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        If ($PSBoundParameters.ContainsKey("AutoSave") -and $PSCmdlet.ParameterSetName -ne "FileInput") {
            Write-Error "-AutoSave can only be used in file input mode." -ErrorAction Stop
        }
        If ($Base32Hex) {
            [System.String]$B32CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUV"
            [System.String]$B32CHARSET_Pattern = "^[A-V0-9 ]+=*$"
            [System.String]$B32Name = "Base32-Hex"
        } Else {
            [System.String]$B32CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
            [System.String]$B32CHARSET_Pattern = "^[A-Z2-7 ]+=*$"
            [System.String]$B32Name = "Base32"
        }
        [System.String]$B32Header = "-----BEGIN $($B32Name.ToUpper()) ENCODED DATA-----"
        [System.String]$B32Footer = "-----END $($B32Name.ToUpper()) ENCODED DATA-----"
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "StringInput" {
                [System.String]$Base32String = $Base32EncodedString.Replace($B32Header,"").Replace($B32Footer,"").Replace(" ","").Replace("`r`n","").Replace("`n","").ToUpper()
                Break
            }
            "FileInput" {
                [System.String]$Base32String = ([System.IO.File]::ReadAllText($InFile.FullName)).Replace($B32Header,"").Replace($B32Footer,"").Replace(" ","").Replace("`r`n","").Replace("`n","").ToUpper()
                Break
            }
        }
        If (-not ($Base32String -match $B32CHARSET_Pattern)) {
            Throw ("Invalid Base32 data encountered in input stream.")
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        [System.Object]$InputStream = New-Object -TypeName System.IO.MemoryStream([System.Text.Encoding]::ASCII.GetBytes($Base32String),0,$Base32String.Length)
        [System.Object]$BinaryReader = New-Object -TypeName System.IO.BinaryReader($InputStream)
        [System.Object]$OutputStream = New-Object -TypeName System.IO.MemoryStream
        [System.Object]$BinaryWriter = New-Object -TypeName System.IO.BinaryWriter($OutputStream)
        Try {
            While ([System.Char[]]$CharsRead = $BinaryReader.ReadChars(8)) {
                [System.Byte[]]$B32Bytes = ,0x00 * 5
                [System.UInt16]$CharLen = 8 - ($CharsRead -Match "=").Count
                [System.UInt16]$ByteLen = [Math]::Floor(($CharLen * 5) / 8)
                [System.Byte[]]$BinChunk = ,0x00 * $ByteLen
                If ($CharLen -lt 8) {
                    [System.Char[]]$WorkingChars = ,"A" * 8
                    [System.Array]::Copy($CharsRead,$WorkingChars,$CharLen)
                    [System.Array]::Resize([ref]$CharsRead,8)
                    [System.Array]::Copy($WorkingChars,$CharsRead,8)
                }
                $B32Bytes[0] = (($B32CHARSET.IndexOf($CharsRead[0]) -band 0x1F) -shl 3) -bor (($B32CHARSET.IndexOf($CharsRead[1]) -band 0x1C) -shr 2)
                $B32Bytes[1] = (($B32CHARSET.IndexOf($CharsRead[1]) -band 0x03) -shl 6) -bor (($B32CHARSET.IndexOf($CharsRead[2]) -band 0x1F) -shl 1) -bor (($B32CHARSET.IndexOf($CharsRead[3]) -band 0x10) -shr 4)
                $B32Bytes[2] = (($B32CHARSET.IndexOf($CharsRead[3]) -band 0x0F) -shl 4) -bor (($B32CHARSET.IndexOf($CharsRead[4]) -band 0x1E) -shr 1)
                $B32Bytes[3] = (($B32CHARSET.IndexOf($CharsRead[4]) -band 0x01) -shl 7) -bor (($B32CHARSET.IndexOf($CharsRead[5]) -band 0x1F) -shl 2) -bor (($B32CHARSET.IndexOf($CharsRead[6]) -band 0x18) -shr 3)
                $B32Bytes[4] = (($B32CHARSET.IndexOf($CharsRead[6]) -band 0x07) -shl 5) -bor ($B32CHARSET.IndexOf($CharsRead[7]) -band 0x1F)
                [System.Buffer]::BlockCopy($B32Bytes, 0, $BinChunk, 0, $ByteLen)
                $BinaryWriter.Write($BinChunk)
            }
            $ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllBytes($OutFile,($OutputStream.ToArray()))
                $ResultObject = $OutFile
            } Else {
                If ($OutBytes -and $Raw) {
                    $ResultObject = $OutputStream.ToArray()
                } ElseIf ($OutBytes) {
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'ByteArray' -Value $OutputStream.ToArray()
                } ElseIf ($Raw) {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString(($OutputStream.ToArray()))
                    $ResultObject = $Results
                } Else {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString(($OutputStream.ToArray()))
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'Base32DecodedString' -Value $Results
                }
            }
            Return ($ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $BinaryReader.Close()
            $BinaryReader.Dispose()
            $BinaryWriter.Close()
            $BinaryWriter.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $OutputStream.Close()
            $OutputStream.Dispose()
            $Timer.Stop()
            [System.String]$TimeLapse = "Base32 decode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertTo-Base64() {
    <#
    .SYNOPSIS
        A PowerShell function to convert arbitrary data into a Base64 encoded string.
 
    .DESCRIPTION
        Takes a string, byte array or file object as input and returns a Base64 encoded string
        or location of the Base64 result output file object. The default input and output type
        if positional parameters are used is [System.String].
 
    .PARAMETER Bytes
        [System.Byte[]] object containing a byte array to be encoded as Base64 string. Accepts
        pipeline input.
 
    .PARAMETER String
        [System.String] object containing plain text to be encoded as Base64 string. Accepts
        pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be converted to
        Base64 string and output as a new file; output files are written as UTF-8 no BOM.
        Accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing Base64 encoded data from the input file. Can be used with any input mode
        (Bytes, String, or InFile).
 
    .PARAMETER Unormatted
        By default the function adds line breaks to output string every 64 characters and block
        style header / footer (-----BEGIN BASE64 ENCODED DATA-----/-----END BASE64 ENCODED
        DATA-----); this parameter suppresses formatting and returns the Base64 string result as
        a single, unbroken string object with no header or footer.
 
    .PARAMETER AutoSave
        [System.String] containing a new file extension to use to automatically generate files.
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter, for example -AutoSave
        "B64" would create the OutFile name <InFile>.B64. Useful if piping the output of
        Get-ChildItem to the function to convert files as a bulk operation. Cannot be used with
        input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw string output instead of a
        PSObject. This parameter limits the functionality of the pipeline but is convenient for
        simple encoding operations.
 
    .INPUTS
        Any single object or collection of strings, bytes, or files (such as those from
        Get-ChildItem) can be piped to the function for processing into Base64 encoded data.
 
    .OUTPUTS
        The output is always an ASCII string; if any input method is used with -OutFile or
        -InFile is used with -AutoSave, the output is a [System.IO.FileInfo] object containing
        details of a UTF8 no BOM text file with the Base64 encoded data as contents. Unless
        -Unformatted is specified, the console or file string data is formatted with block
        headers (-----BEGIN BASE64 ENCODED DATA-----/-----END BASE64 ENCODED DATA-----) and line
        breaks are added every 64 character. If -Unformatted is present, the output is a
        [System.String] with no line breaks or header / footer. If outputting to the console,
        the string is returned within a PSObject with a single member named Base64EncodedData as
        [System.String]; if -Raw is specified, the [System.String] is not wrapped in a PSObject
        and returned directly. This means that output using -Raw cannot easily use the pipeline,
        but makes it a useful option for quick encoding operations. The -Verbose parameter will
        return the function's total execution time.
 
    .EXAMPLE
        Convert a string directly into Base64:
            ConvertTo-Base64 "This is a plaintext string"
 
    .EXAMPLE
        Pipe an object (string or array of strings, byte array or array of byte arrays, file
        info or array of file info objects) to the function for encoding as Base64:
            $MyObject | ConvertTo-Base64
 
    .EXAMPLE
        Convert a byte array to Base64 and return the output with block formatting and not
        wrapped in a PSObject (as a raw [System.String]):
            ConvertTo-Base64 -ByteArray $Bytes -Raw
 
    .EXAMPLE
        Load the contents of a file as byte array and convert directly into Base64-Hex:
            ConvertTo-Base64 -Base64Hex -ByteArray ([System.IO.File]::ReadAllBytes('C:\File.txt'))
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Base64
        encoded file with block formatting for each input file:
            Get-ChildItem C:\Text\*.txt | ConvertTo-Base64 -AutoSave "B64"
 
    .EXAMPLE
        Use file based input to Base64 encode an input file and output the results as new file
        C:\Text\Base64.txt with no line breaks or header / footer:
            ConvertTo-Base64 -File C:\Text\file.txt -OutFile C:\Text\Base64.txt -Unformatted
 
    .NOTES
        This function uses the built-in [System.Convert]::ToBase64String() method so it is
        obscenely fast compared to the hand-rolled methods used in other functions from this
        module. More information on the Base16, Base64, and Base64 Data Encoding standard can be
        found on the IETF web site: https://tools.ietf.org/html/rfc4648
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Byte array to Base64 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('ByteArray','Data')]
        [System.Byte[]]$Bytes,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='String to Base64 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Plaintext','Text')]
        [System.String]$String,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to Base64 encode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName','File')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$False,
            Position=1,
            HelpMessage='Output result to specified file as UTF8-NoBOM text instead of console.'
        )]
        [Parameter(
            ParameterSetName="ByteInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Do not format output string using header/footer and line breaks.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Unformatted,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning a string instead of a file, return a raw string instead of PSObject; applies to both console and file output modes.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        [System.String]$B64Header = "-----BEGIN BASE64 ENCODED DATA-----"
        [System.String]$B64Footer = "-----END BASE64 ENCODED DATA-----"
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
            If ($Raw) {
                Write-Warning "File output mode specified; Parameter '-Raw' will be ignored."
            }
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "ByteInput" {
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,$Bytes)
                Break
            }
            "StringInput" {
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,[System.Text.Encoding]::ASCII.GetBytes($String))
                Break
            }
            "FileInput" {
                [System.IO.Stream]$InputStream  = [System.IO.File]::Open($InFile.FullName,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::ReadWrite)
                Break
            }
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        [System.Object]$BinaryReader = New-Object -TypeName System.IO.BinaryReader($InputStream)
        [System.Object]$Base64Output = New-Object -TypeName System.Text.StringBuilder
        If (-Not $Unformatted) {
            [void]$Base64Output.Append("$($B64Header)`r`n")
        }
        [System.String]$B64Line = [String]::Empty
        Try {
            While ([System.Byte[]]$BytesRead = $BinaryReader.ReadBytes(3)) {
                [System.Boolean]$AtEnd = ($BinaryReader.BaseStream.Length -eq $BinaryReader.BaseStream.Position)
                $B64Line += [System.Convert]::ToBase64String($BytesRead)
                If ($B64Line.Length -eq 64 -and -not $Unformatted) {
                    [void]$Base64Output.Append($B64Line + "`r`n")
                    [System.String]$B64Line = [String]::Empty
                } ElseIf ($AtEnd) {
                    [void]$Base64Output.Append($B64Line)
                }
            }
            If (-Not $Unformatted) {
                [void]$Base64Output.Append("`r`n$($B64Footer)")
            }
            [System.String]$Base64Result = $Base64Output.ToString()
            $Base64ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllLines($OutFile.FullName,$Base64Result,(New-Object -TypeName System.Text.UTF8Encoding $False))
                $Base64ResultObject = $OutFile
            } Else {
                If ($Raw) {
                    $Base64ResultObject = $Base64Result
                } Else {
                    Add-Member -InputObject $Base64ResultObject -MemberType 'NoteProperty' -Name 'Base64EncodedData' -Value $Base64Result
                }
            }
            Return ($Base64ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $BinaryReader.Close()
            $BinaryReader.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $Timer.Stop()
            [System.String]$TimeLapse = "Base64 encode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertFrom-Base64() {
   <#
    .SYNOPSIS
        A PowerShell function to convert an arbitrary Base64 encoded string into a byte array or
        binary file.
 
    .DESCRIPTION
        Takes a string of Base64 formatted data and decodes into the original ASCII string or
        byte array. Input includes a Base64 string or a file containing Base64 string. Both
        formatted (line breaks) and unformatted Base64 data are supported. The default input and
        output type if positional parameters are used is [System.String]; it is also possible to
        write a binary file from the Base64 input using -OutFile.
 
    .PARAMETER Base64EncodedString
        [System.String] object containing Base64 encoded data. Accepts pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be loaded as a
        string object and decoded from Base64 string; accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing Base64 decoded data from the input file. Can be used with any input mode
        (Bytes, String, or InFile); file content will be raw decoded bytes.
 
    .PARAMETER OutBytes
        Return the decoded data as [System.Byte[]] to the console instead of the default ASCII
        string.
 
    .PARAMETER AutoSave
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter; for example, -AutoSave
        "BIN" will result in OutFile name <InFile>.bin. Useful if piping the output of
        Get-ChildItem to the function to convert files as a bulk operation. Cannot be used with
        input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw output instead of a
        PSObject. Depending on the parameters used, the return object could be of type
        [System.String] or [System.Byte[]].
 
    .INPUTS
        Any single object, array or collection of strings or files (such as those from
        Get-ChildItem) can be piped to the function for processing from Base64 encoded data.
        Input data from file is always processed as ASCII text regardless of source file text
        encoding.
 
    .OUTPUTS
        In the case of direct string input, a [System.String] containing the decoded data as
        ASCII text is returned within a PSObject with a single member named Base64DecodedData.
        If any input method is used with -OutFile or -InFile is used with -AutoSave, the output
        is a [System.IO.FileInfo] object containing details of a binary file with the Base64
        decoded data as contents. If -OutBytes is specified, data is returned to the console as
        [System.Byte[]] wrapped in a PSObject. If -Raw is specified, the [System.String] or
        [System.Byte[]] is not wrapped in a PSObject and is returned directly. This means that
        output using -Raw cannot easily use the pipeline. The -Verbose parameter will return the
        function's total execution time.
 
    .EXAMPLE
        Convert a Base64 string to a decoded byte array:
            [System.Byte[]]$Bytes = ConvertFrom-Base64 "VGhpcyBpcyBCYXNlNjQgdGV4dA==" -OutBytes -Raw
 
    .EXAMPLE
        Decode a Base64 encoded string:
            ConvertFrom-Base64 -Base64EncodedString "VGhpcyBpcyBCYXNlNjQgdGV4dA=="
 
    .EXAMPLE
        Pipe an object (string or array of strings, file info or array of file info objects) to
        the function for decoding from Base64:
            $MyObject | ConvertFrom-Base64
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Base64
        decoded file for each input file:
            Get-ChildItem C:\Text\*.B64 | ConvertFrom-Base64 -AutoSave "BIN"
 
    .EXAMPLE
        Use file based input to decode an input file and output the results as new file
        C:\Text\file.txt:
            ConvertFrom-Base64 -File C:\Text\file.B64 -OutFile C:\Text\file.txt
 
    .NOTES
        This function uses the built-in [System.Convert]::FromBase64String() method so it is
        obscenely fast compared to the hand-rolled methods used in other functions from this
        module. More information on the Base16, Base64, and Base64 Data Encoding standard can be
        found on the IETF web site: https://tools.ietf.org/html/rfc4648
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Base64 encoded string.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('String','Plaintext','Text','Base64EncodedData')]
        [System.String]$Base64EncodedString,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to Base64 decode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            Position=1,
            HelpMessage='Path to output file when decoding in file mode.'
        )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Output decoded data as raw bytes instead of ASCII text.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$OutBytes,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning to console, return a raw byte array instead of PSObject.'
        )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        If ($PSBoundParameters.ContainsKey("AutoSave") -and $PSCmdlet.ParameterSetName -ne "FileInput") {
            Write-Error "-AutoSave can only be used in file input mode." -ErrorAction Stop
        }
        [System.String]$B64CHARSET_Pattern = "^[a-zA-Z0-9/+]*={0,2}$"
        [System.String]$B64Header = "-----BEGIN BASE64 ENCODED DATA-----"
        [System.String]$B64Footer = "-----END BASE64 ENCODED DATA-----"
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "StringInput" {
                [System.String]$Base64String = $Base64EncodedString.Replace($B64Header,"").Replace($B64Footer,"").Replace(" ","").Replace("`r`n","").Replace("`n","")
                Break
            }
            "FileInput" {
                [System.String]$Base64String = ([System.IO.File]::ReadAllText($InFile.FullName)).Replace($B64Header,"").Replace($B64Footer,"").Replace(" ","").Replace("`r`n","").Replace("`n","")
                Break
            }
        }
        If (-not ($Base64String -imatch $B64CHARSET_Pattern)) {
            Throw "Invalid Base64 data encountered."
            Return
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        Try {
            [System.Byte[]]$B64Results = [System.Byte[]][Convert]::FromBase64String($Base64String)
            $ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllBytes($OutFile,$B64Results)
                $ResultObject = $OutFile
            } Else {
                If ($OutBytes -and $Raw) {
                    $ResultObject = $B64Results
                } ElseIf ($OutBytes) {
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'Base64DecodedData' -Value $B64Results
                } ElseIf ($Raw) {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString($B64Results)
                    $ResultObject = $Results
                } Else {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString($B64Results)
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'Base64DecodedString' -Value $Results
                }
            }
            Return ($ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $Timer.Stop()
            [System.String]$TimeLapse = "Base64 decode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertTo-UUEncoding() {
    <#
    .SYNOPSIS
        A PowerShell function to convert an arbitrary byte array into a UNIX-to-UNIX (UU)
        encoded string.
 
    .DESCRIPTION
        Takes a string, byte array or file as input and returns a UUencoded string or file info
        for a new UUencoded copy of the input file as a result. The default input and output
        type if positional parameters are used is [System.String].
 
    .PARAMETER Bytes
        [System.Byte[]] object containing a byte array to be UUencoded. Accepts pipeline input.
 
    .PARAMETER String
        [System.String] object containing plain text to be UUencoded. Accepts pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be converted to
        UUencoded string and output as a new file; output files are written as UTF-8 no BOM.
        Accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing UUencoded data from the input file. Can be used with any input mode
        (Bytes, String, or InFile).
 
    .PARAMETER AutoSave
        [System.String] containing a new file extension to use to automatically generate files.
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter. For example, -AutoSave
        "UU" will produce OutFile name <InFile>.UU. Useful if piping the output of Get-ChildItem
        to the function to convert files as a bulk operation. Cannot be used with input methods
        other than -InFile.
 
    .PARAMETER Unformatted
        When the -Unformatted switch is specified, the returned uuencoded string has no header,
        footer, line breaks or byte length indicators; a single unbroken [System.String] is
        returned instead. Using this option will produce output that is considered invalid by
        most uudecode applications and such data cannot be decoded by utilities like the Linux
        uuencode binary. However, unformatted uuencoded strings can still be successfully
        decoded by this module's ConvertFrom-UUEncoding function.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw string output instead of a
        PSObject. This parameter limits the functionality of the pipeline but is convenient for
        simple encoding operations within a script.
 
    .INPUTS
        Any single object or collection of strings, bytes, or files (such as those from
        Get-ChildItem) can be piped to the function for processing into UUencoded data.
 
    .OUTPUTS
        The output is always an ASCII string; if any input method is used with -OutFile or
        -InFile is used with -AutoSave, the output is a [System.IO.FileInfo] object containing
        details of a UTF8 no BOM text file with the UUencoded data as contents. String data is
        formatted with uuencode-compliant header, footer, line length and line breaks - it is
        possible to decode UUencoded data from this function using the Linux UUdecode utility.
        The function adds required uuencoding header row (begin nnnn filename) and ending row
        (end); the function also adds a line break every 60 characters per the specification,
        and a UUencoded value containing the line length is also inserted at the beginning of
        each line for a total of 61 characters per line on full line input. This format is
        required by the specification and is not mutable. If outputting to the console, the
        string is returned within a PSObject as [System.String] with a single member named
        UUEncodedData; if -Raw is specified, the [System.String] is not wrapped in a PSObject
        and returned directly. This means that output using -Raw cannot easily use the pipeline,
        but makes it a useful option for quick encoding operations. The -Verbose parameter will
        return the function's total execution time.
 
    .EXAMPLE
        Convert a string directly into UUencoded string:
            ConvertTo-UUEncoding "This is a plaintext string" -Raw
 
    .EXAMPLE
        Pipe an object (string or array of strings, byte array or array of byte arrays, file
        info or array of file info objects) to the function for uuencoding:
            $MyObject | ConvertTo-UUEncoding
 
    .EXAMPLE
        Convert a byte array to UUencoded string and return the output with block formatting and
        not wrapped in a PSObject (as a raw [System.String]):
            ConvertTo-UUEncoding -ByteArray $Bytes -Raw
 
    .EXAMPLE
        Load the contents of a file as byte array and convert directly UUencoded string:
            ConvertTo-UUEncoding -ByteArray ([System.IO.File]::ReadAllBytes('C:\File.txt'))
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new UUencoded
        file with block formatting for each input file:
            Get-ChildItem C:\Text\*.txt | ConvertTo-UUEncoding -AutoSave "UU"
 
    .EXAMPLE
        Use file based input to uuencode an input file and output the results as new file
        C:\Text\UU.txt:
            ConvertTo-UUEncoding -File C:\Text\file.txt -OutFile C:\Text\UU.txt
 
    .NOTES
        UNIX-style line breaks (\n) are used as opposed to Windows-style line breaks (\r\n) on
        output string data; this is to maintain compatibility with Linux UUdecode utilities.
        This function is really geared more toward encoding / decoding files vs. string or
        pipeline input. UUencoding in general has been largely replaced by Base64, which does
        not suffer from the same technical limitations.
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Byte array to uuencode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('ByteArray','Data')]
        [System.Byte[]]$Bytes,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='String to uuencode.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Plaintext','Text')]
        [System.String]$String,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to uuencode'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName','File')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$False,
            Position=1,
            HelpMessage='Output result to file instead of console.'
        )]
        [Parameter(
            ParameterSetName="ByteInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Do not format output string using header/footer and line breaks.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Unformatted,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning a string instead of a file, return a raw string instead of PSObject.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        [System.String]$OFS = ""
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        If ($OutFile) {
            If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                Remove-Item $OutFile -Confirm:$False
            }
            If (Test-Path $OutFile -PathType Leaf) {
                Write-Error "Could not overwrite existing output file '$($Outfile)'" -ErrorAction Stop
            }
            $Null = New-Item -Path $OutFile -ItemType File
            If ($Raw) {
                Write-Warning "File output mode specified; Parameter '-Raw' will be ignored."
            }
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "ByteInput" {
                [System.String]$UUName = "byteinput"
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,$Bytes)
                Break
            }
            "StringInput" {
                [System.String]$UUName = "stringinput"
                [System.IO.Stream]$InputStream = New-Object -TypeName System.IO.MemoryStream(,[System.Text.Encoding]::ASCII.GetBytes($String))
                Break
            }
            "FileInput" {
                [System.String]$UUName = $InFile.Name.ToString()
                [System.IO.Stream]$InputStream  = [System.IO.File]::Open($InFile.FullName,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::ReadWrite)
                Break
            }
        }
        [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
        [System.Object]$BinaryReader = New-Object -TypeName System.IO.BinaryReader($InputStream)
        [System.String]$UULine = [String]::Empty
        [System.Object]$UUOutput = New-Object -TypeName System.Text.StringBuilder
        If (-Not $Unformatted) {
            [void]$UUOutput.Append("begin 0744 $($UUName)`n")
        }
        Try {
            While ([System.Byte[]]$BytesRead = $BinaryReader.ReadBytes(3)) {
                [System.Boolean]$AtEnd = ($BinaryReader.BaseStream.Length -eq $BinaryReader.BaseStream.Position)
                [System.UInt16]$ByteLength = $BytesRead.Length
                If ($ByteLength -lt 3) {
                    [System.Byte[]]$WorkingBytes = ,0x01 * 3
                    [System.Buffer]::BlockCopy($BytesRead,0,$WorkingBytes,0,$ByteLength)
                    [System.Array]::Resize([ref]$BytesRead,3)
                    [System.Buffer]::BlockCopy($WorkingBytes,0,$BytesRead,0,3)
                }
                [System.Char[]]$UUChars = ,0x00 * 4
                [System.Char[]]$UUChunk = ,0x00 * 4
                $UUChars[0] = [System.Text.Encoding]::ASCII.GetChars((($BytesRead[0] -band 0xFC) -shr 2) + 32)[0]
                $UUChars[1] = [System.Text.Encoding]::ASCII.GetChars(((($BytesRead[0] -band 0x03) -shl 4 ) -bor (($BytesRead[1] -band 0xF0) -shr 4 )) + 32)[0]
                $UUChars[2] = [System.Text.Encoding]::ASCII.GetChars(((($BytesRead[1] -band 0x0F) -shl 2 ) -bor (($BytesRead[2] -band 0xC0) -shr 6 )) + 32)[0]
                $UUChars[3] = [System.Text.Encoding]::ASCII.GetChars(($BytesRead[2] -band 0x3F) + 32)[0]
                [System.Array]::Copy($UUChars,$UUChunk,([Math]::Ceiling(($ByteLength / 3) * 4)))
                $UULine += $UUChunk
                If ($UULine.Length -eq 60) {
                    Switch ($Unformatted) {
                        $False {
                            [void]$UUOutput.Append(([System.Text.Encoding]::ASCII.GetChars((($UULine.Length / 4) * 3) + 32))[0] + $UULine + "`n")
                            [System.String]$UULine = [String]::Empty
                            Break
                        }
                        $True {
                            [void]$UUOutput.Append($UULine)
                            [System.String]$UULine = [String]::Empty
                            Break
                        }
                    }
                }
                If ($AtEnd) {
                    [System.UInt16]$TrimEnd = 3 - $ByteLength
                    Switch ($Unformatted) {
                        $False {
                            [void]$UUOutput.Append(([System.Text.Encoding]::ASCII.GetChars(((($UULine.Length / 4) * 3) - $TrimEnd) + 32))[0] + $UULine.SubString(0,($UULine.Length - $TrimEnd)))
                            Break
                        }
                        $True {
                            [void]$UUOutput.Append($UULine.SubString(0,($UULine.Length - $TrimEnd)))
                            Break
                        }
                    }
                }
            }
            If (-Not $Unformatted) {
                [void]$UUOutput.Append("`n```nend")
            }
            [System.String]$UUResult = $UUOutput.ToString()
            $UUResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllLines($OutFile.FullName,$UUResult,(New-Object -TypeName System.Text.UTF8Encoding $False))
                $UUResultObject = $OutFile
            } Else {
                If ($Raw) {
                    $UUResultObject = $UUResult
                } Else {
                    Add-Member -InputObject $UUResultObject -MemberType 'NoteProperty' -Name 'UUEncodedData' -Value $UUResult
                }
            }
            Return ($UUResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $BinaryReader.Close()
            $BinaryReader.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $Timer.Stop()
            [System.String]$TimeLapse = "UUencode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Function ConvertFrom-UUEncoding() {
    <#
    .SYNOPSIS
        A PowerShell function to convert UUencoded string data into a byte array or binary file.
 
    .DESCRIPTION
        Takes a string of UUencoded data and decodes into the original ASCII string or byte
        array. Input includes a UUencoded string or a file containing UUencoded data. The
        default input and output type if positional parameters are used is [System.String]. It
        is also possible to write a binary file using -OutFile.
 
    .PARAMETER UUEncodedString
        [System.String] object containing UUencoded data. Accepts pipeline input.
 
    .PARAMETER InFile
        [System.IO.Fileinfo] object containing the details of a file on disk to be loaded as a
        string object and UUdecoded; accepts pipeline input.
 
    .PARAMETER OutFile
        Optional [System.IO.Fileinfo] object containing the details of the new file to write to
        disk containing UUencoded data from the input file. Can be used with any input mode
        (Bytes, String, or InFile); file content will be raw decoded bytes.
 
    .PARAMETER OutBytes
        Return the decoded data as [System.Byte[]] to the console instead of the default ASCII
        string.
 
    .PARAMETER Auto
        If the input data contains the header row, automatically extrapolates the destination
        file name. If -Auto is specified in file output mode and the input data does not contain
        a header row or is not formatted and -AutoSave or -OutFile are not present, the function
        will return an error.
 
    .PARAMETER AutoSave
        When paired with -InFile, automatically create an output filename of in the form of the
        original file name plus the suffix specified after the parameter. For example, -AutoSave
        "UUDEC" will produce OutFile name <InFile>.UUDEC. Useful if piping the output of
        Get-ChildItem to the function to convert files as a bulk operation. Cannot be used with
        input methods other than -InFile.
 
    .PARAMETER Raw
        Optional switch parameter that when present will produce raw output instead of a
        PSObject. Depending on the parameters used, the return object could be of type
        [System.String] or [System.Byte[]].
 
    .INPUTS
        Any single object, array or collection of strings or files (such as those from
        Get-ChildItem) can be piped to the function for processing from UUencoded data. Input
        data from file is always processed as ASCII text regardless of source file text encoding.
 
    .OUTPUTS
        In the case of direct string input, a [System.String] containing the decoded data as
        ASCII text is returned within a PSObject with a single member named UUDecodedData. If
        any input method is used with -OutFile or -InFile is used with -AutoSave, the output is
        a [System.IO.FileInfo] object containing details of a binary file with the decoded data
        as contents. If -OutBytes is specified, data is returned to the console as
        [System.Byte[]] wrapped in a PSObject. If -Raw is specified, the [System.String] or
        [System.Byte[]] is not wrapped in a PSObject and is returned directly. This means that
        output using -Raw cannot easily use the pipeline. The -Verbose parameter will return the
        function's total execution time.
 
    .EXAMPLE
        Pipe an object (string or array of strings, file info or array of file info objects) to
        the function for decoding from Base32:
            $MyObject | ConvertFrom-UUEncoding
 
    .EXAMPLE
        Pipe the results of a directory listing from Get-ChildItem and generate a new Base32
        decoded file for each input file:
            Get-ChildItem C:\Text\*.UU | ConvertFrom-UUEncoding -AutoSave "UUDEC"
 
    .EXAMPLE
        Use file based input to decode an input file and output the results as new file
        C:\Text\file.txt:
            ConvertFrom-UUEncoding -File C:\Text\file.UU -OutFile C:\Text\file.txt
 
    .NOTES
        If the input data contains the header row, the output file name can be automatically
        extracted from the UUencoded data. If this row is absent and -Auto is specified, the
        function will return an error. This function is really geared more toward encoding /
        decoding files vs. string or pipeline input; it is really just for historical reference
        as UUencoding was superseded by Base64 encoding which is technically superior.
    #>

    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="High",
        DefaultParameterSetName="StringInput"
    )]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,
            Position=0,
            HelpMessage='Input uuencoded string.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('String','Plaintext','Text','UUEncodedData')]
        [System.String]$UUEncodedString,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            Mandatory=$True,Position=0,
            HelpMessage='File to uudecode'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Filename','FullName')]
        [ValidateScript({
            If (-Not($_ | Test-Path -PathType Leaf)) {
                throw ("Invalid input file name specified.")
            }Else{
                $True
            }
        })]
        [ValidateScript({
            Try {
                $_.Open([System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None).Close()
                $True
            }
            Catch {
                throw ("Input file is locked for reading or could not obtain read access.")
            }
        })]
        [System.IO.Fileinfo]$InFile,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            Position=1,
            HelpMessage='Path to output file when decoding in file mode.'
        )]
        [ValidateNotNullOrEmpty()]
        [System.IO.Fileinfo]$OutFile,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Output decoded data as raw bytes instead of ASCII text.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$OutBytes,
        [Parameter(
            ParameterSetName="ByteInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='Determine the output file name from the uuencoded data header; only applies to file mode. Can be mixed with -AutoSave or -OutFile when using pipeline to extract the file name if it exists, and rely on -AutoSave / -OutFile if it does not.'
        )]
        [Parameter(
            ParameterSetName="StringInput"
         )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Auto,
        [Parameter(
            ParameterSetName="FileInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When in file input mode, automatically select output file name using the specified suffix as the file extension; not valid with any other input mode (String or Bytes).'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            If (-Not(($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1))) {
                throw ("AutoSave suffix contains illegal characters.")
            } Else {
                $True
            }
        })]
        [System.String]$AutoSave,
        [Parameter(
            ParameterSetName="StringInput",
            ValueFromPipeline=$False,
            ValueFromPipelineByPropertyName=$False,
            Mandatory=$False,
            HelpMessage='When returning to console, return a raw byte array instead of PSObject.'
        )]
        [Parameter(
            ParameterSetName="FileInput"
         )]
        [ValidateNotNullOrEmpty()]
        [Switch]$Raw
    )
    BEGIN {
        If ($PSBoundParameters.ContainsKey("AutoSave") -and $PSCmdlet.ParameterSetName -ne "FileInput") {
            Write-Error "-AutoSave can only be used in file input mode." -ErrorAction Stop
        }
        [System.String]$NON_UU_Pattern = "[^\u0000-\u007F“”]"
        [System.String]$OFS = ""
    }
    PROCESS {
        If ($PSBoundParameters.ContainsKey('InFile') -and $PSBoundParameters.ContainsKey('AutoSave')) {
            $OutFile = ($InFile.FullName.ToString()) + ".$($AutoSave)"
        }
        Switch ($PSCmdlet.ParameterSetName) {
            "StringInput" {
                [System.Object]$InputStream = New-Object -TypeName System.IO.MemoryStream(,[System.Text.Encoding]::ASCII.GetBytes($UUEncodedString))
                [System.IO.StreamReader]$StreamReader = New-Object -TypeName System.IO.StreamReader($InputStream)
                Break
            }
            "FileInput" {
                [System.Object]$InputStream = [System.IO.File]::Open($InFile.FullName,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::ReadWrite)
                [System.IO.StreamReader]$StreamReader = New-Object -TypeName System.IO.StreamReader($InputStream)
                Break
            }
        }
        [System.Boolean]$Unformatted = $False
        [System.Object]$OutputStream = New-Object -TypeName System.IO.MemoryStream
        [System.Object]$BinaryWriter = New-Object -TypeName System.IO.BinaryWriter($OutputStream)
        [System.Char[]]$UUHeaderProbe = ,0x00 * 5
        Try {
            [void]$StreamReader.Read($UUHeaderProbe,0,5)
            If (($UUHeaderProbe -join "").ToLower() -ne "begin") {
                [System.Char[]]$UUProbe = ,0x00 * 65535
                [System.UInt16]$UUProbeLength = $StreamReader.Read($UUProbe,0,65535)
                [System.String]$UUProbeLine = ($UUProbe -join "").Substring(0,$UUProbeLength)
                If ($UUProbeLine -match $NON_UU_Pattern) {
                    Throw "Invalid UUencoded data encountered first 64KB of file - file may not be UUencoded."
                }
                $Unformatted = $True
            }
            $StreamReader.BaseStream.Position = 0
            $StreamReader.DiscardBufferedData()
            If (-Not $Unformatted) {
                [System.String[]]$UUHeader = ($StreamReader.ReadLine()) -split " "
            } Else {
                [System.String[]]$UUHeader = [System.String]::Empty
            }
            If ($PSBoundParameters.ContainsKey('Auto') -And $Unformatted) {
                $StreamReader.Close()
                $StreamReader.Dispose()
                $BinaryWriter.Close()
                $BinaryWriter.Dispose()
                $OutputStream.Close()
                $OutputStream.Dispose()
                Write-Error "The -Auto parameter was used but the input stream does not appear to contain a header row. You will need to specify -OutFile or -AutoSave in order to produce file output."  -ErrorAction Stop
            } ElseIf ($PSBoundParameters.ContainsKey('Auto') -And -Not $Unformatted -And $UUHeader[2]) {
                $OutFile = Join-Path (Get-Location).path $UUHeader[2]
            }
            If ($OutFile) {
                If ((Test-Path $OutFile -PathType Leaf) -and ($PSCmdlet.ShouldProcess($OutFile,'Overwrite'))) {
                    Remove-Item $OutFile -Confirm:$False
                }
                If (Test-Path $OutFile -PathType Leaf) {
                    Write-Error "Could not overwrite existing output file '$($Outfile)'"  -ErrorAction Stop
                }
                $Null = New-Item -Path $OutFile -ItemType File
            }
            [System.Object]$Timer = [System.Diagnostics.Stopwatch]::StartNew()
            While ([System.Char[]]$UULine = $StreamReader.ReadLine()) {
                If (-Not $Unformatted) {
                    If (($UULine -join "") -eq "end" -or $UULine[0] -eq "``") {
                        Break
                    }
                }
                If (($UULine -join "") -match $NON_UU_Pattern) {
                    Throw "Invalid UUencode data encountered in input stream."
                }
                [System.Object]$ProcessStream = New-Object -TypeName System.IO.MemoryStream($UULine,0,$UULine.Length)
                [System.Object]$BinaryReader = New-Object -TypeName System.IO.BinaryReader($ProcessStream)
                If (-Not $Unformatted) {
                    [void]$BinaryReader.ReadByte()
                }
                While ([System.Byte[]]$BytesRead  = $BinaryReader.ReadBytes(4)) {
                    [System.UInt16]$ByteLength = $BytesRead.Length
                    If ($ByteLength -lt 4) {
                        [System.Byte[]]$WorkingBytes = ,0x00 * 4
                        [System.Buffer]::BlockCopy($BytesRead,0,$WorkingBytes,0,$ByteLength)
                        [System.Array]::Resize([ref]$BytesRead,4)
                        [System.Buffer]::BlockCopy($WorkingBytes,0,$BytesRead,0,4)
                    }
                    [System.Byte[]]$UUBytes = ,0x00 * 3
                    [System.UInt16]$ByteLen = [Math]::Floor(($ByteLength * 3) / 4)
                    [System.Byte[]]$BinChunk = ,0x00 * $ByteLen
                    $UUBytes[0] = ((($BytesRead[0] - 32) -band 0x3F) -shl 2) -bor ((($BytesRead[1] - 32) -band 0x30) -shr 4)
                    $UUBytes[1] = ((($BytesRead[1] - 32) -band 0x0F) -shl 4) -bor ((($BytesRead[2] - 32) -band 0x3C) -shr 2)
                    $UUBytes[2] = ((($BytesRead[2] - 32) -band 0x03) -shl 6) -bor (($BytesRead[3] - 32) -band 0x3F)
                    [System.Buffer]::BlockCopy($UUBytes,0,$BinChunk,0,$ByteLen)
                    $BinaryWriter.Write($BinChunk)
                }
            }
            $ResultObject = New-Object -TypeName PSObject
            If ($OutFile) {
                [System.IO.File]::WriteAllBytes($OutFile,($OutputStream.ToArray()))
                $ResultObject = $OutFile
            } Else {
                If ($OutBytes -and $Raw) {
                    $ResultObject = $OutputStream.ToArray()
                } ElseIf ($OutBytes) {
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'UUDecodedData' -Value $OutputStream.ToArray()
                } ElseIf ($Raw) {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString(($OutputStream.ToArray()))
                    $ResultObject = $Results
                } Else {
                    [System.String]$Results = [System.Text.Encoding]::ASCII.GetString(($OutputStream.ToArray()))
                    Add-Member -InputObject $ResultObject -MemberType 'NoteProperty' -Name 'UUDecodedString' -Value $Results
                }
            }
            Return ($ResultObject)
        }
        Catch {
            Write-Error "Exception: $($_.Exception.Message)"
            Break
        }
        Finally {
            $BinaryWriter.Close()
            $BinaryWriter.Dispose()
            $InputStream.Close()
            $InputStream.Dispose()
            $OutputStream.Close()
            $OutputStream.Dispose()
            $StreamReader.Close()
            $StreamReader.Dispose()
            $Timer.Stop()
            [System.String]$TimeLapse = "UUdecode completed after $($Timer.Elapsed.Hours) hours, $($Timer.Elapsed.Minutes) minutes, $($Timer.Elapsed.Seconds) seconds, $($Timer.Elapsed.Milliseconds) milliseconds"
            Write-Verbose $TimeLapse
        }
    }
}

Export-ModuleMember -Function Get-RandomByteArray
Export-ModuleMember -Function Get-CompressedByteArray
Export-ModuleMember -Function Get-DecompressedByteArray
Export-ModuleMember -Function ConvertTo-Ascii85
Export-ModuleMember -Function ConvertFrom-Ascii85
Export-ModuleMember -Function ConvertTo-Base16
Export-ModuleMember -Function ConvertFrom-Base16
Export-ModuleMember -Function ConvertTo-Base32
Export-ModuleMember -Function ConvertFrom-Base32
Export-ModuleMember -Function ConvertTo-Base64
Export-ModuleMember -Function ConvertFrom-Base64
Export-ModuleMember -Function ConvertTo-UUEncoding
Export-ModuleMember -Function ConvertFrom-UUEncoding