ChatGPT解决这个技术问题 Extra ChatGPT

How to create a zip archive with PowerShell?

Is it possible to create a zip archive using PowerShell?


B
Brant Bobby

PowerShell v5.0 adds Compress-Archive and Expand-Archive cmdlets. The linked pages have full examples, but the gist of it is:

# Create a zip file with the contents of C:\Stuff\
Compress-Archive -Path C:\Stuff -DestinationPath archive.zip

# Add more files to the zip file
# (Existing files in the zip file with the same name are replaced)
Compress-Archive -Path C:\OtherStuff\*.txt -Update -DestinationPath archive.zip

# Extract the zip file to C:\Destination\
Expand-Archive -Path archive.zip -DestinationPath C:\Destination

PowerShell v5.0 has been officially released now. It also comes with Windows 10.
From Paragraph 2 of Compress-Archive Description: "...the maximum file size that you can compress by using Compress-Archive is currently 2 GB. This is a limiation of underlying API" However, if you use System.IO.Compression.ZipFile you can bypass this limitation.
The 2GB limit was inherited from System.IO.Compression.ZipFile. If the .NET framework you are using does not have this limit, the CmdLet should not hit this limit. I verified in the code.
@Pramod there is no -OutputPath parameter.
w
wonea

A pure PowerShell alternative that works with PowerShell 3 and .NET 4.5 (if you can use it):

function ZipFiles( $zipfilename, $sourcedir )
{
   Add-Type -Assembly System.IO.Compression.FileSystem
   $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
   [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcedir,
        $zipfilename, $compressionLevel, $false)
}

Just pass in the full path to the zip archive you would like to create and the full path to the directory containing the files you would like to zip.


Does this actually need Powershell 3.0, or just .net 4.5? Looks very light on actual powershell features to me, instead just being .net programming.
@bwerks see the 'edit' portion here
I was looking for a way to just compress a single large file, but apparently there isn't a method for this. I had to write code that would create a new directory, copy the single file there, compress that directory to a new zip file, then delete the directory to clean up.
@Baodad, see my answer.
Should've read that I needed the full path. Boy that was confusing! BTW this should be the accepted answer
A
Andrew Diamond

If you head on over to CodePlex and grab the PowerShell Community Extensions, you can use their write-zip cmdlet.

Since

CodePlex is in read-only mode in preparation for shutdown

you can go to PowerShell Gallery.


Yep, and it uses 7z as the core library for most of its compression cmdlets. I know, becaues I implemented it ;) +1
lol nice work, x0n. I imlpemented the feed store provider in PSCX. Slightly less practical but tonnes of fun. :)
If it uses 7z, is it possible to zip using a password?
@SemiDemented write-zip [input file/folder] [output file]
Powershell 5 comes with a Compress-Archive cmdlets that creates .zip blogs.technet.microsoft.com/heyscriptingguy/2015/08/13/…
s
sonjz

A native way with latest .NET 4.5 framework, but entirely feature-less:

Creation:

Add-Type -Assembly "System.IO.Compression.FileSystem" ;
[System.IO.Compression.ZipFile]::CreateFromDirectory("c:\your\directory\to\compress", "yourfile.zip") ;

Extraction:

Add-Type -Assembly "System.IO.Compression.FileSystem" ;
[System.IO.Compression.ZipFile]::ExtractToDirectory("yourfile.zip", "c:\your\destination") ;

As mentioned, totally feature-less, so don't expect an overwrite flag.

UPDATE: See below for other developers that have expanded on this over the years...


Why would you use the featureless method?
This should be the accepted answer in order of date posted and precedent. As to your updated comment - there is truly a vast number of ways to do this now. I am faced with needing this functionality and I'm on PowerShell 4 the first thing I found is the native way. This was a good question in 2009. I still think there could have been further research presented in the question when originally asked.
K
Karl Glennon

Install 7zip (or download the command line version instead) and use this PowerShell method:

