Learn What IT Pros Need to Know About Windows 11 - August 24th at 1 PM ET! Learn What IT Pros Need to Know About Windows 11 - August 24th at 1 PM ET!
PowerShell

Create ZIP archives with PowerShell and the Shell.Application COM object

If you search the web, then you’ll find no shortage of PowerShell scripts for creating ZIP archives. Most of these solutions use the .NET compression classes, where some scripts simply call command-line versions of third-party archiving commands like WinRar, 7Zip, or even the venerable WinZip. That’s great if it works for you. However, all of these should be unnecessary by the time PowerShell 5.0 rolls around, but we’ll need to roll out our own zip tools for now.

This is especially true when you’re working with Desired State Configuration (DSC) and a pull server. If you want to deploy custom resources to managed nodes, then the resources need to be zipped up and copied to the pull server. Although there’s a checksum step that needs to be performed, but that’s not what I’ll cover in this article today. There are reports that zip files created with the .NET compression classes don’t work properly in a pull server situation. The suggestion is to use Windows Explorer to create the zip file. Alternatively, you can use the Shell.Application COM object, which I’ll show you how to use in this article.

Here’s the function.

#requires -version 3.0

Function New-ZipArchive {

<#
.Synopsis
Create a zip archive from a folder.
.Description
This command will create a zip file from the specified path. The path will be a top level folder in the archive.
.Parameter Path
The top level folder to be archived. This parameter has aliases of PSPath and Source.
.Parameter OutputPath
The filename for the zip file to be created. If it already exists, the command will not run, unless you use -Force. This parameter has aliases of Zip and Target.
.Parameter Force
Delete the existing zip file and create a new one.
.Example
PS C:\> New-ZipArchive -path c:\work -outputpath e:\workback.zip 

Create a new zip file called WorkBack.zip. The top level folder in the archive will be Work.
.Example
PS C:\> $dscres = Get-DSCResource | Select -expandproperty Module -unique | where {$_.path -notmatch "windows\\system32"}
PS C:\> $dscres | foreach {
 $out = "{0}_{1}.zip" -f $_.Name,$_.Version
 $zip = Join-Path -path "E:\DSC\ZipResource" -ChildPath $out
 New-ZipArchive -path $_.ModuleBase -OutputPath $zip -Passthru -force
 }

 The first command gets a unique list of modules for all DSC resources filtering out anything under System32. The second command creates a zip file for each module using the naming format modulename_version.zip.

.Notes
Version      : 1.0
Last Updated : February 2, 2015

Learn more about PowerShell:
http://jdhitsolutions.com/blog/essential-powershell-resources/


  ****************************************************************
  * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *
  * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK.  IF   *
  * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *
  * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING.             *
  ****************************************************************

#>

[cmdletbinding(SupportsShouldProcess)]
param(
[Parameter(Position=0,Mandatory,
HelpMessage="Enter the folder path to be archived.")]
[ValidateScript({Test-Path $_})]
[Alias("PSPath","Source")]
[String]$Path,
[Parameter(Position=1,Mandatory,
HelpMessage="Enter the path and filename for the zip file")]
[Alias("zip","Target")]
[ValidateNotNullorEmpty()]
[String]$OutputPath,
[Switch]$Force,
[switch]$Passthru
)

Write-Verbose "Starting $($MyInvocation.Mycommand)"  
Write-Verbose "Using bound parameters:"
Write-verbose  ($MyInvocation.BoundParameters| Out-String).Trim()

if ($Force -AND (Test-Path -path $OutputPath)) {
    Write-Verbose "Testing for existing file and deleting it"
    Remove-Item -Path $OutputPath
}
     
if(-NOT (Test-Path $OutputPath)) {
    Write-Verbose "Creating $OutputPath" 
    Try {
        #create an empty zip file
        Set-Content -path $OutputPath -value ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18)) -ErrorAction Stop
        
        #get the zip file object
        $zipfile = $OutputPath | Get-Item -ErrorAction Stop

        #make sure it is not set to ReadOnly
        write-verbose "Setting isReadOnly to False"
        $zipfile.IsReadOnly = $false  
    }
    Catch {
        Write-Warning "Failed to create $outputpath"
        write-Warning $_.exception.message
        #bail out
        Return
    }
} #if not test zip file path
else {
    Write-Warning "The file $OutputPath already exists. Please delete or use -Force and try again."
    
    #bail out
    Return
}

