有没有办法用Powershell操作内存中的zip文件内容?

时间:2014-08-05 16:04:42

标签: powershell zip

我目前正在尝试编写一个PowerShell函数,该函数与Lync powershell cmdlet" Export-CsConfiguration -AsBytes"的输出一起使用。使用Lync Cmdlet的隐式Powershell远程处理时,-AsBytes标志是处理Export-CsConfiguration cmdlet的唯一方法,它返回一个Byte数组,如果您使用" Set-Content - 将其写入磁盘 - 编码字节",产生一个zip文件。

我想知道是否有办法将字节数组的内容扩展为该zip文件中包含的两个文件,但只能在内存中进行。我并不真的对保持zip文件很长时间感兴趣,因为它经常更改,以及将文件内容写入磁盘只是为了再次直接读取它们,所以我可以用未压缩的内容做一些事情似乎对我来说可怕的错误。

所以有一些做这样的事情可以避免写入磁盘:

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

而不是:

$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

或者我是SOL?

3 个答案:

答案 0 :(得分:7)

在这里回答我自己的问题,以防它对别人有用:( N.B.需要.NET 4.5)

看起来将System.IO.Compression.ZipArchive与System.IO.Memorystream结合使用是前进的方向。我现在有了这个:

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
}

这正是我所需要的。

全部谢谢:)

答案 1 :(得分:0)

DotNetZip是你的朋友。 This SO post从流中读取,但它是C#代码。

以下代码是PowerShell,但尚未经过测试。但是,它应该是一个很好的起点。您应该在DotNetZip对象上获得intellisense,并且在他们的网站上有大量的帮助和示例代码。

#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()

答案 2 :(得分:0)

使“组成Powershell功能”成为现实:

#
#   .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 }
        )
    }
}