function create-7zip([String] $aDirectory, [String] $aZipfile){
    [string]$pathToZipExe = "$($Env:ProgramFiles)\7-Zip\7z.exe";
    [Array]$arguments = "a", "-tzip", "$aZipfile", "$aDirectory", "-r";
    & $pathToZipExe $arguments;
}

You can the call it like this:

create-7zip "c:\temp\myFolder" "c:\temp\myFolder.zip"

If 7zip is in your path then all you need to write is "& 7z c:\temp\myFolder c:\temp\myFolder.zip"
If you don't want to install it, you can download the command line version instead. (Just look on 7-zip's Download page.) It's just an executable, and the command syntax is the same. The executable is a different name, though; it's 7za.exe for some reason. I've done this on a number of projects and have never been disappointed.
I tried with .net and Powershell tools for way too long until going the 7zip path which worked right away. Simple foreach loop on $file does the trick & "C:\Program Files\7-Zip\7z.exe" a -tzip ($file.FullName+".zip") $file.FullName
K
Krishna Prasad Yalavarthi

Lot has changed since the initial answer was posted. Here are some of the latest examples using Compress-Archive command.

Command to create new archive file, Draft.zip, by compressing two files, Draftdoc.docx and diagram2.vsd, specified by the Path parameter. The compression level specified for this operation is Optimal.

Compress-Archive -Path C:\Reference\Draftdoc.docx, C:\Reference\Images\diagram2.vsd -CompressionLevel Optimal -DestinationPath C:\Archives\Draft.Zip

Command to creates new archive file, Draft.zip, by compressing two files, Draft doc.docx and Diagram [2].vsd, specified by the LiteralPath parameter. The compression level specified for this operation is Optimal.

Compress-Archive -LiteralPath 'C:\Reference\Draft Doc.docx', 'C:\Reference\Images\Diagram [2].vsd'  -CompressionLevel Optimal -DestinationPath C:\Archives\Draft.Zip

Command to create new archive file, Draft.zip, in the C:\Archives folder. The new archive file contains every file in the C:\Reference folder, because a wildcard character was used in place of specific file names in the Path parameter.

Compress-Archive -Path C:\Reference\* -CompressionLevel Fastest -DestinationPath C:\Archives\Draft

Command creates an archive from an entire folder, C:\Reference

Compress-Archive -Path C:\Reference -DestinationPath C:\Archives\Draft

PowerShell appends the .zip extension to the file name automatically.


C
Community

Edit two - This code is an ugly, ugly kluge from olden days. You do not want it.

This compresses the contents of .\in to .\out.zip with System.IO.Packaging.ZipPackage following the example here

