DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

ファイルやフォルダをNTFS圧縮する

エクスプローラでファイルやフォルダのプロパティを表示させると、その「属性」の「詳細設定」に「内容を圧縮してディスク領域を節約する」という項目があります。これを適用すると、ファイルやフォルダがNTFS圧縮され、圧縮属性が付きます。このような圧縮機能は、Windows NT 3.51以降のNTFSでサポートされています。

このようにファイルやフォルダをNTFS圧縮する方法は、.NET Frameworkでは用意されていません。ここではWin32 APIやWMIなどを使ってNTFS圧縮する方法を紹介します。

DeviceIoControl関数を使用する

Win32 APIのDeviceIoControl関数を使って、ファイルをNTFS圧縮する例を以下に示します。なお圧縮を解除するときは、COMPRESSION_FORMAT_DEFAULTの代わりにCOMPRESSION_FORMAT_NONEを指定してください。

VB.NET
コードを隠すコードを選択
'Imports System.Runtime.InteropServices
'Imports System.IO

Private Const FSCTL_SET_COMPRESSION As Integer = &H9C040
Private Const COMPRESSION_FORMAT_NONE As Short = 0
Private Const COMPRESSION_FORMAT_DEFAULT As Short = 1

<DllImport("kernel32.dll", SetLastError:=True)> _
Private Shared Function DeviceIoControl( _
    ByVal hDevice As Microsoft.Win32.SafeHandles.SafeFileHandle, _
    ByVal dwIoControlCode As Integer, _
    ByRef lpInBuffer As Short, _
    ByVal nInBufferSize As Integer, _
    ByVal lpOutBuffer As IntPtr, _
    ByVal nOutBufferSize As Integer, _
    ByRef lpBytesReturned As Integer, _
    ByVal lpOverlapped As IntPtr) As Integer
End Function

'.NET Framework 1.1 以前
'<DllImport("kernel32.dll", SetLastError:=True)> _
'Private Shared Function DeviceIoControl( _
'    ByVal hDevice As IntPtr, _
'    ByVal dwIoControlCode As Integer, _
'    ByRef lpInBuffer As Short, _
'    ByVal nInBufferSize As Integer, _
'    ByVal lpOutBuffer As IntPtr, _
'    ByVal nOutBufferSize As Integer, _
'    ByRef lpBytesReturned As Integer, _
'    ByVal lpOverlapped As IntPtr) As Integer
'End Function

''' <summary>
''' ファイルをNTFS圧縮する
''' </summary>
''' <param name="filePath">圧縮するファイルのパス</param>
Public Shared Sub CompressFile(ByVal filePath As String)
    'ファイルを開く
    Dim fs As New FileStream(filePath, _
        FileMode.Open, FileAccess.ReadWrite, FileShare.None)

    '圧縮する
    Dim lpBytesReturned As Integer = 0
    Dim lpInBuffer As Short = COMPRESSION_FORMAT_DEFAULT
    Dim res As Integer = DeviceIoControl( _
        fs.SafeFileHandle, _
        FSCTL_SET_COMPRESSION, _
        lpInBuffer, _
        2, _
        IntPtr.Zero, _
        0, _
        lpBytesReturned, _
        IntPtr.Zero)

    '.NET Framework 1.1 以前
    'Dim res As Integer = DeviceIoControl( _
    '    fs.Handle, _
    '    FSCTL_SET_COMPRESSION, _
    '    lpInBuffer, _
    '    2, _
    '    IntPtr.Zero, _
    '    0, _
    '    lpBytesReturned, _
    '    IntPtr.Zero)

    '失敗したとき
    If res <> 0 Then
        Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error())
    End If

    '閉じる
    fs.Dispose()
End Sub
C#
コードを隠すコードを選択
//using System;
//using System.Runtime.InteropServices;
//using System.IO;

private const int FSCTL_SET_COMPRESSION = 0x9C040;
private const short COMPRESSION_FORMAT_NONE = 0;
private const short COMPRESSION_FORMAT_DEFAULT = 1;

[DllImport("kernel32.dll", SetLastError = true)]
private static extern int DeviceIoControl(
    Microsoft.Win32.SafeHandles.SafeFileHandle hDevice,
    int dwIoControlCode,
    ref short lpInBuffer,
    int nInBufferSize,
    IntPtr lpOutBuffer,
    int nOutBufferSize,
    ref int lpBytesReturned,
    IntPtr lpOverlapped);

//.NET Framework 1.1 以前
//[DllImport("kernel32.dll", SetLastError = true)]
//private static extern int DeviceIoControl(
//    IntPtr hDevice,
//    int dwIoControlCode,
//    ref short lpInBuffer,
//    int nInBufferSize,
//    IntPtr lpOutBuffer,
//    int nOutBufferSize,
//    ref int lpBytesReturned,
//    IntPtr lpOverlapped);

/// <summary>
/// ファイルをNTFS圧縮する
/// </summary>
/// <param name="filePath">圧縮するファイルのパス</param>
public static void CompressFile(string filePath)
{
    //ファイルを開く
    FileStream fs = new FileStream(filePath,
        FileMode.Open, FileAccess.ReadWrite, FileShare.None);

    //圧縮する
    int lpBytesReturned = 0;
    short lpInBuffer = COMPRESSION_FORMAT_DEFAULT;
    int res = DeviceIoControl(
        fs.SafeFileHandle,
        FSCTL_SET_COMPRESSION,
        ref lpInBuffer,
        sizeof(short),
        IntPtr.Zero, 0,
        ref lpBytesReturned,
        IntPtr.Zero);

    //.NET Framework 1.1 以前
    //int res = DeviceIoControl(
    //    fs.Handle,
    //    FSCTL_SET_COMPRESSION,
    //    ref lpInBuffer,
    //    sizeof(short),
    //    IntPtr.Zero, 0,
    //    ref lpBytesReturned,
    //    IntPtr.Zero);

    //失敗したとき
    if (res != 0)
    {
        Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
    }

    //閉じる
    fs.Dispose();
}

