如何在 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
,而是使用NtOpenFile
和FILE_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(...)
成功地将内容移动到新目录,如果您停留在要移动的目录的子文件夹中,它只会无法删除旧目录。这可以通过捕获第一次错误时抛出的异常并再次尝试来解决。现在源文件夹也被删除了。
在 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
,而是使用NtOpenFile
和FILE_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(...)
成功地将内容移动到新目录,如果您停留在要移动的目录的子文件夹中,它只会无法删除旧目录。这可以通过捕获第一次错误时抛出的异常并再次尝试来解决。现在源文件夹也被删除了。