Сжатие папки с использованием сжатия NTFS в .NET.

Я хочу сжать папку, используя сжатие NTFS в .NET. Я нашел этот пост, но он не работает. Он генерирует исключение ( "Недопустимый параметр" ).

DirectoryInfo directoryInfo = new DirectoryInfo( destinationDir );
if( ( directoryInfo.Attributes & FileAttributes.Compressed ) != FileAttributes.Compressed )
{
   string objPath = "Win32_Directory.Name=" + "\"" + destinationDir + "\"";
   using( ManagementObject dir = new ManagementObject( objPath ) )
   {
      ManagementBaseObject outParams = dir.InvokeMethod( "Compress", null, null );
      uint ret = (uint)( outParams.Properties["ReturnValue"].Value );
   }
}

Кто-нибудь знает, как включить сжатие NTFS в папке?

Ответ 1

Использование P/Invoke, по моему опыту, обычно проще, чем WMI. Я считаю, что следующее должно работать:

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

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

public static bool EnableCompression(SafeFileHandle handle)
{
    int lpBytesReturned = 0;
    short lpInBuffer = COMPRESSION_FORMAT_DEFAULT;

    return DeviceIoControl(handle, FSCTL_SET_COMPRESSION,
        ref lpInBuffer, sizeof(short), IntPtr.Zero, 0,
        ref lpBytesReturned, IntPtr.Zero) != 0;
}

Поскольку вы пытаетесь установить это в каталоге, вам, вероятно, понадобится использовать P/Invoke для вызова CreateFile с помощью FILE_FLAG_BACKUP_SEMANTICS, чтобы получить SafeFileHandle в каталоге.

Также обратите внимание, что установка сжатия в каталоге в NTFS не сжимает все содержимое, а только создает новые файлы как сжатые (то же самое верно для шифрования). Если вы хотите сжать весь каталог, вам нужно пройти весь каталог и вызвать DeviceIoControl для каждого файла/папки.

Ответ 2

Я проверил код и он alt text!

  • Удостоверьтесь, что он работает для вас с gui. Возможно, размер блока выделения слишком большой для сжатия. Или у вас недостаточно прав.
  • Для вашего назначения используйте формат: "c:/temp/testcomp" с косой чертой.

Полный код:

using System.IO;
using System.Management;

class Program
{
    static void Main(string[] args)
    {
        string destinationDir = "c:/temp/testcomp";
        DirectoryInfo directoryInfo = new DirectoryInfo(destinationDir);
        if ((directoryInfo.Attributes & FileAttributes.Compressed) != FileAttributes.Compressed)
        {
            string objPath = "Win32_Directory.Name=" + "\"" + destinationDir + "\"";
            using (ManagementObject dir = new ManagementObject(objPath))
            {
                ManagementBaseObject outParams = dir.InvokeMethod("Compress", null, null);
                uint ret = (uint)(outParams.Properties["ReturnValue"].Value);
            }
        }
     }
}

Ответ 3

При создании строки Win32_Directory.Name =... вам нужно удвоить обратную косую черту, так что путь C:\Foo\Bar будет создан как:

Win32_Directory.Name = "C:\\\\ Foo Bar",

или используя код примера:

string objPath = "Win32_Directory.Name = \" C: \\\\ Foo \\\\ Bar\"";

По-видимому, строка передается в некоторый процесс, который ожидает escape-форму строки пути.

Ответ 4

Я не верю, что есть способ установить сжатие папки в платформе .NET, поскольку документы (раздел замечаний) утверждают, что это невозможно сделать через File.SetAttributes. Похоже, что это доступно только в Win32 API, используя функцию DeviceIoControl. Все еще можно сделать это через .NET, используя PInvoke.

Как только вы знакомы с PInvoke в общем, посмотрите ссылку pinvoke.net, в которой обсуждается, что подпись должна выглядеть так, чтобы это произошло.

Ответ 5

Это небольшая адаптация ответа Игаля Сербана. Я столкнулся с тонкой проблемой, когда Name должен быть в очень специфическом формате. Поэтому я добавил магию Replace("\\", @"\\").TrimEnd('\\') для нормализации пути во-первых, я также немного очистил код.

var dir = new DirectoryInfo(_outputFolder);

if (!dir.Exists)
{
    dir.Create();
}

if ((dir.Attributes & FileAttributes.Compressed) == 0)
{
    try
    {
        // Enable compression for the output folder
        // (this will save a ton of disk space)

        string objPath = "Win32_Directory.Name=" + "'" + dir.FullName.Replace("\\", @"\\").TrimEnd('\\') + "'";

        using (ManagementObject obj = new ManagementObject(objPath))
        {
            using (obj.InvokeMethod("Compress", null, null))
            {
                // I don't really care about the return value, 
                // if we enabled it great but it can also be done manually
                // if really needed
            }
        }
    }
    catch (Exception ex)
    {
        System.Diagnostics.Trace.WriteLine("Cannot enable compression for folder '" + dir.FullName + "': " + ex.Message, "WMI");
    }
}

Ответ 6

Существует гораздо более простой способ, который я использую в 64-разрядной версии Windows 8, переписанный для VB.NET. Наслаждайтесь.

    Dim Path as string = "c:\test"
    Dim strComputer As String = "."
    Dim objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    Dim colFolders = objWMIService.ExecQuery("Select * from Win32_Directory where name = '" & Replace(path, "\", "\\") & "'")
    For Each objFolder In colFolders
        objFolder.Compress()
    Next

отлично работает для меня. Chagne.\Root to\pcname\root, если вам нужно сделать это на другом компьютере. Используйте с осторожностью.