$zipArchive = $pwd.path + "\out.zip"
[System.Reflection.Assembly]::Load("WindowsBase,Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
$ZipPackage=[System.IO.Packaging.ZipPackage]::Open($zipArchive,
  [System.IO.FileMode]"OpenOrCreate", [System.IO.FileAccess]"ReadWrite")
$in = gci .\in | select -expand fullName
[array]$files = $in -replace "C:","" -replace "\\","/"
ForEach ($file In $files)
{
   $partName=New-Object System.Uri($file, [System.UriKind]"Relative")
   $part=$ZipPackage.CreatePart($partName, "application/zip",
      [System.IO.Packaging.CompressionOption]"Maximum")
   $bytes=[System.IO.File]::ReadAllBytes($file)
   $stream=$part.GetStream()
   $stream.Write($bytes, 0, $bytes.Length)
   $stream.Close()
}
$ZipPackage.Close()

Edit: Unreliable for larger files, maybe >10mb, YMMV. Something to do with appdomain evidence and isolated storage. The friendlier .NET 4.5 approach works nicely from PS v3, but wanted more memory in my case. To use .NET 4 from PS v2, config files need an unsupported tweak.


the major problem of ZipPackage is it is not normal ZIP file, but contains a content xml file. see: [how to avoid [Content_Types].xml in .net's ZipPackage class - Stack Overflow](stackoverflow.com/questions/3748970/…)
@aaron One more great reason not to use this ever again! You've got stiff competition for "the major problem" here ;)
s
sirdank

Giving below another option. This will zip up a full folder and will write the archive to a given path with the given name.

Requires .NET 3 or above

Add-Type -assembly "system.io.compression.filesystem"

$source = 'Source path here'    
$destination = "c:\output\dummy.zip"

If(Test-path $destination) {Remove-item $destination}

[io.compression.zipfile]::CreateFromDirectory($Source, $destination)

c
chwarr

Here is a native solution for PowerShell v5, using the cmdlet Compress-Archive Creating Zip files using PowerShell.

See also the Microsoft Docs for Compress-Archive.

Example 1:

Compress-Archive `
    -LiteralPath C:\Reference\Draftdoc.docx, C:\Reference\Images\diagram2.vsd `
    -CompressionLevel Optimal `
    -DestinationPath C:\Archives\Draft.Zip

Example 2:

Compress-Archive `
    -Path C:\Reference\* `
    -CompressionLevel Fastest `
    -DestinationPath C:\Archives\Draft

Example 3:

Write-Output $files | Compress-Archive -DestinationPath $outzipfile

P
Pluto

Why does no one look at the documentation? The same .NET 4.5 library everyone is referencing lets you do anything you want, including creating an empty ZIP and adding individual files to it.

See below for a code example:

# Load the .NET assembly
Add-Type -Assembly 'System.IO.Compression'
Add-Type -Assembly 'System.IO.Compression.FileSystem'

# Must be used for relative file locations with .NET functions instead of Set-Location:
[System.IO.Directory]::SetCurrentDirectory('.\Desktop')

# Create the zip file and open it:
$z = [System.IO.Compression.ZipFile]::Open('z.zip', [System.IO.Compression.ZipArchiveMode]::Create)

# Add a compressed file to the zip file:
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($z, 't.txt', 't.txt')

# Close the file
$z.Dispose()

Here's an overview on how to manipulate the zip archive while you're at it (just remember to close the file afterwards):

You can compress files by specifying a fourth parameter for CreateEntryFromFile(...).

Creating an entry returns a ZipArchiveEntry. This object lets you inspect the zipped file afterwards including letting you report the .CompressedLength, view or change the .LastWriteTime (needs Update mode), and more below.

If you need to inspect the ZIP archive later, you can access its .Entries property, and use the methods above as well as view the filename, the full path, the decompressed size, or delete the file (needs Update mode).

You can extract an archive two ways later. First open it, then extract either the entire archive or an individual entry (from .Entries or .GetEntry(...)). You can also extract an archive by its filename alone.

If you need to work with streams, you can create an empty entry and open its stream for writing afterwards. You can also modify an existing zip entry (from .Entries or .GetEntry(...)), which would let you do everything in-memory.

I encourage you to browse the documentation because that's how I found all this.


Thank you for this, this is perfect. Especially compared to the Compress-Archive cmdlet which is badly designed and doesn't have a good way of specifying paths INSIDE the zip.
e
evandrix

What about System.IO.Packaging.ZipPackage?

It would require .NET 3.0 or greater.

#Load some assemblys. (No line break!)
[System.Reflection.Assembly]::Load("WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")

#Create a zip file named "MyZipFile.zip". (No line break!)
$ZipPackage=[System.IO.Packaging.ZipPackage]::Open("C:\MyZipFile.zip",
   [System.IO.FileMode]"OpenOrCreate", [System.IO.FileAccess]"ReadWrite")

#The files I want to add to my archive:
$files = @("/Penguins.jpg", "/Lighthouse.jpg")

#For each file you want to add, we must extract the bytes
#and add them to a part of the zip file.
ForEach ($file In $files)
{
   $partName=New-Object System.Uri($file, [System.UriKind]"Relative")
   #Create each part. (No line break!)
   $part=$ZipPackage.CreatePart($partName, "",
      [System.IO.Packaging.CompressionOption]"Maximum")
   $bytes=[System.IO.File]::ReadAllBytes($file)
   $stream=$part.GetStream()
   $stream.Write($bytes, 0, $bytes.Length)
   $stream.Close()
}

#Close the package when we're done.
$ZipPackage.Close()

via Anders Hesselbom


C
Community

For compression, I would use a library (7-Zip is good like Michal suggests).

If you install 7-Zip, the installed directory will contain 7z.exe which is a console application.
You can invoke it directly and use any compression option you want.

If you wish to engage with the DLL, that should also be possible. 7-Zip is freeware and open source.


Here is an example of using 7 zip with AES encryption from Powershell: codeblog.theg2.net/2010/02/…
M
Michal Sznajder

This is really obscure but works. 7za.exe is standalone version of 7zip and is available with install package.

# get files to be send
$logFiles = Get-ChildItem C:\Logging\*.* -Include *.log | where {$_.Name -match $yesterday} 

foreach ($logFile in $logFiles)
{
    Write-Host ("Processing " + $logFile.FullName)

    # compress file
    & ./7za.exe a -mmt=off ($logFile.FullName + ".7z") $logFile.FullName

}

D
Dherik

If someone needs to zip a single file (and not a folder): http://blogs.msdn.com/b/jerrydixon/archive/2014/08/08/zipping-a-single-file-with-powershell.aspx

[CmdletBinding()]
Param(
     [Parameter(Mandatory=$True)]
     [ValidateScript({Test-Path -Path $_ -PathType Leaf})]
     [string]$sourceFile,

     [Parameter(Mandatory=$True)]
     [ValidateScript({-not(Test-Path -Path $_ -PathType Leaf)})]
     [string]$destinationFile
) 

<#
     .SYNOPSIS
     Creates a ZIP file that contains the specified innput file.

     .EXAMPLE
     FileZipper -sourceFile c:\test\inputfile.txt 
                -destinationFile c:\test\outputFile.zip
#> 

function New-Zip
{
     param([string]$zipfilename)
     set-content $zipfilename 
          ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
     (dir $zipfilename).IsReadOnly = $false
}

function Add-Zip
{
     param([string]$zipfilename) 

     if(-not (test-path($zipfilename)))
     {
          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) 
     { 
          $zipPackage.CopyHere($file.FullName)
          Start-sleep -milliseconds 500
     }
}

dir $sourceFile | Add-Zip $destinationFile

This code relies on a shell application and then guesses 500ms to wait for it to finish... I'm not disagreeing that it works (in most cases). But it creates popups as you use it in every case where adding a compressed file takes some amount of time (easily reproduced with adding large files or working with large zips). Also if any zip operation is slower than whatever sleep time specified, it'll fail to add the file and leave behind a popup dialog box. This is terrible for scripting. I also downvoted the other answer that relies on a COM object because it doesn't address these pitfalls.
A
Arkesh Patel

Here is the working code, zipping all files from a source folder and create a zip file in destination folder.

    $DestZip="C:\Destination\"
    $Source = "C:\Source\"

    $folder = Get-Item -Path $Source

    $ZipTimestamp = Get-Date -format yyyyMMdd-HHmmss;
    $ZipFileName  = $DestZip + "Backup_" + $folder.name + "_" + $ZipTimestamp + ".zip" 

    $Source

    set-content $ZipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18)) 
    # Wait for the zip file to be created.
    while (!(Test-Path -PathType leaf -Path $ZipFileName))
    {    
        Start-Sleep -Milliseconds 20
    } 
    $ZipFile = (new-object -com shell.application).NameSpace($ZipFileName)

    Write-Output (">> Waiting Compression : " + $ZipFileName)       

    #BACKUP - COPY
    $ZipFile.CopyHere($Source) 

    $ZipFileName
    # ARCHIVE

    Read-Host "Please Enter.."

