Click here to Skip to main content
15,881,738 members
Articles / Web Development / HTML

Cabinet File (*.CAB) Compression and Extraction

Rate me:
Please Sign up or sign in to vote.
4.93/5 (217 votes)
23 Mar 2012CPOL38 min read 3.1M   26.5K   573  
How to implement creation and extraction of Microsoft CAB files
using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Runtime.InteropServices;
using System.Threading;

namespace Demo
{
	class CabLibDemo
	{
		[DllImport("kernel32.dll", EntryPoint="GetWindowsDirectoryA", CharSet=CharSet.Ansi)]
		private static extern int GetWindowsDirectory(StringBuilder s_Text, int MaxCount); 

		[DllImport("kernel32.dll", EntryPoint="GetCurrentDirectoryA", CharSet=CharSet.Ansi)]
		private static extern int GetCurrentDirectory(int MaxCount, StringBuilder s_Text); 

		[DllImport("kernel32.dll", EntryPoint="SetConsoleTextAttribute")]
		private static extern bool SetConsoleTextAttribute(IntPtr h_ConsoleOutput, Int16 u16_Attributes);

		[DllImport("kernel32.dll", EntryPoint="GetStdHandle")]
		private static extern IntPtr GetStdHandle(int u32_Device);

		const int GREY   = 0x7;
		const int WHITE  = 0xF;
		const int GREEN  = 0xA;
		const int CYAN   = 0xB;
		const int RED    = 0xC;
		const int YELLOW = 0xE;

