2

I am writing a PowerShell function/script (using the version that ships with Windows 10, which I believe is 5.0) to take in a GZip compressed Base64 string and decompress it, and then decode it under the assumption the original uncompressed/decoded string was Unicode encoded.

I am trying to instantiate a new object instance of type MemoryStream using this constructor and the New-Object cmdlet. It takes one parameter, which is an array of bytes.

PowerShell is unable to find a valid overload that accepts the array of bytes I am trying to pass as the constructor's parameter. I believe the issue is due to the array's relatively large length. Please see my code below:

Function DecompressString()
{
    param([parameter(Mandatory)][string]$TextToDecompress)
    
    try
    {
        $bytes = [Convert]::FromBase64String($TextToDecompress)

        $srcStreamParameters = @{
            TypeName = 'System.IO.MemoryStream'
            ArgumentList = ([byte[]]$bytes)
        }

        $srcStream = New-Object @srcStreamParameters
        $dstStream = New-Object -TypeName System.IO.MemoryStream
        
        $gzipParameters = @{
            TypeName = 'System.IO.Compression.GZipStream'
            ArgumentList = ([System.IO.Stream]$srcStream, [System.IO.Compression.CompressionMode]::Decompress)
        }
        
        $gzip = New-Object @gzipParameters
        $gzip.CopyTo($dstStream)
        $output = [Text.Encoding]::Unicode.GetString($dstStream.ToArray())
        Write-Output $output
    }
    catch
    {
        Write-Host "$_" -ForegroundColor Red
    }
    finally
    {
        if ($gzip -ne $null) { $gzip.Dispose() }
        if ($srcStream -ne $null) { $srcStream.Dispose() }
        if ($dstStream -ne $null) { $dstStream.Dispose() }
    }
}

DecompressString
$ExitPrompt = Read-Host -Prompt 'Press Enter to Exit'

The error message I get is: Cannot find an overload for "MemoryStream" and the argument count: "1764".

Can anyone please clarify how I can get the script interpreter to use the constructor correctly with a large byte array?

6
  • 2
    It's trying to pass each element of $bytes, which apparently is of length 1764, as a separate argument to a MemoryStream constructor but no such overload exists. You need to wrap $bytes in an array like this: ,([byte[]]$bytes) (note the leading comma). Commented Feb 11, 2022 at 19:07
  • @LanceU.Matthews Thank you! That worked! Can you please remove your comment and add it as an answer, so I can upvote and accept it? Thanks again! Commented Feb 11, 2022 at 19:14
  • 1
    Out of curiosity, does it work if you use $srcStream = [System.IO.MemoryStream]::new([byte[]]$bytes) instead? Commented Feb 11, 2022 at 19:16
  • 1
    @SantiagoSquarzon Yes it does! Commented Feb 11, 2022 at 20:13
  • 1
    If this script is running on PS5.1+, I would recommend you to change all those New-Object statements for [typename]::new(..). Faster and more efficient. Commented Feb 11, 2022 at 20:18

1 Answer 1

2

I thought I would share an answer to this question which I found very interesting, solution to the error has already been provided by Lance U. Matthews in his helpful comment, by adding the unary operator , before the $bytes assigned to the ArgumentList of New-Object, by doing so, the $bytes are passed as a single argument (array of bytes) and not as individual elements to the constructor of System.IO.MemoryStream:

$bytes = [Convert]::FromBase64String($compressedString)
$srcStreamParameters = @{
    TypeName     = 'System.IO.MemoryStream'
    ArgumentList = , $bytes
}

$srcStream = New-Object @srcStreamParameters

Beginning in PowerShell 5.0 and going forward, you can construct your memory stream with the following syntax, which is more efficient and straight forward:

$srcStream = [System.IO.MemoryStream]::new($bytes)

As for the functions (Compress & Expand), I would like to share my take on these cool functions.

using namespace System.Text
using namespace System.IO
using namespace System.IO.Compression
using namespace System.Collections
using namespace System.Management.Automation
using namespace System.Collections.Generic
using namespace System.Management.Automation.Language

Add-Type -AssemblyName System.IO.Compression

class EncodingCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument (
        [string] $commandName,
        [string] $parameterName,
        [string] $wordToComplete,
        [CommandAst] $commandAst,
        [IDictionary] $fakeBoundParameters
    ) {
        [CompletionResult[]] $arguments = foreach($enc in [Encoding]::GetEncodings().Name) {
            if($enc.StartsWith($wordToComplete)) {
                [CompletionResult]::new($enc)
            }
        }
        return $arguments
    }
}
  • Compression from string to Base64 GZip compressed string:
function Compress-GzipString {
    [cmdletbinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string] $String,

        [Parameter()]
        [ArgumentCompleter([EncodingCompleter])]
        [string] $Encoding = 'utf-8',

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

    try {
        $enc       = [Encoding]::GetEncoding($Encoding)
        $outStream = [MemoryStream]::new()
        $gzip      = [GZipStream]::new($outStream, [CompressionMode]::Compress, $CompressionLevel)
        $inStream  = [MemoryStream]::new($enc.GetBytes($string))
        $inStream.CopyTo($gzip)
    }
    catch {
        $PSCmdlet.WriteError($_)
    }
    finally {
        $gzip, $outStream, $inStream | ForEach-Object Dispose
    }

    try {
        [Convert]::ToBase64String($outStream.ToArray())
    }
    catch {
        $PSCmdlet.WriteError($_)
    }
}
  • Expansion from Base64 GZip compressed string to string:
function Expand-GzipString {
    [cmdletbinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string] $String,

        [Parameter()]
        [ArgumentCompleter([EncodingCompleter])]
        [string] $Encoding = 'utf-8'
    )

    try {
        $enc       = [Encoding]::GetEncoding($Encoding)
        $bytes     = [Convert]::FromBase64String($String)
        $outStream = [MemoryStream]::new()
        $inStream  = [MemoryStream]::new($bytes)
        $gzip      = [GZipStream]::new($inStream, [CompressionMode]::Decompress)
        $gzip.CopyTo($outStream)
        $enc.GetString($outStream.ToArray())
    }
    catch {
        $PSCmdlet.WriteError($_)
    }
    finally {
        $gzip, $outStream, $inStream | ForEach-Object Dispose
    }
}

And for the little Length comparison, querying the Loripsum API:

$loremIp = Invoke-RestMethod loripsum.net/api/10/long
$compressedLoremIp = Compress-GzipString $loremIp

$loremIp, $compressedLoremIp | Select-Object Length

Length
------
  8353
  4940

(Expand-GzipString $compressedLoremIp) -eq $loremIp # => Should be True

These 2 functions as well as Compression From File Path and Expansion from File Path can be found on this repo.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.