199

I'm pretty sure this is not a duplicate so bear with me for just a minute.

How can I programatically (C#) ZIP a file (in Windows) without using any third party libraries? I need a native windows call or something like that; I really dislike the idea of starting a process, but I will if I absolutely have to. A PInovke call would be much better.

Failing that, let me tell you what I'm really trying to accomplish: I need the ability to let a user download a collection of documents in a single request. Any ideas on how to accomplish this?

Cheeso
  • 189,189
  • 101
  • 473
  • 713
Esteban Araya
  • 29,284
  • 24
  • 107
  • 141

8 Answers8

362

How can I programatically (C#) ZIP a file (in Windows) without using any third party libraries?

If using the 4.5+ Framework, there is now the ZipArchive and ZipFile classes.

using (ZipArchive zip = ZipFile.Open("test.zip", ZipArchiveMode.Create))
{
    zip.CreateEntryFromFile(@"c:\something.txt", "data/path/something.txt");
}

You need to add references to:

  • System.IO.Compression
  • System.IO.Compression.FileSystem

For .NET Core targeting net46, you need to add dependencies for

  • System.IO.Compression
  • System.IO.Compression.ZipFile

Example project.json:

"dependencies": {
  "System.IO.Compression": "4.1.0",
  "System.IO.Compression.ZipFile": "4.0.1"
},

"frameworks": {
  "net46": {}
}

For .NET Core 2.0, just adding a simple using statement is all that is needed:

  • using System.IO.Compression;
GalacticJello
  • 11,235
  • 2
  • 25
  • 35
  • 7
    How has this not gotten more upvotes? It's the only direct answer. – Matt Cashatt Jan 08 '14 at 16:39
  • 17
    Because the question is five years old, whereas this answer is only two months old. Derp :-P – Riegardt Steyn Jan 15 '14 at 08:21
  • 3
    @heliac still the Stackoverflow thingie should be a question and answers repository and in the spirit the best answer shoudl be on top... (damn, i knew this does not work) – Offler Feb 18 '14 at 13:17
  • 6
    Just in case it helps anybody, the second argument is the file entry. This is the path to which the file will be extracted relative to the unzip folder. In Windows 7, I found that if the file entry is a full path, e.g., @"D:\Temp\file1.pdf", the native Windows extractor fails. You may run into this issue if you simply use the filenames resulting from Directory.GetFiles(). Best to extract the file name using Path.GetFileName() for the file entry argument. – Manish Aug 11 '14 at 21:48
  • 2
    I don't seem to be able to find this in 4.5.2? – user3791372 May 02 '16 at 07:52
  • 2
    @user3791372, you need to explicitly add the reference to system.io.compression. simply having system won't work. – RayLoveless Aug 10 '16 at 16:58
  • 1
    Also remember `ZipFile.CreateFromDirectory(filesDirectory, zipFileName)` can be used to zip a whole directory – Savage Sep 11 '19 at 11:21
  • Looks like it doesn't support passwords if that's a requirement – Noobie3001 Aug 30 '21 at 19:05
  • I found I need zip.Dispose() after the zip.CreateEntryFromFile, else the created file stays open. I previously beleived "using" did the dispose by itself. And, yes, I'm deleting the zip file outside the using clause. – Skyfish Nov 18 '21 at 12:04
88

Are you using .NET 3.5? You could use the ZipPackage class and related classes. Its more than just zipping up a file list because it wants a MIME type for each file you add. It might do what you want.

I'm currently using these classes for a similar problem to archive several related files into a single file for download. We use a file extension to associate the download file with our desktop app. One small problem we ran into was that its not possible to just use a third-party tool like 7-zip to create the zip files because the client side code can't open it -- ZipPackage adds a hidden file describing the content type of each component file and cannot open a zip file if that content type file is missing.

adrianbanks
  • 81,306
  • 22
  • 176
  • 206
Brian Ensink
  • 11,092
  • 3
  • 50
  • 63
  • 6
    Note that this doesn't always work in reverse. Some Zip files will not rehydrate using the ZipPackage class. Files made with ZipPackage will so you should be good. – Craig Jun 02 '09 at 16:59
  • Note that ZipPackage cannot append to an existing zipped package. – ΩmegaMan Oct 23 '13 at 13:53
  • Sigh: "The type or namespace "Packaging" does not exist in namespace "System.IO". – Hot Licks Aug 19 '14 at 22:34
  • 2
    (Answer to the above "sigh": Open "References" and add (illogically enough) "WindowsBase".) – Hot Licks Aug 20 '14 at 16:04
16
    private static string CompressFile(string sourceFileName)
    {
        using (ZipArchive archive = ZipFile.Open(Path.ChangeExtension(sourceFileName, ".zip"), ZipArchiveMode.Create))
        {
            archive.CreateEntryFromFile(sourceFileName, Path.GetFileName(sourceFileName));
        }
        return Path.ChangeExtension(sourceFileName, ".zip");
    }
FLICKER
  • 6,439
  • 4
  • 45
  • 75
12

I was in the same situation, wanting to .NET instead of a third party library. As another poster mentioned above, simply using the ZipPackage class (introduced in .NET 3.5) is not quite enough. There is an additional file that MUST be included in the archive in order for the ZipPackage to work. If this file is added, then the resulting ZIP package can be opened directly from Windows Explorer - no problem.

All you have to do is add the [Content_Types].xml file to the root of the archive with a "Default" node for every file extension you wish to include. Once added, I could browse the package from Windows Explorer or programmatically decompress and read its contents.

More information on the [Content_Types].xml file can be found here: http://msdn.microsoft.com/en-us/magazine/cc163372.aspx

Here is a sample of the [Content_Types].xml (must be named exactly) file:

<?xml version="1.0" encoding="utf-8" ?>
<Types xmlns=
    "http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="xml" ContentType="text/xml" /> 
  <Default Extension="htm" ContentType="text/html" /> 
  <Default Extension="html" ContentType="text/html" /> 
  <Default Extension="rels" ContentType=
    "application/vnd.openxmlformats-package.relationships+xml" /> 
  <Default Extension="jpg" ContentType="image/jpeg" /> 
  <Default Extension="png" ContentType="image/png" /> 
  <Default Extension="css" ContentType="text/css" /> 
</Types>

And the C# for creating a ZIP file:

var zipFilePath = "c:\\myfile.zip"; 
var tempFolderPath = "c:\\unzipped"; 

    using (Package package = ZipPackage.Open(zipFilePath, FileMode.Open, FileAccess.Read)) 
    { 
        foreach (PackagePart part in package.GetParts()) 
        { 
            var target = Path.GetFullPath(Path.Combine(tempFolderPath, part.Uri.OriginalString.TrimStart('/'))); 
            var targetDir = target.Remove(target.LastIndexOf('\\')); 

            if (!Directory.Exists(targetDir)) 
                Directory.CreateDirectory(targetDir); 

            using (Stream source = part.GetStream(FileMode.Open, FileAccess.Read)) 
            { 
                source.CopyTo(File.OpenWrite(target)); 
            } 
        } 
    } 

Note:

Joshua
  • 4,099
  • 25
  • 37
1

Based off Simon McKenzie's answer to this question, I'd suggest using a pair of methods like this:

    public static void ZipFolder(string sourceFolder, string zipFile)
    {
        if (!System.IO.Directory.Exists(sourceFolder))
            throw new ArgumentException("sourceDirectory");

        byte[] zipHeader = new byte[] { 80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        using (System.IO.FileStream fs = System.IO.File.Create(zipFile))
        {
            fs.Write(zipHeader, 0, zipHeader.Length);
        }

        dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
        dynamic source = shellApplication.NameSpace(sourceFolder);
        dynamic destination = shellApplication.NameSpace(zipFile);

        destination.CopyHere(source.Items(), 20);
    }

    public static void UnzipFile(string zipFile, string targetFolder)
    {
        if (!System.IO.Directory.Exists(targetFolder))
            System.IO.Directory.CreateDirectory(targetFolder);

        dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
        dynamic compressedFolderContents = shellApplication.NameSpace(zipFile).Items;
        dynamic destinationFolder = shellApplication.NameSpace(targetFolder);

        destinationFolder.CopyHere(compressedFolderContents);
    }
}
Community
  • 1
  • 1
mccdyl001
  • 182
  • 11
1

There is now an officially documented section in the dotnet docs on how to compress and decompress without third-party libraries. It includes full code samples too!

https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-compress-and-extract-files

Ssh Quack
  • 647
  • 10
  • 13
0

Add these 4 functions to your project:

        public const long BUFFER_SIZE = 4096;
    public static void AddFileToZip(string zipFilename, string fileToAdd)
    {
        using (Package zip = global::System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate))
        {
            string destFilename = ".\\" + Path.GetFileName(fileToAdd);
            Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
            if (zip.PartExists(uri))
            {
                zip.DeletePart(uri);
            }
            PackagePart part = zip.CreatePart(uri, "", CompressionOption.Normal);
            using (FileStream fileStream = new FileStream(fileToAdd, FileMode.Open, FileAccess.Read))
            {
                using (Stream dest = part.GetStream())
                {
                    CopyStream(fileStream, dest);
                }
            }
        }
    }
    public static void CopyStream(global::System.IO.FileStream inputStream, global::System.IO.Stream outputStream)
    {
        long bufferSize = inputStream.Length < BUFFER_SIZE ? inputStream.Length : BUFFER_SIZE;
        byte[] buffer = new byte[bufferSize];
        int bytesRead = 0;
        long bytesWritten = 0;
        while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) != 0)
        {
            outputStream.Write(buffer, 0, bytesRead);
            bytesWritten += bytesRead;
        }
    }
    public static void RemoveFileFromZip(string zipFilename, string fileToRemove)
    {
        using (Package zip = global::System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate))
        {
            string destFilename = ".\\" + fileToRemove;
            Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
            if (zip.PartExists(uri))
            {
                zip.DeletePart(uri);
            }
        }
    }
    public static void Remove_Content_Types_FromZip(string zipFileName)
    {
        string contents;
        using (ZipFile zipFile = new ZipFile(File.Open(zipFileName, FileMode.Open)))
        {
            /*
            ZipEntry startPartEntry = zipFile.GetEntry("[Content_Types].xml");
            using (StreamReader reader = new StreamReader(zipFile.GetInputStream(startPartEntry)))
            {
                contents = reader.ReadToEnd();
            }
            XElement contentTypes = XElement.Parse(contents);
            XNamespace xs = contentTypes.GetDefaultNamespace();
            XElement newDefExt = new XElement(xs + "Default", new XAttribute("Extension", "sab"), new XAttribute("ContentType", @"application/binary; modeler=Acis; version=18.0.2application/binary; modeler=Acis; version=18.0.2"));
            contentTypes.Add(newDefExt);
            contentTypes.Save("[Content_Types].xml");
            zipFile.BeginUpdate();
            zipFile.Add("[Content_Types].xml");
            zipFile.CommitUpdate();
            File.Delete("[Content_Types].xml");
            */
            zipFile.BeginUpdate();
            try
            {
                zipFile.Delete("[Content_Types].xml");
                zipFile.CommitUpdate();
            }
            catch{}
        }
    }

And use them like this:

foreach (string f in UnitZipList)
{
    AddFileToZip(zipFile, f);
    System.IO.File.Delete(f);
}
Remove_Content_Types_FromZip(zipFile);
Teemo
  • 211
  • 2
  • 6
-1

Looks like Windows might just let you do this...

Unfortunately I don't think you're going to get around starting a separate process unless you go to a third party component.

Dave Swersky
  • 34,502
  • 9
  • 78
  • 118