Search code examples
c#winapimovefileex

Delete non empty folder on reboot


I am attempting to delete user profile folders in the Users folder after I remove the user from the domain. The problem is some times the folder may be in use by the system.

The problem is using MOVEFILE_DELAY_UNTIL_REBOOT with MoveFileEx will only delete folders that are empty.

From the MSDN:

If dwFlags specifies MOVEFILE_DELAY_UNTIL_REBOOT and lpNewFileName is NULL, MoveFileEx registers the lpExistingFileName file to be deleted when the system restarts. If lpExistingFileName refers to a directory, the system removes the directory at restart only if the directory is empty.

What is the correct way to delete a non-empty folder that has a file inside it that is in use?


Here is a simple test program based on Michel's answer, it works as expected.

internal static class Program
{
    private static void Main(string[] args)
    {
        foreach (var file in Directory.EnumerateFiles(args[0], "*", SearchOption.AllDirectories))
        {
            Console.WriteLine(file);
        }

        foreach (var directory in Directory.EnumerateDirectories(args[0], "*", SearchOption.AllDirectories))
        {
            Console.WriteLine(directory);
            DeleteFileOnReboot(directory);
        }

        DeleteFileOnReboot(args[0]);
    }

    private static void DeleteFileOnReboot(string file)
    {
        bool result = MoveFileEx(file, null, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT);
        try
        {
            if (!result)
                throw new Win32Exception();
        }
        catch (Win32Exception ex)
        {
            Console.WriteLine(ex);
        }
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);

}

[Flags]
enum MoveFileFlags
{
    MOVEFILE_REPLACE_EXISTING = 0x00000001,
    MOVEFILE_COPY_ALLOWED = 0x00000002,
    MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004,
    MOVEFILE_WRITE_THROUGH = 0x00000008,
    MOVEFILE_CREATE_HARDLINK = 0x00000010,
    MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020
}

I do this over two loops with Directory.EnumerateFiles and Directory.EnumerateDirectories because Directory.EnumerateFileSystemEntries will list the folders before the files in the folders so the delete would fail.


Solution

  • In most of the implementations I've seen, every file in the directory is "deleted" with MoveFileEx with MOVEFILE_DELAY_UNTIL_REBOOT, and then the directory is "deleted" the same way. The order of operations is preserved upon reboot -- the files are deleted first, then the directory.