如何在 c# 中重命名当前由 windows 资源管理器打开的文件夹

How to rename a folder in c# which is currently opened by windows explorer

在 C# 中重命名文件夹时,如果该文件夹或任何子文件夹当前由 (Windows 7) 打开,System.IO.Directory.Move 将抛出 System.IO.IOException(消息 "access denied")探险家 window。 使用命令行 RENAME 也会失败。 使用第二个资源管理器 windows 成功。

即使在折叠父文件夹(或其父文件夹)后错误仍然存​​在。事实上,特定的资源管理器 window 需要关闭。 因此,资源管理器似乎只是为了显示文件夹结构而创建了一些锁,即使不再显示实际文件夹也不会释放它们(这在 IMO 中纯属无稽之谈)。

有没有办法重命名资源管理器当前显示(或可见,见上文)的文件夹(在程序中,例如使用 C#)window?

更新

使用 SHFileOperation() 找到了我自己对这个问题的回答(见下文)所描述的方法。但是,这个解决方案不是很可行(另见下文)。

我已经使用 API Monitor v2 by Rohitab 来监控 Windows API 来电。

将目录名称从 D:\test 更改为 D:\abc 时,记录了此调用:

explorerframe.dll   ITransferSource::RenameItem ( 0x0000000015165738, "abc", TSF_COPY_CREATION_TIME | TSF_COPY_LOCALIZED_NAME | TSF_COPY_WRITE_TIME | TSF_DELETE_RECYCLE_IF_POSSIBLE, 0x00000000150f77d0 )

进一步挖掘监视器的输出显示了一些本机调用:

如你所见,他们没有使用MoveFile,而是使用NtOpenFileFILE_OPEN_FOR_BACKUP_INTENT等打开原始目录,然后调用NtSetInformationFile 与新目录名称和标志 FileRenameInformation 记录在案 here.

不幸的是,这些都是内核调用。

您可以像这样从用户模式获取 C/C++ 目录的句柄:

HANDLE h = ::CreateFileA("D:\test",
    DELETE | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
    FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS,
    NULL);

但是,对于 NtSetInformationFile 调用,您仍然需要一个用户模式替代方案。

继续的一些选项(按复杂程度排序):

  • 看看你是否可以使用 shell 接口 ITransferSource::RenameItem 或者找到一个现成的 shell 函数
  • 深入研究用户模式解决方案并尝试找到 NtSetInformationFile
  • 的替代方案
  • 编写包含执行这些内核模式操作的 IOCTL 的驱动程序并从 C# 调用 DeviceIoControl

更新

似乎 SHFileOperation 函数完成了 OP 发现的所有上述操作。

将把这个答案留在网上,因为它可能会向其他人展示如何调试类似问题并获得有价值的指导。

所以我在进一步研究后回答我自己的问题..

可以使用 SHFileOperation() 重命名文件夹,如下所示: https://docs.microsoft.com/en-us/windows/win32/shell/manage
(这是否使用 Wouter 提到的 'magic'。;-)

但是如果有 windows/.Net API 比如 System.IO.Directory.Move,WTF 我需要使用 Shell 吗?不谈性能...

无论如何,使用 SHFileOperation() 在 a.. 使用 C# 中是一种痛苦,因为您需要声明所有这些 p-invoke 内容。在这种特殊情况下,您需要对 32 位和 64 位 windows 使用不同的结构,因为封装不同(参见 https://www.pinvoke.net/default.aspx/shell32.shfileoperation)。这非常麻烦,因为通常我会指定 AnyCPU 作为目标。在这一点上,您要么需要在运行时进行分支(确实非常糟糕),具体取决于您是 64 位还是 32 位进程,要么您为两个不同的目标构建不同的方式,这对于解决愚蠢的资源管理器来说是相当大的影响。

此致

我遇到了同样的问题。一旦我打开资源管理器 window 并导航到要重命名的文件夹后,Directory.Move 失败并显示 "access denied"(Windows 7 Professional 64 位,应用程序编译为 x86 ).

有趣的是,命令 Microsoft.VisualBasic.FileIO.FileSystem.MoveDirectory(...) 成功地将内容移动到新目录,如果您停留在要移动的目录的子文件夹中,它只会无法删除旧目录。这可以通过捕获第一次错误时抛出的异常并再次尝试来解决。现在源文件夹也被删除了。