Friday, August 30, 2013

Zip files and folders with PowerShell

One of the more frustrating things in this day of PowerShell v3 is not having cmdlets that can simply manipulate ZIP archives.

It is right there in the Windows GUI, but is it easy to automate?  Nope.

In fact if you search around you will find lots of different ways to handle this, one getting more complex than the next.  You will also find community projects that attempt to do the same thing.

One common reference that I ran across was this:

From the spectacular David Aiken. I have to admit, it is not the first time he has saved my bacon.

His solution is built in, no funky community add-ins, nothing strange I can’t follow, and best of all it is compact – it is really small.

I have already had to do some things with Shell.Application with PowerShell, so I figured I would give it a shot.

Well, I immediately ran into some issues. 

One, his use of –Recurse.  Not necessary.  Use Get-ChildItem and pipe in the folder and then entire folder is zipped.  Perfect.  So you can work the input just about any way you like and it will simply pass whatever you pipe to it.

Two, file locking.  This tripped my up for hours.  And lots of other folks that have found his solution too.  His little 500 millisecond wait before advancing on to the next is simply not real nor flexible based on varying file sizes.

I found all kinds of folks commenting on the same thing and developing all kinds of fancy solutions to handle it.  But, in the end I found something really simple, and it was buried right there in the shell.application all along.  Simply test for the existence of the item you are zipping in the zip archive.

So simple.  One little do loop.  With my crafty Until ( $zipPackage.Items() | select {$_.Name -eq $file.Name} )

And, I only modified the Add-Zip, since David already had a fail safe to create the .ZIP if it didn’t already exist.

I am going to update my use of his usage example as well.

In my case I have a number of XML files.  Each is in a unique Folder.  There are other files and folders with the XML files as well (this is an OVF, for you OVF fans).

I want to create the .ZIP one level up from the folder where the XML is.

$ovfFolder = Get-Item $xmlFile.PSParentPath 

$zipFullName = $ovfFolder.Parent.FullName + "\" + $xmlFile.BaseName + ".zip"

Get-ChildItem $ovfFolder | Add-Zip $zipFullName

Now, below is my modification to the original Add-Zip function.

function Add-Zip  # usage: Get-ChildItem $folder | Add-Zip $zipFullName

        set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
        (dir $zipfilename).IsReadOnly = $false   
    $shellApplication = new-object -com shell.application
    $zipPackage = $shellApplication.NameSpace($zipfilename)
    foreach($file in $input)
        do {
            Start-sleep 2
        } until ( $zipPackage.Items() | select {$_.Name -eq $file.Name} )


Mark said...

I used your function and it worked beautifully, thanks for posting. Its hard to believe that the native functionality of PowerShell is so limited when it comes to manipulating archive files. Thanks for the solution.

dimm0k the drunken dragon said...

wonderful modifications to the original as I was stuck getting it to work properly... is there any way to include empty directories in this as well?

BrianEh said...

Hmm. I do not know. Zip is file based, so it preserves the path but I don't know if it will grab just an empty path or not.

That is actually a question of the underlying library.

dimm0k the drunken dragon said...

I suppose it should be possible to filter out empty directories before piping it to Add-Zip? if so, any ideas how?

BrianEh said...

Yea, you would handle any of that in your Get-ChildItem include or exclude or filter options.
There are file, directly, and not empty options just to name a few.