		[STAThread]
		static void Main(string[] args)
		{
			// ######################### SETTINGS MULTITHREAD DEMO ########################

			// This demo extracts or compresses 2 huge folders multithreaded at the same time.
			// Before setting "true" adapt the paths in the function MultiThread()!
			bool b_MultiThreadCompress = false;
			bool b_MultiThreadExtract  = false;

			// ############################ SETTINGS NORMAL DEMO ##########################

			// Change this to split the archive into multiple files (200000 --> CAB split size = 200kB)
			// ATTENTION: In this case Parameter 1 of CompressFile() MUST contain "%d" or "%02u",...
			int s32_SplitSize = 0x7FFFFFFF;

			// Store filetimes as UTC in CAB files
			bool b_UtcTime = true;

			// Encode ANSI or Unicode filenames in CAB file using UTF8. Read the documentation!
			bool b_EncodeUtf = true;

			// NONE: store files without compression
			// MSZIP, LZX: compression algorithms
			CabLib.Compress.eCompress e_Compress = CabLib.Compress.eCompress.LZX;

			// true -> Don't write any file to disk, all files are passed in OnAfterCopyFile()
			bool b_ExtractToMemory = false;

			// You can specify your own key for CAB encryption here (the longer the more secure, up to 5000 characters)

			String s_EncryptionKey = ""; // to handle CAB files without encryption
//			String s_EncryptionKey = "AH%KJ/76?K�H�\\��dghf7(ZTbjsf82iz<sx87qc5ba&m;-@^l#jeL9/)$D2@rTbZ<�9�}2&";

			// ########################## Initialization ############################

			if (b_MultiThreadCompress)
			{
				StartThread("Compress1");
				StartThread("Compress2");
				return;
			}
			if (b_MultiThreadExtract)
			{
				StartThread("Extract1");
				StartThread("Extract2");
				return;
			}

			StringBuilder s_WinDir = new StringBuilder(500);
			GetWindowsDirectory(s_WinDir, 500);

			String s_Explorer = s_WinDir + "\\Explorer.exe";
			String s_Notepad  = s_WinDir + "\\Notepad.exe";

			StringBuilder s_WorkDir = new StringBuilder(500);
			GetCurrentDirectory(500, s_WorkDir);

			String s_CompressDir = s_WorkDir + "\\_Compressed";
			String s_ExtractDir  = s_WorkDir + "\\_ExtractFile";
			String s_ResourceDir = s_WorkDir + "\\_ExtractResource";
			String s_StreamDir   = s_WorkDir + "\\_ExtractStream";
			String s_UrlDir      = s_WorkDir + "\\_ExtractUrl";

			String s_CompressFile = s_CompressDir + "\\Packed_%02d.cab";

			CabLib.Compress i_Compress = new CabLib.Compress();
			CabLib.Extract  i_Extract  = new CabLib.Extract();

			i_Compress.SetEncryptionKey(s_EncryptionKey);
			i_Extract. SetDecryptionKey(s_EncryptionKey);

			// Call this if you want to change the default compression algorithm (LZX)
			i_Compress.SwitchCompression(e_Compress);

			// Attach compress event handler
			i_Compress.evFilePlaced    += new CabLib.Compress.delFilePlaced   (OnFilePlaced);
			i_Compress.evUpdateStatus  += new CabLib.Compress.delUpdateStatus (OnUpdateStatus);
			// Attach extract event handler
			i_Extract.evBeforeCopyFile += new CabLib.Extract.delBeforeCopyFile(OnBeforeCopyFile);
			i_Extract.evAfterCopyFile  += new CabLib.Extract.delAfterCopyFile (OnAfterCopyFile);
			i_Extract.evProgressInfo   += new CabLib.Extract.delProgressInfo  (OnProgressInfo);
			i_Extract.evCabinetInfo    += new CabLib.Extract.delCabinetInfo   (OnCabinetInfo);
			i_Extract.evNextCabinet    += new CabLib.Extract.delNextCabinet   (OnNextCabinet);

            // ########################## Print CabLib version ############################

			Assembly i_Lib = typeof(CabLib.Extract).Assembly;
			object[] o_Title = i_Lib.GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
			AssemblyTitleAttribute i_Title = (AssemblyTitleAttribute)o_Title[0];
			Version i_Vers = i_Lib.GetName().Version;
			Print(CYAN, i_Title.Title + "  version " + i_Vers.Major + "." + i_Vers.Minor + "\n");

			// ############################# Compress demo ################################

			// This will pack Explorer.exe and Notepad.exe into a CAB file with subfolders

			Print(WHITE,  "----------------------------------------------------------------");
			Print(YELLOW, "                 CAB FILE COMPRESSION (" + e_Compress.ToString() + ")\n");

			try
			{
				ArrayList i_Files = new ArrayList();
				i_Files.Add(new string[] { s_Explorer, @"FileManager\Explorer.exe" });
				i_Files.Add(new string[] { s_Notepad,  @"TextManager\Notepad.exe"  });

				i_Compress.CompressFileList(i_Files, s_CompressFile, b_UtcTime, b_EncodeUtf, s32_SplitSize);

				Print(GREEN, "\nSUCCESS: Compressed Explorer.exe and Notepad.exe into cabinet in\n" + s_CompressDir);
			}
			catch (Exception Ex)
			{
				Print(RED, Ex.Message);
				goto _RESOURCE;
			}

			// ########################## Extract File demo ############################

			// This will decrypt the CAB file if it was encrypted above

			Print(WHITE,  "----------------------------------------------------------------");
			Print(YELLOW, "\n                   CAB FILE EXTRACTION\n");

			// "Packed_%02d.cab" --> "Packed_01.cab"
			s_CompressFile = CabLib.Compress.Sprintf(s_CompressFile, new object[]{1});

			try
			{
				// Print header information
				CabLib.Extract.kHeaderInfo k_Info;
				// ATTENTION: 
				// The following code only demonstrates how to use IsFileCabinet()
				// But it is not necessary to call IsFileCabinet() before every extraction!
				if (i_Extract.IsFileCabinet(s_CompressFile, out k_Info))
				{
					Print(WHITE, "CAB filesize:        "                 + k_Info.u32_FileSize + " Bytes");
					Print(WHITE, "Folder count in CAB: "                 + k_Info.u16_Folders);
					Print(WHITE, "File   count in CAB: "                 + k_Info.u16_Files);
					Print(WHITE, "Set ID:              "                 + k_Info.u16_SetID);
					Print(WHITE, "CAB Index:           "                 + k_Info.u16_CabIndex);
					Print(WHITE, "Has additional header data:          " + k_Info.b_HasReserve);
					Print(WHITE, "Has predecessor in splitted Cabinet: " + k_Info.b_HasPrevious);
					Print(WHITE, "Has successor   in splitted Cabinet: " + k_Info.b_HasNext + "\n");
				}
				else
				{
					Print(RED, "The file is not a valid Cabinet: " + s_CompressFile);
				}
			}
			catch (Exception Ex)
			{
				Print(RED, Ex.Message);
			}

			try
			{
				if (b_ExtractToMemory)
					s_ExtractDir = "MEMORY";

				// Now extract into subdirectory "_ExtractFile" and the corresponding subdirectories in the CAB file
				i_Extract.ExtractFile(s_CompressFile, s_ExtractDir);
			
				Print(GREEN, "\nSUCCESS: Extracted all files from the above compressed cabinet to\n" + s_ExtractDir);
			}
			catch (Exception Ex)
			{
				Print(RED, Ex.Message);
			}

			// ########################## Extract Win32 resource demo ############################

			// This will extract the file "Test.cab" in the embedded Win32 resources in CabLib.dll

			_RESOURCE:
			i_Extract.SetDecryptionKey(""); // the CAB resource is not encrypted
			
			Print(WHITE,  "----------------------------------------------------------------");
			Print(YELLOW, "\n                 WIN32 CAB RESOURCE EXTRACTION\n");

			try
			{
				// Extract only one file from the cabinet which must be in the root folder!!
//				i_Extract.SetSingleFile("Root.txt");

				if (b_ExtractToMemory)
					s_ResourceDir = "MEMORY";

				// Now extract into subdirectory "_ExtractResource" and the corresponding subdirectories in the CAB file
				i_Extract.ExtractResource("CabLib.dll", 101, "CABFILE", s_ResourceDir);

				Print(GREEN, "\nSUCCESS: Extracted all files from Win32 resource Test.cab in CabLib.dll to\n" + s_ResourceDir);
			}
			catch (Exception Ex)
			{
				Print(RED, Ex.Message);
			}

			// ########################## Extract Stream demo ############################

			// This will extract the file "Test.cab" in the embedded .NET resources in CabLibDemo.exe 

			Print(WHITE,  "----------------------------------------------------------------");
			Print(YELLOW, "\n                 .NET CAB STREAM EXTRACTION\n");

			try
			{
				Assembly i_Exe  = Assembly.GetExecutingAssembly();
				Stream   i_Strm = i_Exe.GetManifestResourceStream("CabLibDemo.Resources.Test.cab");

				if (b_ExtractToMemory)
					s_StreamDir = "MEMORY";

				// Now extract into subdirectory "_ExtractStream" and the corresponding subdirectories in the CAB file
				i_Extract.ExtractStream(i_Strm, s_StreamDir);

				Print(GREEN, "\nSUCCESS: Extracted all files from .NET resource Test.cab in CabLibDemo.exe to\n" + s_StreamDir);
			}
			catch (Exception Ex)
			{
				Print(RED, Ex.Message);
			}

			// ########################## Extract URL demo ############################

			// This will download and extract "UrlExtractDemo.cab"

			Print(WHITE,  "----------------------------------------------------------------");
			Print(YELLOW, "\n                      URL EXTRACTION\n");

			try
			{
				// !!!! IMPORTANT: Read in the documentation about the 100 possible ways to build the URL !!!!
				String s_Url = "http://home.arcor.de/starwalker22/Test/UrlExtractDemo.cab";
//				String s_Url = "http://server/Downloads/Installer_1.35.cab";
//				String s_Url =  "ftp://user:password@server/Downloads/Installer_1.35.cab";
//				String s_Url = "file://C:/Downloads/Installer_1.35.cab";

				// Size of blocks to be loaded to memory (HTTP Server must run a script, FTP Server must support "REST")
				// Set zero for entire file download to disk
				// !!!! IMPORTANT: Read in the documentation about the 100 possible types of downloading !!!!
				int s32_BlockSize = 0;

				// If s_LocalFile is an empty string -> create a temporary CAB file, extract it, then delete it.
				// Otherwise download into the specified file, extract it and do NOT delete it.
				string s_LocalFile = "";
//				String s_LocalFile = "C:\\Downloaded.cab"; // Ignored if blocksize > 0

				// Passive mode for FTP downloads
				i_Extract.SetPassiveFtpMode(true);

				// Proxy Format= "http=http://Proxy1.com:8000 https=https://Proxy2.com:443"
				// Proxy = "" --> Use Internet Explorer default settings
				i_Extract.SetProxy("");

				// Modifies the HTTP headers which are sent to the server. (separated by pipe)
				// e.g. "Referer: http://www.test.com|Accept-Language:en"  (no space before or after pipe!!)
				// If the value of a header is empty, the header is removed.
				i_Extract.SetHttpHeaders("");

				if (b_ExtractToMemory)
					s_UrlDir = "MEMORY";

				// !!!! IMPORTANT: Read in the documentation about the 100 possible types of downloading !!!!
				Print(GREY, "Extracting: {0}", s_Url);
				     if (s32_BlockSize>0)      Print(GREY, "Download to memory, Blocksize: {0} Bytes\n", s32_BlockSize);
				else if (s_LocalFile.Length>0) Print(GREY, "Download to : {0}\n", s_LocalFile);
				else                           Print(GREY, "Download to temporary file\n");

				// Now extract into subdirectory "_ExtractUrl"
				i_Extract.ExtractUrl(s_Url, s32_BlockSize, s_LocalFile, s_UrlDir);
				
				// You can reutilize the downloaded CAB data for further extractions:
//				i_Extract.ExtractMoreUrl("C:\\");

				// IMPORTANT: Close all handles and free memory after every URL extraction!!
				i_Extract.CleanUp();

				Print(GREEN, "\nSUCCESS: Extracted all files from downloaded CAB file to\n" + s_UrlDir);
			}
			catch (Exception Ex)
			{
				Print(RED, Ex.Message);
			}

			// ############################## END #######################################

			// Under some cirumstances the DOS window disappears immediately, so you cannot read anything!
			Print(GREY, "\nHit any key to exit");
			Console.Read();
		}

