13

When renaming a folder in C#, System.IO.Directory.Move throws System.IO.IOException (message "access denied") if that folder or any subfolder is currently opened by a (Windows 7) explorer window. Using the commandline RENAME fails, too. Using a second explorer windows succeeds.

The error persists even after collapsing the parent folder (or its parents). In fact the particular explorer window needs to be closed. So the explorer seems to create some locks just to show the folder structure and does not release them even if the actual folder isnt displayed anymore (which is pure nonsens IMO).

Is there a way to rename a folder (in program e.g. using C#), that is presently displayed (or was visible, see above) by an explorer window?

Update

Found a way as described by my own answer to this question (see below) using SHFileOperation(). However, this solution is not very feasible (see also below).

user2261015
  • 440
  • 4
  • 17
  • I've tested this and it works, probably you also have a file open or not enough permissions for that folder. I've created a dir `D:\a` and browsed to it via explorer, when I executed this code `Directory.Move("D:\\a", "D:\\b");`, the folder name changed automatically in explorer and location bar. – huysentruitw Sep 02 '15 at 08:16
  • 1
    @Wouter: I think you need to add one more level. Create `D:\a\b`, browse to `D:\a\b` via explorer and then try to rename `a` (as `x` or whatever). It does not work here. Now browse to `a` or to `computer` so neither `a` nor `b` are shown in that window (only the drives are shown). It still does not work. BTW, no files open and no permission issue by sure. – user2261015 Sep 02 '15 at 09:09
  • Partially, when explorer shows the subfolder, it throws the `IOException`, when explorer shows the root drive, it works. I must say I'm testing on Windows 10 which might explain the slight difference. – huysentruitw Sep 02 '15 at 09:17
  • 1
    Well, there may be differences depending on the system configuration details, but the actual issue keeps the same and the question unanswered until now.What can I do to rename the folder after the IOException was thrown? – user2261015 Sep 02 '15 at 09:26
  • What if you (or someone else) has a command prompt open in that folder (or a subfolder)? What happens to their working directory? My point is you're looking for a brute-force solution which (in .net at least) may not exist. IMO it would be better to pop up a message informing the user of the problem -- maybe with a *Retry* button? – David Sep 02 '15 at 11:54
  • @David: This is a different situation. A command prompt with a working directory sets another lock (i.e. opens a new handle). It is a different discussion how to deal with this. The particular question is about the windows-explorer mostly _unnecessarily_ locking a folder (even if not displayed anymore). BTW, a pop up may be sufficient for an interactive application that is presently in focus but its a bad idea if the application is not in focus (running in background) or has no UI at all (Batch, Service, ...). – user2261015 Sep 02 '15 at 13:58

3 Answers3

5

I've used API Monitor v2 by Rohitab to monitor Windows API calls.

When changing the directory name from D:\test to D:\abc, this call was logged:

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

Digging further into the output of the monitor shows some native calls:

enter image description here

As you can see, they're not using MoveFile, instead, they use NtOpenFile with FILE_OPEN_FOR_BACKUP_INTENT and others to open the original directory, then call NtSetInformationFile with the new directory name and the flag FileRenameInformation which is documented here.

Unfortunately, these are all kernel calls.

You can get a handle to a directory in C/C++ from user-mode like this:

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

But then, you still need a user-mode alternative for the NtSetInformationFile-call.

Some options to proceed (ordered by complexity):

  • See if you can use the shell interface ITransferSource::RenameItem or find a ready-to-use shell function
  • Dig further into the user-mode solution and try to find an alternative to NtSetInformationFile
  • Write a driver that contains a IOCTL that does these kernel-mode stuff and call DeviceIoControl from C#.

Update

Seems like the SHFileOperation function does all of the above as found by the OP.

Will leave this answer online, because it might show others how to debug similar problems and get valuable pointers.

huysentruitw
  • 27,376
  • 9
  • 90
  • 133
5

So I'm answering my own question after some further reasearch..

The folder can be renamed using SHFileOperation() as shown here: https://learn.microsoft.com/en-us/windows/win32/shell/manage
(whether this uses the 'magic' mentioned by Wouter or not. ;-)

But if there is a windows/.Net API such as System.IO.Directory.Move, WTF do I need to use the Shell? Not talking about performance ...

Anyway, using SHFileOperation() is a pain in the a.. using C# since you need to declare all this p-invoke stuff. And in this particular case you need to use different structs for 32 and 64-bit windows, since the packing is different (see https://www.pinvoke.net/default.aspx/shell32.shfileoperation). This is very cumbersome, as usually I'd specify AnyCPU as target. At this point you either need to branch at runtime (very bad indeed), depending whether you are a 64 or 32 bit process, or you build differently for two different targets, which is quite a big impact just to work around the silly explorer.

Regards

Martin Schneider
  • 14,263
  • 7
  • 55
  • 58
user2261015
  • 440
  • 4
  • 17
1

I had the same problem. As soon as I had an explorer window open and once navigated into the folder to rename, the Directory.Move failed with a "access denied" (Windows 7 Professional 64bit, application compiled as x86).

Interestingly, the command Microsoft.VisualBasic.FileIO.FileSystem.MoveDirectory(...) succeeds to move the contents to a new directory, it only fails to delete the old directory if you are staying inside a subfolder of the directory to move. This can be fixed by catching the exception that is thrown on the first error and try again a second time. Now the source folder is also removed.

LionAM
  • 1,271
  • 10
  • 28