I'm currently trying to write a powershell function which works with the output of the Lync powershell cmdlet "Export-CsConfiguration -AsBytes". When using implicit Powershell remoting with the Lync Cmdlets, the -AsBytes flag is the only way to work the Export-CsConfiguration cmdlet, and it returns a Byte array, which, if you write it to disk with "Set-Content -Encoding Byte", results in a zip file.
I'm wondering if there's a way to expand the content of the byte array into the two files which are contained in that zip, but only do it in memory. I'm not really interested in keeping the zip file around for long, as it changes frequently, and something about writing the file contents out to disk only to read them straight back in again so I can do something with the uncompressed content seems horribly wrong to me.
So is there someway of doing something like this which avoids writes 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 doing:
$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 SOL?
Answering my own question here, in case it proves useful to others: (N.B. Requires .NET 4.5)
It looks like using System.IO.Compression.ZipArchive in combination with System.IO.Memorystream is the way forward. I've got 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
}
Which does exactly what I need.
Thanks all :)
Making the "Made up Powershell function" a reality:
#
# .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 }
)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With