C# Zip File Compression Register - Wednesday, July 24, 2013
Ad Minimize

Encrypt your C# and VB.Net DLL files to protect your code from decompilers, competitors, hackers, and viruses.

Assembly Lockbox... The anti-Decompiler.

ALBBoxMediumTransparent.gif

Compress Zip files with Windows Shell API and C# Minimize

SampleSoftware.gif Download demo project - 54 Kb

CompressWithWindowsShellAPIandCSharp.PNG

Introduction

This is a follow up article to the one that I wrote about decompressing Zip files. With this code you can use the Windows Shell API in C# to compress Zip files and do so without having to show the Copy Progress window shown above. Normally when you use the Shell API to compress a Zip file, it will show a Copy Progress window even when you set the options to tell Windows not to show it. To get around this, you move the Shell API code to a separate executable and then launch that executable using the .NET Processclass being sure to set the process window style to 'Hidden'.

Background

Ever needed to compress Zip files and needed a better Zip than what comes with many of the free compression libraries out there? I.e. you needed to compress folders and subfolders as well as files. Windows Zipping can compress more than just individual files. All you need is a way to programmatically get Windows to silently compress these Zip files. Of course you could spend $300 on one of the commercial Zip components, but it's hard to beat free if all you need is to compress folder hierarchies.

Using the code

The following code shows how to use the Windows Shell API to compress a Zip file. First you create an empty Zip file. To do this create a properly constructed byte array and then save that array as a file with a '.zip' extension. How did I know what bytes to put into the array? Well I just used Windows to create a Zip file with a single file compressed inside. Then I opened the Zip with Windows and deleted the compressed file. That left me with an empty Zip. Next I opened the empty Zip file in a hex editor (Visual Studio) and looked at the hex byte values and converted them to decimal with Windows Calc and copied those decimal values into my byte array code. The source folder points to a folder you want to compress. The destination folder points to the empty Zip file you just created. This code as is will compress the Zip file, however it will also show the Copy Progress window. To make this code work, you will also need to set a reference to a COM library. In the References window, go to the COM tab and select the library labeled 'Microsoft Shell Controls and Automation'.



using System;
using System.IO;

namespace zip
{
     /// <summary>
     /// Summary description for Class1.
     /// </summary>
     class Class1
     {
          /// <summary>
          /// The main entry point for the application.
          /// </summary>
          [STAThread]
          static void Main(string[] args)
          {
               
               //Create an empty zip file
               byte[] emptyzip = 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};

               FileStream fs = File.Create(args[1]);
               fs.Write(emptyzip, 0, emptyzip.Length);
               fs.Flush();
               fs.Close();
               fs = null;

               //Copy a folder and its contents into the newly created zip file
               Shell32.ShellClass sc = new Shell32.ShellClass();
               Shell32.Folder SrcFlder = sc.NameSpace(args[0]);
               Shell32.Folder DestFlder = sc.NameSpace(args[1]); 
               //Shell32.FolderItems items = SrcFlder.Items();

               //All of the _DELETE_ME_ stuff is because Windows pops up a
               //message box about empty folders not being able to be added
               //to zip folders. So we make the folders NOT empty by adding
               //the _DELETE_ME_ files before the copy into zip folder
               //and then remove the _DELETE_ME_ files afterwards from the
               //source folder structure and from the zip file
               
               

               //Add _DELETE_ME_ file to each empty folder
               AddNULLFiles(args[0]);
               
               //The '3' version of FolderItems has a method called Filter
               //Filter allows you to demand that hidden files also be included in the list
               //I ran into this because I was ziping visual studio project directories
               //which have hidden files that were not being counted but were being copied
               //which caused an infinite loop below at the for( ; ; )
               //Actually SOME hidden files were being copied into the zip and some were not
               //So I am passing the '3' version of FolderItems into the CopyHere method also
               //which seemed to force it to include all hidden files in the copy
               //This resulted in the number of files in the source and the destination
               //zip to be equal as soon as the zip process is finished.
               Shell32.Folder3 DestFlder3 = (Shell32.Folder3)sc.NameSpace(args[0]);
               Shell32.FolderItems3 items3 = (Shell32.FolderItems3) DestFlder3.Items();
               int SHCONTF_INCLUDEHIDDEN = 128;
               int SHCONTF_FOLDERS = 32;
               int SHCONTF_NONFOLDERS = 64;
               //"*" == all files
               items3.Filter(SHCONTF_INCLUDEHIDDEN | SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, "*");

               


