Zlib compression in Powershell

125 Views Asked by At

I need to compress a string to Zlib in PowerShell.

Is there a way we can compress using the ZLibStream class in PowerShell?

https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.zlibstream?view=net-8.0

I was able to compress using the class DeflateStream and GZipStream, but the external system which uses the data was not able to decode it.

This is the powershell script I tried with

$stringToCompress  = 'string to compress'

$memStream = New-Object System.IO.MemoryStream
$deflateStream = New-Object System.IO.Compression.GZipStream($memStream, [System.IO.Compression.CompressionMode]::Compress)
$streamWriter = New-Object System.IO.StreamWriter($deflateStream)
$streamWriter.Write($stringToCompress)
$streamWriter.Close();
$encodedCompressedString = [System.Convert]::ToBase64String($memStream.ToArray())
Write-Output $encodedCompressedString 
2

There are 2 best solutions below

0
Santiago Squarzon On BEST ANSWER

The process of compression and expansion with ZLibStream is pretty much the same as with GZipStream.

Note that ZlibStream only works with .NET 6+ meaning that it is not available for Windows PowerShell 5.1 (.NET Framework 4.5+). If you want to use this class you will need to install PowerShell 7+.

# Choose encoding here, need to use the same for compress & decompress
$enc = [System.Text.Encoding]::UTF8

# - Compress:
$string = 'hello world!'

$outstream = [System.IO.MemoryStream]::new()
$zlib = [System.IO.Compression.ZLibStream]::new(
    $outstream,
    [System.IO.Compression.CompressionMode]::Compress)

$bytes = $enc.GetBytes($string)
$zlib.Write($bytes, 0, $bytes.Length)
$zlib.Dispose()

[System.Convert]::ToBase64String($outstream.ToArray())
# eJzLSM3JyVcozy/KSVEEAB6JBH4=

# - Decompress:
$instream = [System.IO.MemoryStream]::new(
    [System.Convert]::FromBase64String('eJzLSM3JyVcozy/KSVEEAB6JBH4='))

$zlib = [System.IO.Compression.ZLibStream]::new(
    $instream,
    [System.IO.Compression.CompressionMode]::Decompress)

$outstream = [System.IO.MemoryStream]::new()
$zlib.CopyTo($outstream)
$zlib.Dispose()
$enc.GetString($outstream.ToArray())
# hello world!

An alternative for Windows PowerShell 5.1 can be to use the ZLibWrapper package.

First download the package and expand it:

$invokeWebRequestSplat = @{
    OutFile = 'Joveler.ZLibWrapper.zip'
    Uri     = 'https://www.nuget.org/api/v2/package/Joveler.ZLibWrapper'
}
Invoke-WebRequest @invokeWebRequestSplat
Expand-Archive 'Joveler.ZLibWrapper.zip'

Then the flow is pretty similar using their assembly:

Add-Type -Path .\Joveler.ZLibWrapper\lib\net45\Joveler.ZLibWrapper.dll

# Choose encoding here, need to use the same for compress & decompress
$enc = [System.Text.Encoding]::UTF8

# - Compress:
$string = 'hello world!'

$outstream = [System.IO.MemoryStream]::new()
$zlib = [Joveler.ZLibWrapper.ZlibStream]::new(
    $outstream,
    [Joveler.ZLibWrapper.ZLibMode]::Compress)

$bytes = $enc.GetBytes($string)
$zlib.Write($bytes, 0, $bytes.Length)
$zlib.Dispose()

[System.Convert]::ToBase64String($outstream.ToArray())

# - Decompress:
$instream = [System.IO.MemoryStream]::new(
    [System.Convert]::FromBase64String('eJzLSM3JyVcozy/KSVEEAB6JBH4='))

$zlib = [Joveler.ZLibWrapper.ZLibStream]::new(
    $instream,
    [Joveler.ZLibWrapper.ZLibMode]::Decompress)

$outstream = [System.IO.MemoryStream]::new()
$zlib.CopyTo($outstream)
$zlib.Dispose()
$enc.GetString($outstream.ToArray())
# hello world!
3
jdweng On

My answer is similar to other answers. I spent lots of time debugging. Found major issue with the sample code at MSDN not flushing the stream and I got no results. The dispose in other answers does a flush and close and will get results. He is my code with flush and close.

Note : The only thing wrong with OPs original Posting is the missing Flush.

$message = 'The quick brown fox jumped over the lazy dog'
$enc = [system.Text.Encoding]::UTF8
$bytes = $enc.GetBytes($message)

$writeStream = [System.IO.MemoryStream]::New()
$compressor = [System.IO.Compression.DeflateStream]::New($writeStream, [System.IO.Compression.CompressionMode]::Compress, $true)
$compressor.Write($bytes, 0, $bytes.Length)
$compressor.Flush()
$compressor.Close()

$writeStream.Position = 0
$finalStream = [System.IO.MemoryStream]::New()
$decompressor = [System.IO.Compression.DeflateStream]::New($writeStream, [System.IO.Compression.CompressionMode]::Decompress, $true);
$decompressor.CopyTo($finalStream)
$decompressor.Flush()
$decompressor.Close()
$finalStream.Position = 0
$bytes = [byte[]]::New($finalStream.Length)
$numberBytes = $finalStream.Read($bytes, 0, $bytes.Length)
$results = $enc.GetString($bytes)
$results