V
Venkatakrishnan
function Zip-File
    {
    param (
    [string]$ZipName,
    [string]$SourceDirectory 

    )
       Add-Type -Assembly System.IO.Compression.FileSystem
       $Compress = [System.IO.Compression.CompressionLevel]::Optimal
       [System.IO.Compression.ZipFile]::CreateFromDirectory($SourceDirectory,
            $ZipName, $Compress, $false)
    }

Note: ZipName: Full Path of the Zip File which you want to create.

SourceDirectory: Full path to the directory containing the files which you would like to zip.


R
Roman O

In case you have WinRAR installed:

function ZipUsingRar([String] $directory, [String] $zipFileName)
{
  Write-Output "Performing operation ""Zip File"" on Target ""Item: $directory Destination:"
  Write-Output ($zipFileName + """")
  $pathToWinRar = "c:\Program Files\WinRAR\WinRar.exe";
  [Array]$arguments = "a", "-afzip", "-df", "-ep1", "$zipFileName", "$directory";
  & $pathToWinRar $arguments;
}

The meaning of the arguments: afzip creates zip archive, df deletes files, ep1 does not create full directory path within archive


I thought I finally found the answer to my problem when I saw this, but no matter what I do, WinRar won't run. If I use a batch file to run it, everything's fine, but if I try to run that batch file from a PowerShell script, nothing happens, it just goes on to the next part of the script which uploads the file to an FTP server.
I tried using this function as is, and it does nothing. I don't get it. My $Arguments: [Array]$arguments = 'a', '-ag-YYYY-MM-DD-NN', '-dh', '-ed', '-ep3', '-ilogE:\Logs\WinRar\backup.log', '-INUL', '-r', '-y', 'E:\UsageWebLogs\Weblogs', 'W:\', 'X:\', 'Y:\', 'Z:\';
L
Lou O.

Here is a slightly improved version of sonjz's answer,it adds an overwrite option.

function Zip-Files(
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$false)]
        [string] $zipfilename,
        [Parameter(Position=1, Mandatory=$true, ValueFromPipeline=$false)]
        [string] $sourcedir,
        [Parameter(Position=2, Mandatory=$false, ValueFromPipeline=$false)]
        [bool] $overwrite)

{
   Add-Type -Assembly System.IO.Compression.FileSystem
   $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal

    if ($overwrite -eq $true )
    {
        if (Test-Path $zipfilename)
        {
            Remove-Item $zipfilename
        }
    }

    [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcedir, $zipfilename, $compressionLevel, $false)
}

Could you please elaborate more your answer adding a little more description about the solution you provide?
I took a previous answer and improved it by adding overwrite option, not much more to say!
C
Community

This should also work for compressing a single file without using a temp folder and using native .Net 4.5, converted from C# from this StackOverflow answer. It uses a nicer using syntax taken from here.

Usage:

ZipFiles -zipFilename output.zip -sourceFile input.sql -filename name.inside.zip.sql

Code:

function ZipFiles([string] $zipFilename, [string] $sourceFile, [string] $filename)
{
    $fullSourceFile = (Get-Item -Path "$sourceFile" -Verbose).FullName
    $fullZipFile = (Get-Item -Path "$zipFilename" -Verbose).FullName

    Add-Type -AssemblyName System.IO
    Add-Type -AssemblyName System.IO.Compression
    Add-Type -AssemblyName System.IO.Compression.FileSystem

    Using-Object ($fs = New-Object System.IO.FileStream($fullZipFile, [System.IO.FileMode]::Create)) {
         Using-Object ($arch = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)) {
             [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($arch, $fullSourceFile, $filename)
        }
    }
}

Using:

function Using-Object
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [AllowEmptyCollection()]
        [AllowNull()]
        [Object]
        $InputObject,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock
    )

    try
    {
        . $ScriptBlock
    }
    finally
    {
        if ($null -ne $InputObject -and $InputObject -is [System.IDisposable])
        {
            $InputObject.Dispose()
        }
    }
}

Excellent. I was looking for a way to zip ONE file without using that shell.application business or 7-Zip / other separate utilities. I like the Using-Object function too, although I went for a shorter, quick-n-dirty approach without that.
O
Owen Blacker

I use this snippet to check my database backups folder for backup files not compressed yet, compress them using 7-Zip, and finally deleting the *.bak files to save some disk space. Notice files are ordered by length (smallest to biggest) before compression to avoid some files not being compressed.

$bkdir = "E:\BackupsPWS"
$7Zip = 'C:\"Program Files"\7-Zip\7z.exe'

get-childitem -path $bkdir | Sort-Object length |
where
{
    $_.extension -match ".(bak)" -and
    -not (test-path ($_.fullname -replace "(bak)", "7z"))
} |
foreach
{
    $zipfilename = ($_.fullname -replace "bak", "7z")
    Invoke-Expression "$7Zip a $zipfilename $($_.FullName)"
}
get-childitem -path $bkdir |
where {
    $_.extension -match ".(bak)" -and
   (test-path ($_.fullname -replace "(bak)", "7z"))
} |
foreach { del $_.fullname }

Here you can check a PowerShell script to backup, compress and transfer those files over FTP.


A
Alex

Here a complete command line example to launch from cmd.exe or from ssh or what you want !

powershell.exe -nologo -noprofile -command "&{ Add-Type -A 'System.IO.Compression.FileSystem' [System.IO.Compression.ZipFile]::CreateFromDirectory('c:/path/to/source/folder/', 'c:/path/to/output/file.zip');}"

Regards


P
Paul Latour

Loading the [System.IO.IOException] class and using its methods is an important step in order to suppress unwanted errors, due the fact that it's a class not native to PowerShell, so expect various contexts of errors without it.

I error-controlled my script to the T, but got a lot of extra red 'file exists' output while using [System.IO.Compression.ZipFile] class

function zipFiles(
    [Parameter(Position=0, Mandatory=$true]
    [string] $sourceFolder,
    [Parameter(Position=1, Mandatory=$true]
    [string]$zipFileName,
    [Parameter(Position=2, Mandatory=$false]
    [bool]$overwrite)

{   
Add-Type -Assembly System.IO
Add-Type -Assembly System.IO.Compression.FileSystem

$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal

$directoryTest = (Test-Path $dailyBackupDestFolder)
$fileTest = (Test-Path $zipFileName)

if ( $directoryTest -eq $false) 
{ 
    New-Item -ItemType Directory -Force -Path $dailyBackupDestFolder 
}

     if ( $fileTest -eq $true)
     {
           if ($overwrite -eq $true ){Remove-Item $zipFileName}
     }   


    try
    {
         [System.IO.Compression.ZipFile]::CreateFromDirectory($sourceFolder,$zipFileName,$compressionLevel)       

    }
    catch [System.IO.IOException] 
    {
       Write-Output ($dateTime + ' | ' + $_.Exception.Message ) | Out-File $logFile -append -force 
    }
} 

What I am doing here is catching these IO Errors, such as accessing files that exist already, catching that error and directing it to a logfile that I am maintaining with a larger program.


h
henrycarteruk

Complete command-line Commands in Windows for Compressing and Extracting Directory is as follows:

For Compression: powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::CreateFromDirectory('C:\Indus','C:\Indus.zip'); }"

For Extracting: powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem';[IO.Compression.ZipFile]::ExtractToDirectory('C:\Indus.zip','C:\Indus'); }"


u
user3569196

Old thread but still, I got here :)

It is not an answer to the original question, but maybe someone will find it useful how to create ZipArchive object with PS.

# Example, if you have like I have a $myByteArray byte[] with the compressed data:
Add-Type -AssemblyName System.IO.Compression.FileSystem

# Fixed length stream:
$strm = New-Object -TypeName System.IO.MemoryStream -ArgumentList @(, $myByteArray);

# Create ZipArchive object
$arch = [System.IO.Compression.ZipArchive]::new($strm);

# List entries
$arch.Entries

There is already an accepted answer. This answer is not new, there are already multiple .NET answers based around System.IO.Compression.ZipFile.