               //We know when the zip process is complete by checking
               //the number of files in the original source location
               //with the number of files in the zip folder
               //When they are equal we are done.

               //Count the number of FolderItems in the original source location
               int OriginalItemCount = RecurseCount3(items3);

               //Start the ziping
               DestFlder.CopyHere((Shell32.FolderItems3)items3, 1024);

               //Timeout period... if the compression is not done within this time
               //limit then the zip.exe shuts down and the ziping is stoped
               DateTime timeoutDeadline = DateTime.Now.AddMinutes(30);

               //Wait until the ziping is done.
               for( ;;  )
               {
                    //Are we past the deadline?
                    if(DateTime.Now > timeoutDeadline)
                    {
                         break;
                    }

                    //Check the number of items in the new zip to see if it matches
                    //the number of items in the original source location

                    //Only check the item count every 5 seconds
                    System.Threading.Thread.Sleep(5000);

                    int ZipFileItemCount = RecurseCount(DestFlder.Items());

                    if(OriginalItemCount == ZipFileItemCount)
                    {
                         break;
                    }
               }     
               
          
               //Remove all _DELETE_ME_ files from the source
               DeleteNULLFiles(SrcFlder.Items());

               //Remove all _DELETE_ME_ files from the zip file

               //First create a zip_temp folder where the zip.exe is at so we can
               //cut paste from the zip folder into this zip_temp folder
               Shell32.Folder MoveToFolder = sc.NameSpace(AppDomain.CurrentDomain.BaseDirectory);
               MoveToFolder.NewFolder("zip_temp"0);

               Shell32.FolderItem TempFolder = null;

               //Find the zip_temp folder
               foreach(Shell32.FolderItem item in MoveToFolder.Items())
               {
                    if(item.Name == "zip_temp")
                    {
                         TempFolder = item;
                    }
               }

               DeleteNULLFilesFromZip(DestFlder.Items(), TempFolder);
          }

          private static void DeleteNULLFilesFromZip(Shell32.FolderItems Source, Shell32.FolderItem TempFolder)
          {
               Shell32.ShellClass sc = new Shell32.ShellClass();

               //for each file that we find with the name _DELETE_ME_ cut and
               //paste it into the TempFolder
               foreach(Shell32.FolderItem item in Source)
               {
                    if(item.IsFolder == true)
                    { 
                         DeleteNULLFilesFromZip(((Shell32.Folder)item.GetFolder).Items(), TempFolder);
                    }
                    else
                    { 
                         if(item.Name == "_DELETE_ME_")
                         {
                              //If there is already a file there by that name then delete it
                              if(File.Exists(AppDomain.CurrentDomain.BaseDirectory + "zip_temp\\_DELETE_ME_") == true)
                              {
                                   File.Delete(AppDomain.CurrentDomain.BaseDirectory + "zip_temp\\_DELETE_ME_");
                              }                              
                              //Move file out of zip
                              item.InvokeVerb("Cut");
                              TempFolder.InvokeVerb("Paste");                              
                         }
                    }
               }

               //Once that is all done remove all files from the zip_temp folder
               //and then delete the zip_temp folder
               if(File.Exists(AppDomain.CurrentDomain.BaseDirectory + "zip_temp\\_DELETE_ME_") == true)
               {
                    File.Delete(AppDomain.CurrentDomain.BaseDirectory + "zip_temp\\_DELETE_ME_");
               }

               //Delete the zip_temp folder
               if(Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "zip_temp") == true)
               {
                    Directory.Delete(AppDomain.CurrentDomain.BaseDirectory + "zip_temp"true);
               }
          }


          //Add _DELETE_ME_ files to each empty folder
          static private void AddNULLFiles(string CurrentDir)
          {
               //add A NULL file to empty dirs
               string[] files=System.IO.Directory.GetFiles(CurrentDir,"*.*");
               //Now recurse to sub dirs
               string[] subdirs=System.IO.Directory.GetDirectories(CurrentDir);
               foreachstring dir in subdirs)
               {
                    AddNULLFiles(dir);
               }
               if((files.Length==0)&&(subdirs.Length==0))
               {
                    FileStream fs = File.Create(CurrentDir+"\\_DELETE_ME_");
                    fs.Close();
                    fs = null;
               }
          }
          
          //Remove all _DELETE_ME_ files from all folders
          private static void DeleteNULLFiles(Shell32.FolderItems Source)
          {
               foreach(Shell32.FolderItem item in Source)
               {
                    if(item.IsFolder == true)
                    { 
                         DeleteNULLFiles(((Shell32.Folder)item.GetFolder).Items());
                    }
                    else
                    { 
                         if(item.Name.EndsWith("_DELETE_ME_"))
                         {
                              System.IO.File.Delete(item.Path);
                         }
                    }
               }
          }

          

          //Get the number of files and folders in the source location
          //including all subfolders
          private static int RecurseCount(Shell32.FolderItems Source)
          {
               int ItemCount = 0;

               foreach(Shell32.FolderItem item in Source)
               {
                    if(item.IsFolder == true)
                    {
                         //Add one for this folder
                         ItemCount++;
                         //Then continue walking down the folder tree
                         ItemCount += RecurseCount(((Shell32.Folder)item.GetFolder).Items());                         
                    }
                    else
                    {                         
                         //Add one for this file
                         ItemCount++;                         
                    }
               }

               return ItemCount;
          }

          //Get the number of files and folders in the source location
          //including all subfolders and hidden files
          private static int RecurseCount3(Shell32.FolderItems3 Source)
          {
               int ItemCount = 0;

               foreach(Shell32.FolderItem item in Source)
               {
                    if(item.IsFolder == true)
                    {
                         //Add one for this folder
                         ItemCount++;
                         Shell32.FolderItems3 items3 = (Shell32.FolderItems3)((Shell32.Folder3)item.GetFolder).Items();
                         int SHCONTF_INCLUDEHIDDEN = 128;
                         int SHCONTF_FOLDERS = 32;
                         int SHCONTF_NONFOLDERS = 64;
                         items3.Filter(SHCONTF_INCLUDEHIDDEN | SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, "*");
                         //Then continue walking down the folder tree
                         ItemCount += RecurseCount3(items3);                         
                    }
                    else
                    {                         
                         //Add one for this file
                         ItemCount++;                         
                    }
               }

               return ItemCount;
          }

          
     }
}

