BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Input / Output
.NET 1.1+

Enabling and Disabling File Compression

Microsoft Windows allows files to be compressed on disk to save space, at the expense of some performance. Using the .NET framework and Platform Invocation Services, the compression status can be changed programmatically.

File Compression

Microsoft operating systems have included the facility to compress files since MS-DOS 6.0 introduced DoubleSpace, which later became DriveSpace. In modern versions of Windows the compression can be enabled and disabled for individual files or folders. By compressing files you can save disk space, allowing more information to be stored. However, compressed files are generally slower to work with as they must be decompressed before use.

If you wish to compress a file you can do so using an option in the Advanced section of its Properties dialog box. This is a simple matter of ticking or clearing a checkbox. You can also work with the setting programmatically from .NET framework-based applications. You can determine if a file is compressed using the File.GetAttributes method. To change the setting, however, requires an alternative approach. One option is to call a Windows API function using Platform Invocation Services (P/Invoke).

In this article we'll look at how you can compress and decompress files using a sample console application. We'll be using types from the System.IO and System.Runtime.InteropServices namespaces so add the following using directives:

using System.IO;
using System.Runtime.InteropServices;

DeviceIoControl Function

To change the compression status of a file we'll use the DeviceIoControl API function. This allows you to send messages to device drivers and has a wide range of uses. To make it available, add the following declarations. The first references the function in the kernel32 library. We'll use the three constants to control compression.

[DllImport("kernel32.dll")]
public static extern bool DeviceIoControl(
    IntPtr hDevice, uint dwIoControlCode, ref short lpInBuffer, uint nInBufferSize,
    IntPtr lpOutBuffer, uint nOutBufferSize, ref uint lpBytesReturned,
    IntPtr lpOverlapped);

private const uint FSCTL_SET_COMPRESSION = 0x0009C040;
private const short COMPRESSION_FORMAT_NONE = 0;
private const short COMPRESSION_FORMAT_DEFAULT = 1;

The parameters of the function are as follows:

  • hDevice. This parameter is used to determine which device a message should be sent to. When working with files, the handle of a file is provided.
  • dwIoControlCode. The control code determines the command or type of message that you wish to send to the device indicated by the first argument. To add or remove compression from a file we use the FSCTL_SET_COMPRESSION control code, which we have defined as a constant.
  • lpInBuffer. This argument is used to provide a pointer to an input buffer that contains additional data for the command or message. We'll use this to specify whether we wish to compress or decompress files.
  • nInBufferSize. Specifies the length of the input buffer in bytes. The data we will be using will come from one of the 16-bit integer constants. This means that we will pass the value two.
  • lpOutBuffer. A pointer to an output buffer that is used when sending messages that return additional information. We don't need this for compressing files so will use IntPtr.Zero.
  • nOutBufferSize. Specifies the length of the output buffer. As we don't require such a buffer we'll pass zero to this parameter.
  • lpBytesReturned. This parameter provides a pointer to a variable that will receive the number of bytes of data returned by the function. Again, this is not necessary for our purpose. We will pass a variable by reference but will not check its contents after the call.
  • lpOverlapped. The final parameter is used for asynchronous, or overlapped operations. We will be working synchronously so will pass IntPtr.Zero.

DeviceIoControl returns a Boolean value. If true, the change was applied successfully. If false, an error occurred and the compression status remains unchanged.

We can use this information to create a method that simplifies calls to the DeviceIoControl function. The method below requires a file handle and a compression status value. All of the other parameters are given standard values.

private static bool SetCompression(IntPtr handle, short compression)
{
    uint lpBytesReturned = 0;
    return DeviceIoControl(
        handle, FSCTL_SET_COMPRESSION, ref compression, 2, IntPtr.Zero,
        0, ref lpBytesReturned, IntPtr.Zero);
}

Compressing and Decompressing Files

We can now create methods to compress or decompress a file. Rather than requiring consumers of the methods to provide file handles and to understand the available compression options, we'll require only a string containing the path of the file to be modified. Within the methods this file is opened for reading so that we can obtain a handle. We then call the SetCompression method using the handle and appropriate compression constant to perform the update.

The methods are as follows:

private static bool SetFileCompressed(string path)
{
    using (FileStream fs = OpenFileStream(path))
    {
        return SetCompression(fs.Handle, COMPRESSION_FORMAT_DEFAULT);
    }
}

private static bool SetFileUncompressed(string path)
{
    using (FileStream fs = OpenFileStream(path))
    {
        return SetCompression(fs.Handle, COMPRESSION_FORMAT_NONE);
    }
}

private static FileStream OpenFileStream(string path)
{
    return File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}

To test the new methods, add the following code to the Main method of the console application. This code first compresses then decompresses the file named in the path variable, which you should change to the path of an existing test file. Try running the code and checking the compression status of the file at each stage of the process.

string path = @"c:\Test\Test.txt";
            
Console.WriteLine("Press Enter to compress the file");
Console.ReadLine();

if (SetFileCompressed(path))
    Console.WriteLine("File is compressed");
else
    Console.WriteLine("Failed to compress");
            
Console.WriteLine("Press Enter to decompress the file");
Console.ReadLine();

if (SetFileUncompressed(path))
    Console.WriteLine("File is uncompressed");
else
    Console.WriteLine("Failed to decompress");

Console.ReadLine();
7 March 2013