如何在C#中重命名当前被Windows资源管理器打开的文件夹

13
在C#中重命名文件夹时,如果该文件夹或其任何子文件夹当前由(Windows 7)资源管理器窗口打开,则System.IO.Directory.Move会引发System.IO.IOException(消息为“访问被拒绝”)。 使用命令行RENAME也会失败。 在第二个资源管理器窗口中使用可以成功。
即使折叠了父文件夹(或其父文件夹),错误也会持续存在。实际上,特定的资源管理器窗口需要关闭。 因此,资源管理器似乎创建了一些锁,只是为了显示文件夹结构,并且即使实际文件夹不再显示(这纯粹是胡说八道),它也不会释放这些锁。
是否有一种方法可以重命名文件夹(例如使用C#编写程序),该文件夹目前由资源管理器窗口显示或曾经可见(参见上文)?
更新
找到了一种方法,如下面对本问题(请参见下文)所述,使用SHFileOperation()。但是,这种解决方案并不是非常可行(请参见下文)。

我已经测试过了,它可以正常工作。可能是因为您也有一个文件打开或者没有足够的权限来访问该文件夹。我创建了一个名为 D:\a 的目录,并通过资源管理器浏览到该目录。当我执行这段代码 Directory.Move("D:\\a", "D:\\b"); 时,在资源管理器和地址栏中,文件夹名称会自动更改。 - huysentruitw
1
@Wouter:我认为您需要再添加一个级别。创建D:\a\b,通过资源管理器浏览到D:\a\b,然后尝试将a重命名为x或其他任何名称。在这里它不起作用。现在浏览到a计算机,因此在该窗口中未显示ab(仅显示驱动器)。它仍然不起作用。顺便说一句,没有打开文件,也没有权限问题。 - user2261015
部分地,当资源管理器显示子文件夹时,它会抛出 IOException 异常,而当资源管理器显示根驱动器时,它可以正常工作。我必须说我是在 Windows 10 上进行测试的,这可能解释了轻微的差异。 - huysentruitw
1
系统配置细节可能有所不同,但实际问题仍然存在,直到现在也没有得到答案。在 IOException 抛出后,我该怎么重命名文件夹? - user2261015
如果你(或其他人)在那个文件夹(或子文件夹)中打开了命令提示符,会发生什么?他们的工作目录会发生什么变化?我的意思是,你正在寻找一个蛮力解决方案,而这在 .net 中可能不存在。在我看来,最好弹出一条消息告知用户问题——也许带有“重试”按钮? - David
@David:这是一个不同的情况。带有工作目录的命令提示符设置了另一个锁(即打开了一个新句柄)。如何处理这个问题是另一个讨论。特定的问题主要是关于Windows资源管理器通常会_不必要地_锁定文件夹(即使不再显示)。顺便说一句,弹出窗口可能对于当前处于焦点的交互式应用程序来说已经足够了,但如果应用程序没有焦点(在后台运行)或根本没有UI(批处理、服务等),这是一个坏主意。 - user2261015
3个回答

5
经过进一步的研究,我自己回答自己的问题...
可以使用 SHFileOperation() 重命名文件夹,如下所示: https://learn.microsoft.com/en-us/windows/win32/shell/manage (无论这是否使用了Wouter提到的“魔法”)。但是如果有windows/.Net API,例如 System.IO.Directory.Move,那么我为什么需要使用Shell呢?不是在谈论性能... 总之,在C#中使用 SHFileOperation() 很麻烦,因为你需要声明所有这些p-invoke内容。并且在这种情况下,您需要使用不同的结构体来处理32位和64位Windows,因为打包方式不同(请参见https://www.pinvoke.net/default.aspx/shell32.shfileoperation)。这非常繁琐,因为通常我会指定AnyCPU作为目标平台。此时,您需要根据运行时分支(确实非常糟糕)或者根据两个不同的目标构建不同的版本,以绕过愚蠢的资源管理器。 问候

5
我使用Rohitab的API Monitor v2来监控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 )

深入挖掘监视器的输出可以看到一些本地调用:

enter image description here

正如您所看到的,他们没有使用MoveFile,而是使用NtOpenFile和其他选项打开原始目录,然后调用NtSetInformationFile以新的目录名称和标志FileRenameInformation,该标志在此处有记录。不幸的是,这些都是内核调用。您可以通过以下方式在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
更新 似乎 OP 发现 SHFileOperation 函数可以完成上述所有操作。
将保留此答案在线,因为它可能向其他人展示如何调试类似的问题并获得有价值的指针。

1
我遇到了同样的问题。只要打开一个资源管理器窗口并进入要重命名的文件夹,Directory.Move 就会失败并显示“访问被拒绝”(Windows 7 Professional 64位,应用程序编译为 x86)。
有趣的是,命令 Microsoft.VisualBasic.FileIO.FileSystem.MoveDirectory(...) 成功将内容移动到新目录,但如果您停留在要移动的目录的子文件夹中,则无法删除旧目录。这可以通过捕获第一个错误时抛出的异常并再次尝试解决。现在源文件夹也被删除了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接