The sample solution included with this article shows how to put this code into a console application and then launch this console app to compress the Zip without showing the Copy Progress window.

The code below shows a button click event handler that contains the code used to launch the console application so that there is no UI during the compress:

private void btnUnzip_Click(object sender, System.EventArgs e)
{
    //Test to see if the user entered a zip file name
    if(txtZipFileName.Text.Trim() == "")
    {
        MessageBox.Show("You must enter what" + 
               " you want the name of the zip file to be");
        //Change the background color to cue the user to what needs fixed
        txtZipFileName.BackColor = Color.Yellow;
        return;
    }
    else
    {
        //Reset the background color
        txtZipFileName.BackColor = Color.White;
    }

    //Launch the zip.exe console app to do the actual zipping
    System.Diagnostics.ProcessStartInfo i =
        new System.Diagnostics.ProcessStartInfo(
        AppDomain.CurrentDomain.BaseDirectory + "zip.exe");
    i.CreateNoWindow = true;
    string args = "";

    
    if(txtSource.Text.IndexOf(" ") != -1)
    {
        //we got a space in the path so wrap it in double qoutes
        args += "\"" + txtSource.Text + "\"";
    }
    else
    {
        args += txtSource.Text;
    }

    string dest = txtDestination.Text;

    if(dest.EndsWith(@"\") == false)
    {
        dest += @"\";
    }

    //Make sure the zip file name ends with a zip extension
    if(txtZipFileName.Text.ToUpper().EndsWith(".ZIP") == false)
    {
        txtZipFileName.Text += ".zip";
    }

    dest += txtZipFileName.Text;

    if(dest.IndexOf(" ") != -1)
    {
        //we got a space in the path so wrap it in double qoutes
        args += " " + "\"" + dest + "\"";
    }
    else
    {
        args += " " + dest;
    }

    i.Arguments = args;
    

    //Mark the process window as hidden so
    //that the progress copy window doesn't show
    i.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;    
    System.Diagnostics.Process p = System.Diagnostics.Process.Start(i);
    p.WaitForExit();
    MessageBox.Show("Complete");
}

Points of Interest

  • It's free!
  • You can use Windows to create the Zip file instead of an expensive Zip library to work with folder hierarchies.
  • Works with or without showing the Copy Progress window.
  • Uses the Windows Shell API which has a lot of of interesting Windows integration possibilities.

**************************************
CodeTalk <<< Check this out if you write code!
Code Encrypter instead of Obfuscator for C# or VB.NET

Copyright (c) 2013 GeraldGibson.Net - DotNetNuke