WMIを使用する

WMIのWin32_Directoryクラスを使えばフォルダの圧縮ができます。指定したフォルダと、それ以下にあるすべてのファイルとフォルダが圧縮されます。

Win32_Directoryクラスを使ってフォルダを圧縮する例を示します。ここでは、圧縮されていないときは圧縮し、圧縮されているときは解除しています。なお、System.Management.dllを参照設定に追加する必要があります。

VB.NET
コードを隠すコードを選択
'圧縮するフォルダ名
Dim dirPath As String = "C:\test"

'ManagementObjectオブジェクトを取得する
Dim mo As New ManagementObject("Win32_Directory.Name='" & dirPath & "'")
'圧縮されていないときは圧縮し、されているときは解除する
Dim mbo As ManagementBaseObject
If CBool(mo.Properties("Compressed").Value) Then
    '圧縮を解除する
    mbo = mo.InvokeMethod("Uncompress", Nothing, Nothing)
Else
    '圧縮する
    mbo = mo.InvokeMethod("Compress", Nothing, Nothing)
End If

'結果を取得する
Dim ret As Long = Convert.ToInt64(mbo.Properties("ReturnValue").Value)
If ret = 0 Then
    Console.WriteLine("成功しました")
Else
    Console.WriteLine("失敗しました")
End If

mo.Dispose()
C#
コードを隠すコードを選択
//using System.Management;
//using System;

//圧縮するフォルダ名
string dirPath = @"C:\test";

//ManagementObjectオブジェクトを取得する
ManagementObject mo = new ManagementObject(
    "Win32_Directory.Name='" + dirPath + "'");
//圧縮されていないときは圧縮し、されているときは解除する
ManagementBaseObject mbo;
if ((bool)mo.Properties["Compressed"].Value)
{
    //圧縮を解除する
    mbo = mo.InvokeMethod("Uncompress", null, null);
}
else
{
    //圧縮する
    mbo = mo.InvokeMethod("Compress", null, null);
}

//結果を取得する
uint ret = (uint)mbo.Properties["ReturnValue"].Value;
if (ret == 0)
{
    Console.WriteLine("成功しました");
}
else
{
    Console.WriteLine("失敗しました");
}

mo.Dispose();

ファイルを圧縮するには、Win32_Directoryクラスの代わりにCIM_DataFileクラスを使用します。使い方は、先に示したWin32_Directoryクラスの例と同じですので、省略します。

compact.exeを使用する

DOSコマンド"compact"を使用してNTFS圧縮することができます。compactはWindows 2000以上のOSで使用できます。

フォルダ"C:\test"と、それ以下のすべてのファイルを圧縮する例を示します。

VB.NET
コードを隠すコードを選択
Dim psi As New System.Diagnostics.ProcessStartInfo()
psi.FileName = "compact.exe"
'コマンドラインを指定する
psi.Arguments = "/c /s:""C:\test"""
'ウィンドウを表示しないようにする(こうしても表示される)
psi.CreateNoWindow = True
'実行する
Dim p As System.Diagnostics.Process = System.Diagnostics.Process.Start(psi)
C#
コードを隠すコードを選択
System.Diagnostics.ProcessStartInfo psi =
    new System.Diagnostics.ProcessStartInfo();
psi.FileName = "compact.exe";
//コマンドラインを指定する
psi.Arguments = @"/c /s:""C:\test""";
//ウィンドウを表示しないようにする(こうしても表示される)
psi.CreateNoWindow = true;
//実行する
System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);

Windows XPで「compact /?」を実行したときに表示されるshutdown.exeの使用法は、以下の通りです。

NTFS パーティション上のファイルの圧縮状態を表示または変更します。

COMPACT [/C | /U] [/S[:ディレクトリ]] [/A] [/I] [/F] [/Q] [ファイル名 [...]]

  /C           指定されたファイルを圧縮します。後で追加されたファイルが
               圧縮されるように、ディレクトリはマークされます。
  /U           指定されたファイルを圧縮解除します。後で追加されたファイル
               が圧縮されないように、ディレクトリはマークされます。
  /S           指定されたディレクトリおよびそのサブディレクトリにある
               ファイルに、指定された操作を実行します。
               "ディレクトリ" の既定値は現在のディレクトリです。
  /A           隠しファイルやシステム ファイルも表示します。既定の設定では
               これらのファイルは省略されます。
  /I           エラーが発生した場合でも、指定された処理を続行します。
               既定の設定では、エラーが発生すると COMPACT は停止されます。
  /F           圧縮済みのファイルも含めて、指定されたすべてのファイルを
               強制圧縮します。既定の設定では圧縮済みのファイルはスキップ
               されます。
  /Q           重要な情報だけを報告します。
  ファイル名   パターン、ファイル、またはディレクトリを指定します。

  パラメータを指定せずに COMPACT を実行すると、現在のディレクトリと
  ディレクトリに含まれるすべてのファイルの圧縮状態を表示します。
  複数のファイル名やワイルド カードを指定できます。複数のパラメータを
  指定する場合は、パラメータをスペースで区切ってください。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。