Custom Archiving in PowerShell 5.0

Tutorial Hero
In a previous article I demonstrated how to use the new Compress-Archive cmdlet in PowerShell 5.0. In that article, I needed to create a zip file of selected files and retain the folder structure. In my original command I wanted to zip up XML files that reside in a number of subfolders under C:\Work. How can I retain that information?

I couldn’t find a simple way by using the cmdlet’s parameters or basic pipeline expressions. I have a solution, but it takes a little more effort.
Because I know the only way to maintain the folder structure is to only specify a folder name if I copy the files to a new directory structure and zip that up, then I should end up with a structured archive. Here’s how I accomplished this using the same XML files.
First, I’m going to get all the files I want and group them by their directory name. I’m sorting the results on the directory name so that when I create the corresponding temp folders, parent folders will get created first, although in reality, PowerShell doesn’t really care.

$g = dir c:\work\*.xml -Recurse | Group Directory | Sort name

The rest of the code looks like this:

$g | foreach -begin {
    #define the name of the zip file
    $zip = "c:\work\xmldata.zip"
    #delete the zip file if an old copy exists
    if (Test-Path $zip) {
        Del $zip
    }
    #create a temp folder
    $zipTemp = Join-Path -path $env:temp -childpath $([System.IO.Path]::GetRandomFileName())
    write-host $zipTemp -ForegroundColor Yellow
    New-Item -Path $zipTemp -ItemType Directory -ea stop
} -process {
    #get everything after drive:\
    $folder = $_.name.Split(":")[1].Substring(1)
    #construct a new path
    $newpath = Join-Path -Path $zipTemp -ChildPath $folder
    write-host $newpath -ForegroundColor green
    #create a temp folder
    $tempdestination = New-Item -Path $newpath -ItemType directory
    #copy files to the folders
    $_.group | copy -Destination $tempdestination
} -end {
    #compress each temp folder starting with the top level
    $top = dir $ziptemp -Directory
    Compress-Archive -Path $top.fullname -DestinationPath $zip
    #delete temp
    remove-item $ziptemp -Recurse -Force
}

I’m using ForEach-Object in a way you may not be familiar with. I’m piping each group object to ForEach, but before I process anything, I delete my target zip file if it already exists and create a random folder name in the TEMP directory.

$zipTemp = Join-Path -path $env:temp -childpath $([System.IO.Path]::GetRandomFileName())
write-host $zipTemp -ForegroundColor Yellow
New-Item -Path $zipTemp -ItemType Directory -ea stop

Then for each group object I split apart the name, which is the directory name, getting everything after C: because my top-level folder is C:\Work.

$folder = $_.name.Split(":")[1].Substring(1)


If it was something nested I’d need a bit more code to get just the top-level part, probably something with regular expressions. Once I have the folder path, I can create a new version of it in the randomly generated temp folder.

$tempdestination = New-Item -Path $newpath -ItemType director

Once this is in place, I can copy the files from each group to the corresponding temp folder.

$_.group | copy -Destination $tempdestination

The end result is the same directory structure for all of the XML files.
Once all of the files are copied, in the End scriptblock, I can create the archive getting the top folder in the temp directory, in this case Work. My solution assumes you will only have a single folder in the root of the temporary directory.

$top = dir $ziptemp –Directory
Compress-Archive -Path $top.fullname -DestinationPath $zip

When the zip file is finished I delete the temporary folder and files.

An archive with folders (Image Credit: Jeff Hicks)
An archive with folders (Image Credit: Jeff Hicks)

My zip file now has just the XML files I was after and their corresponding folders. You could take my code and turn it into a wrapper function or perhaps even create a proxy version of Compress-Archive. If you get around to doing this I hope you’ll let me know. Otherwise I’ll put it on my ever-growing to-do list
Before we leave our exploration of the archive cmdlets we need to talk about unzipping. Expanding an archive is trivial using Expand-Archive. All you need to do is specify the zip file and destination directory.

expand-archive -Path C:\work\XMLData.zip -DestinationPath E:\XMLData

Be aware that the target folder must already exist or you will get an error. Likewise, If the same file exists in the target PowerShell will throw an exception. You can solve that by using –Force.

expand-archive -Path C:\work\XMLData.zip -DestinationPath E:\XMLData -force

Expanding will also recreate any folders.

Viewing expanded folders (Image Credit: Jeff Hicks)
Viewing expanded folders (Image Credit: Jeff Hicks)


As I mentioned the cmdlets are pretty basic, but they get the job done. Are they something you might use, and what kind of IT management problem do they solve? What else do you wish they would do? Feel free to leave your thoughts in the comments.