Borrowing from Windows Explorer in PowerShell part 1 – ZIP files

When I was at tech-ed in Barcelona recently I met Corey Hynes, who looks after building a lot the labs for these events and some of our internal training too. If you were at Tech-ed and saw Windows 2008 R2 on our stand there, you were seeing some of Corey’s work. He builds a lot of virtual Machines and asked if I could add a feature to the codeplex library for Hyper-V which will show up in the next version I post there. I was looking at the way he distributes his VMs and added a second thing to the library.

Without giving all of his trade secrets away, Corey builds VMs making the maximum use of differencing disks, so if he has 4 machines running the same OS he will have 1 parent and 4 differencing VHDs. This doesn’t give the best performance*, but these small scale environments don’t need it. Corey also makes a lot of use of snapshots to allow a VM to be rolled back – or rolled forward to the state at the end of a lab, again something you’d avoid in production to get the best performance.

His technique is to export each VM, remove the duplicated parent VHD and make a secondary copy of the description files which are destroyed in the import process and then compact the whole lot. If anything goes wrong with the VM on the target computer it can be deleted and re-imported just by unpacking the import description files. So I though it would be a good idea to allow my Import-VM function to preserve the files, the line of code it needs is

 if ($Preserve) {Add-ZipFile "$path\importFiles.zip" "$Path\config.xml","$path\virtual machines"}

Add-ZipFile  ZipName  FileNames, is all very well as a function specification, but how do you write it.  I’m told that for licensing reasons the only way to access ZIP files that Windows provides is via explorer so the technique is to create an empty ZIP file and then tell explorer to add the files to it.

Here’s the code to make a new, empty, Zip file.

 Function new-zip

{Param ($zipFile)

 if (-not $ZipFile.EndsWith('.zip')) {$ZipFile += '.zip'} 

 set-content $ZipFile ("PK" + [char]5 + [char]6 + ([string][char]0) * 18)
}

As you can see a 22 character header marks a file as a ZIP file. The the code below adds files to it.

 Filter  Add-ZIPfile 

{Param ($zipFile=$(throw "You must specify a Zip File"), $files) 
 if ($files -eq $null) {$files = $_} 
 if (-not $ZipFile.EndsWith('.zip')) {$ZipFile += '.zip'} 
 if (-not (test-path $Zipfile)) {new-zip $ZipFile} 
 $ZipObj = (new-object -com shell.application).NameSpace(((resolve-path $ZipFile).path))  $files | foreach { if     ($_ -is [String]) 
                               {$zipObj.CopyHere((resolve-path $_).path )}
                  elseif (($_ -is [System.IO.FileInfo]) -or ($_ -is [System.IO.DirectoryInfo]) )
                               {$zipObj.CopyHere($_.fullname) }
              start-sleep -seconds 2} 
 $files = $null 

} 

The key thing is the Shell.application object has a namespace method which takes a path, and returns a folder or zipfile as a namespace. The namespace has a “copy here” method, so the logic is check for one or more file(s) passed as a parameter or via the pipe.Check that the Zip file ends with .ZIP and if it does, and the .ZIP extension. If the file doesn’t exist,create it as an empty ZIPfile.Get a namespace for it and call  the copy here method for each file passed. (If the file was a name, resolve it to a full name and if it is an object get the full name from the object).

Easy …. Now this led me to explore the Shell.application object more, but I’ll make that another post.

 

* Update Corey pointed out that by sharing a VHD with the common files on you maximize the benefit of any read cache. Differencing disks (including the ones used for snapshots) are extended when blocks on the parent change, that’s the slow bit. In a workload with few changes to the disk a differencing disk can work out faster.

Update 2. Corey was way to polite to mention I’d misspelled his name ! I’ve put that right.