Is there a way to manage the contents of a zip file in memory using Powershell?

I am currently trying to write a powershell function that works with the output of the Lync powershell cmdlet "Export-CsConfiguration -AsBytes". When using implicit Powershell remoting with Lync cmdlets, the -AsBytes flag is the only way the Export-CsConfiguration cmdlet works and returns an array of bytes, which, if you write it to disk using Set-Content -Encoding Byte, creates a zip file.

I am wondering if there is a way to expand the contents of a byte array into two files that are contained in this zip, but only do it in memory. I am not very interested in keeping the zip file for long as it changed frequently and something about writing the contents of the file to disk only to read them again so I can do something with the uncompressed content seems terribly wrong to me.

So, is there a way to do something like this that avoids writing to disk:

$ZipFileBytes = Export-CsConfiguration -AsBytes
# Made up Powershell function follows:
[xml]DocItemSet = Extract-FileFromInMemoryZip -ByteArray $ZipFileBytes -FileInsideZipIWant "DocItemSet.xml"
# Do stuff with XML here

      

Instead of this:

$ZipFileBytes = Export-CsConfiguration -AsBytes | Set-Content -Encoding Byte "CsConfig.zip"
[System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem')
[System.IO.Compression.ZipFile]::ExtractToDirectory("CsConfig.zip", "C:\Temp")
[xml]$DocItemSet = New-Object Xml.XmlDocument
$DocItemSet.Load("C:\Temp\DocItemSet.xml")
# Do stuff with XML here

      

Or am I a SOL?

+3


source to share


3 answers


Answering my own question here in case it proves useful to others: (NB Requires .NET 4.5)

It looks like using System.IO.Compression.ZipArchive in combination with System.IO.Memorystream is the way forward. I have this now:

Function Load-CsConfig{
  [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression') | Out-Null

  $ZipBytes = Export-CsConfiguration -AsBytes
  $ZipStream = New-Object System.IO.Memorystream
  $ZipStream.Write($ZipBytes,0,$ZipBytes.Length)
  $ZipArchive = New-Object System.IO.Compression.ZipArchive($ZipStream)
  $ZipEntry = $ZipArchive.GetEntry('DocItemSet.xml')
  $EntryReader = New-Object System.IO.StreamReader($ZipEntry.Open())
  $DocItemSet = $EntryReader.ReadToEnd()
  return $DocItemSet
}

      



What exactly do I need.

Thanks everyone :)

+7


source


DotNetZip is your friend. This SO post is read from a stream, but this is C # code.

Below is the PowerShell code but not tested. However, we hope this will be a good starting point for you. You should be getting intellisense on DotNetZip objects and their website has a bunch of help and sample code.



#load the DotNetZip assembly from disk and create a new zip object
[System.Reflection.Assembly]::LoadFrom("C:\Path\To\Ionic.Zip.dll")
$zipfile = New-Object Ionic.Zip.ZipFile

#your stream
$ZipFileBytes = Export-CsConfiguration -AsBytes

#your filename
$filename = "DocItemSet.xml"

$zipfile.Read($ZipFileBytes)
$file = $zipfile[$filename]

$stream = $file.OpenReader()

$buffer = New-Object Byte[] 4096
[int]$n

[string]$xmlFile = ""

cls
do {
  $n = $stream.Read($buffer,0, $buffer.Length);
  $totalBytesRead+=$n;
  $xmlFile += [System.Text.Encoding]::ASCII.GetString($buffer)
} while ($n -gt 0);

$stream.Close()
$stream.Dispose()
$zipfile.Dispose()

      

0


source


Implementation of "Made Powershell Function":

#
#   .SYNOPSIS
#       Extract a file from a byte[] zip file
#   
#   .DESCRIPTION
#       Extracts a file from a byte[] zip file as byte[]
#   
#   .PARAMETER ByteArray
#       Byte array containing zip file
#   
#   .PARAMETER FileInsideZipIWant
#       The file inside zip I want
#   
#   .PARAMETER utf8
#       If the file is UTF-8 encoded, use this switch to get a string
#   
#   .EXAMPLE
#       PS C:\> $utf8str = Extract-FileFromInMemoryZip -ByteArray $ZipFileBytes -FileInsideZipIWant "DocItemSet.xml" -utf8
#       PS C:\> $utf8str = Extract-FileFromInMemoryZip $ZipFileBytes "DocItemSet.xml" -utf8
#       PS C:\> $bs = Extract-FileFromInMemoryZip $ZipFileBytes "DocItemSet.xml"        
#   
#   .OUTPUTS
#       string, byte[]
#   
#   .NOTES
#       Exactly as desired. You may want to change the name of the "FileInsideZipIWant" parameter.
#       Don't plan on extracting files larger than 2GB.
#
function Extract-FileFromInMemoryZip
{
    [CmdletBinding(DefaultParameterSetName = 'raw')]
    [OutputType([string], ParameterSetName = 'utf8')]
    [OutputType([byte[]], ParameterSetName = 'raw')]
    param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0,
                   HelpMessage = 'Byte array containing zip file')]
        [byte[]]$ByteArray,
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 1,
                   HelpMessage = 'Single file to extract')]
        [string]$FileInsideZipIWant,
        [Parameter(ParameterSetName = 'utf8')]
        [switch]$utf8
    )

    BEGIN { Add-Type -AN System.IO.Compression -ea:Stop } # Stop on error

    PROCESS {
        $entry = (
            New-Object System.IO.Compression.ZipArchive(
                New-Object System.IO.MemoryStream ( ,$ByteArray)
            )
        ).GetEntry($FileInsideZipIWant)

        # Note ZipArchiveEntry.Length returns a long (rather than int),
        # but we can't conveniently construct arrays longer than System.Int32.MaxValue
        $b = [byte[]]::new($entry.Length)

        # Avoid StreamReader to (dramatically) improve performance
        # ...but it may be useful if the extracted file has a BOM header
        $entry.Open().Read($b, 0, $b.Length)


        write $(
            if ($utf8) { [System.Text.Encoding]::UTF8.GetString($b) }
            else { $b }
        )
    }
}

      

0


source







All Articles