		/// <summary>
		/// The lazy guys at Microsoft forgot to implement coloured text output into .NET framework
		/// </summary>
		static private void Print(Int16 u16_Color, string s_Format, object o_Param)
		{
			Print(u16_Color, string.Format(s_Format, o_Param));
		}
		static private void Print(Int16 u16_Color, string s_Text)
		{
			const int STD_OUTPUT_HANDLE = -11;

			SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), u16_Color); 
			Console.WriteLine(s_Text);
		}


		// ##################### EXTRACT EVENT HANDLER ########################

		/// <summary>
		/// This event handler is called for every extratced file before it is written to disk
		/// return false here if you want a file not to be extracted
		/// </summary>
		static private bool OnBeforeCopyFile(CabLib.Extract.kCabinetFileInfo k_Info)
		{
			string s_File = k_Info.s_File;
			if (s_File[0] > 255) s_File = "Console cannot display Unicode filename.";

			Print(GREY, "Callback OnBeforeCopyFile: " + s_File);
			return true;
		}

		// This function will be called when a file has been succesfully extracted.
		// If Extraction to Memory is enabled, no file is written to disk,
		// instead the entire content of the file is passed in u8_ExtractMem
		// If Extraction to Memory is not enabled, u8_ExtractMem is null
		private static void OnAfterCopyFile(string s_File, Byte[] u8_ExtractMem)
		{
			s_File = Path.GetFileName(s_File);
			if (s_File[0] > 255) s_File = "Console cannot display Unicode filename.";

			Print(GREY, "Callback OnAfterCopyFile:  " + s_File);

			if (u8_ExtractMem != null)
			{
//				string s_FileContent = Encoding.ASCII.GetString(u8_ExtractMem);
			}
		}

		// This callback is called every 200 ms when extracting to disk. (only when huge files are extracted)
		// It shows the progress of the current file in percent ("82.4%")
		// On extraction to memory it will not be called. (It would be nonsene to extract huge files to memory)
		// This callback will not be called for all files that extract faster than 200 ms
		private static void OnProgressInfo(CabLib.Extract.kProgressInfo k_Info)
		{
			Print(GREY, "Callback OnProgressInfo: " + k_Info.fl_Percent.ToString("N1") + "%");
		}

		private static void OnCabinetInfo(CabLib.Extract.kCabinetInfo k_Info)
		{
			Print(GREY, "Callback OnCabinetInfo: Next CAB: '" + k_Info.s_NextCabinet + "'");
		}

		private static void OnNextCabinet(CabLib.Extract.kCabinetInfo k_Info, int s32_FdiError)
		{
			Print(GREY, "Callback OnNextCabinet: Please insert '" + k_Info.s_NextDisk + "'");
		}

		// ##################### COMPRESS EVENT HANDLER ########################

		private static int OnFilePlaced(string s_File, int s32_FileSize, bool bContinuation)
		{
			Print(GREY, "Callback OnFilePlaced:   " + s_File);
			return 0;
		}

		private static int OnUpdateStatus(CabLib.Compress.kCurStatus k_CurStatus)
		{
 			if (k_CurStatus.u32_FolderPercent > 0)
				Print(GREY, "Callback OnUpdateStatus: {0}% completed", k_CurStatus.u32_FolderPercent);
 
			return 0;
		}

		// ###################################################################################################
		// ###################################################################################################
		// ###################################################################################################

		// This is just a demo which compresses 2 huge folders into 2 CAB files at the same time.
		// Or it can extract these CAB files into two new folders at the same time.
		private static void StartThread(string s_Name)
		{
			Thread i_Thread = new Thread(new ThreadStart(MultiThread));
			i_Thread.Name = s_Name;
			i_Thread.Start();
		}

		private static void MultiThread()
		{
			// ATTENTION: The folders you compress must contain less than 2GB data!
			string s_ComprFolder1 = @"C:\Program Files\Babylon6";
			string s_ComprFolder2 = @"C:\Program Files\Common Files";
			string s_CabFile1     = @"D:\Compressed\Babylon.cab";
			string s_CabFile2     = @"D:\Compressed\Common.cab";
			string s_ExtrFolder1  = @"D:\Extracted\Babylon";
			string s_ExtrFolder2  = @"D:\Extracted\Common";
			try 
			{
				CabLib.Compress i_Compress = new CabLib.Compress();
				CabLib.Extract  i_Extract  = new CabLib.Extract();

				switch (Thread.CurrentThread.Name)
				{
					case "Compress1":
						Print(GREY, "Compressing {0}", s_ComprFolder1);
						i_Compress.CompressFolder(s_ComprFolder1, s_CabFile1, "*.*", true, true, 0);
						break;
					case "Compress2":
						Print(GREY, "Compressing {0}", s_ComprFolder2);
						i_Compress.CompressFolder(s_ComprFolder2, s_CabFile2, "*.*", true, true, 0); 
						break;
					case "Extract1":
						Print(GREY, "Extracting {0}", s_CabFile1);
						i_Extract.ExtractFile(s_CabFile1, s_ExtrFolder1);
						break;
					case "Extract2":
						Print(GREY, "Extracting {0}", s_CabFile2);
						i_Extract.ExtractFile(s_CabFile2, s_ExtrFolder2);
						break;
					default:
						return;
				}
				Print(GREEN, "Thread {0} exited successfully.", Thread.CurrentThread.Name);
			}
			catch (Exception Ex) 
			{ 
				Print(RED,    Ex.Message);
				Print(YELLOW, Ex.StackTrace);
			}			
			Console.Read();
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ElmüSoft
Chile Chile
Software Engineer since 40 years.

Comments and Discussions