if ($PSCmdlet.ShouldProcess($Path)) {
    Write-Verbose "Creating Shell.Application"
    $shellApp = New-Object -com shell.application

    Write-Verbose "Using namespace $($zipfile.fullname)" 
    $zipPackage = $shellApp.NameSpace($zipfile.fullname)

    write-verbose ($zipfile | Out-String)

    $target = Get-Item -Path $Path

    $zipPackage.CopyHere($target.FullName) 

    If ($passthru) {
        #Pause enough to give the zip file a chance to update
        Start-Sleep -Milliseconds 200
        Get-Item -Path $Outputpath
    }
} #should process

Write-Verbose "Ending $($MyInvocation.Mycommand)"

} #close New-ZipFile function

Although I wrote the function with DSC in mind, you can use it to create a zip archive for any folder. It’s very important to note that the specified folder that you’re archiving will be a top-level folder in the zip archive. To use, all you need to do is specify the target folder and the name of the zip file to create. The zip file must not already exist, otherwise you’ll receive a warning. I’ve included a –Force parameter to delete the zip file if it already exists so that you can create a new one. I’ve also included simple support for –WhatIf.

Here’s how you might use my solution to backup a folder.

Sponsored Content

Read the Best Personal and Business Tech without Ads

Staying updated on what is happening in the technology sector is important to your career and your personal life but ads can make reading news, distracting. With Thurrott Premium, you can enjoy the best coverage in tech without the annoying ads.

new-ziparchive c:\work e:\work.zip -verb -Passthru -force
Creating a zip archive with PowerShell. (Image Credit: Jeff Hicks)
Creating a zip archive with PowerShell. (Image Credit: Jeff Hicks) 

With –Passthru, you may see the zip file that shows the initial size. Although there’s supposed to be flags that you can use to suppress the copy dialog box, I can’t get any of them to work on Windows 8.1. In my research, it appears that these flags may or may not work by design. However, the dialog may be helpful for larger folders.

You can use this function for any module that’s installed on your system. This is very handy if you’re developing a new module and want to package it.

$m = Get-Module ISEScriptingGeek –ListAvailable
New-ZipArchive $m.ModuleBase "e:\$($m.name).zip"

The ModuleBase property is the top-level folder for the module. The example above will create a zip file for the module using the module name on the E:\ drive.

Finally, here’s an example where I use the function to create zip files for all the custom DSC resources installed on my computer.

Get-DSCResource | Select -expandproperty Module -unique | 
where {$_.path -notmatch "windows\\system32"} | foreach {
 $out = "{0}_{1}.zip" -f $_.Name,$_.Version
 $zip = Join-Path -path "E:\DSC\ZipResource" -ChildPath $out
 New-ZipArchive -path $_.ModuleBase -OutputPath $zip -Passthru -force
}

My sample skips anything under System32, which should be the out-of-the-box resource modules like PSDesiredStateConfiguration. Note that I am using the .NET format operator, -f, to create the output file name.

$out = "{0}_{1}.zip" -f $_.Name,$_.Version

This is often an easier approach instead of relying on variable expansion, or God forbid, concatenation.

If you’re getting started with DSC, then you might want to take a look at my fundamentals course at Pluralsight. If you’re looking to create PowerShell tools and modules, then be sure to take a look at Learn PowerShell Toolmaking in a Month of Lunches. In any event, I hope this function helps until PowerShell 5.0 hits your desktop.

Related Topics:

BECOME A PETRI MEMBER:

Don't have a login but want to join the conversation? Sign up for a Petri Account

Register
Comments (0)

Leave a Reply

Register for Advanced Microsoft 365 Day!

GET-IT: Advanced Microsoft 365 1-Day Virtual Conference - Live August 24th!

Join us on Tuesday, August 24th and hear from Microsoft MVPs and industry experts about how to take advantage of Microsoft 365 at a technical level and dive deep into the features and functionality that will make your environment more secure and compliant.

RSVP Now

Sponsored By