在 .NET 中使用 NTFS 压缩来压缩文件夹

问题描述 投票:0回答:7

我想在.NET 中使用 NTFS 压缩来压缩文件夹。我找到了这篇文章,但它不起作用。它抛出异常(“无效参数”)。

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 压缩吗?

c# .net windows ntfs
7个回答
16
投票

根据我的经验,使用 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。


14
投票

我已经测试了代码,它
(来源:typepad.com

  • 确保它适用于您的 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=" + "'" + directoryInfo.FullName.Replace("\\", @"\\").TrimEnd('\\') + "'";
            using (ManagementObject dir = new ManagementObject(objPath))
            {
                ManagementBaseObject outParams = dir.InvokeMethod("Compress", null, null);
                uint ret = (uint)(outParams.Properties["ReturnValue"].Value);
            }
        }
     }
}

2
投票

有一种更简单的方法,我在 Windows 8 64 位中使用,为 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

对我来说非常有用。查涅。 oot 到 \pcname oot 如果您需要在另一台计算机上执行此操作。小心使用。


1
投票

创建 Win32_Directory.Name=... 字符串时,您需要双反斜杠,因此例如路径 C:\Foo\Bar 将构建为:

Win32_Directory.Name =“C:\ Foo \ Bar”,

或使用您的示例代码:

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

显然,该字符串被馈送到某个需要路径字符串转义形式的进程。


1
投票

这是对 Igal Serban 答案的轻微改编。我遇到了一个微妙的问题,

Name
必须采用非常特定的格式。所以我首先添加了一些
Replace("\\", @"\\").TrimEnd('\\')
magic 来标准化路径,我还清理了一些代码。

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");
    }
}

0
投票

我不相信有一种方法可以在 .NET 框架中设置文件夹压缩,因为文档(备注部分)声称它不能通过 File.SetAttributes 来完成。这似乎仅在使用 DeviceIoControl 函数的 Win32 API 中可用。人们仍然可以通过 .NET 使用 PInvoke 来完成此操作。

总体熟悉 PInvoke 后,请查看 pinvoke.net 上的参考资料,其中讨论了 signature 需要是什么样子才能实现这一点。


0
投票

我根据 Zack Elan 的解决方案创建了

SafeFileHandle
,所以我在此处发布了整个课程以供参考:

internal class FileCompressor
{
    private const int FSCTL_SET_COMPRESSION = 0x9C040;
    private const short COMPRESSION_FORMAT_DEFAULT = 1;
    private const uint GENERIC_ALL = 0x10000000;
    private const uint OPEN_EXISTING = 3;
    private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;

    [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);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode,
        IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

    public void CompressFile(string path)
    {
        using SafeFileHandle handle = CreateFile(path, GENERIC_ALL, 0, IntPtr.Zero, OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);

        if (!EnableCompression(handle))
        {
            // Handle error
        }
    }

    private 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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.