private static void DeleteHelper(String fullPath, String userPath, bool recursive, bool throwOnTopLevelDirectoryNotFound) { bool r; int hr; Exception ex = null; // Do not recursively delete through reparse points. Perhaps in a // future version we will add a new flag to control this behavior, // but for now we're much safer if we err on the conservative side. // This applies to symbolic links and mount points. // Note the logic to check whether fullPath is a reparse point is // in Delete(String, String, bool), and will set "recursive" to false. // Note that Win32's DeleteFile and RemoveDirectory will just delete // the reparse point itself. if (recursive) { Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); String searchPath = null; if (fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { searchPath = fullPath + "*"; } else { searchPath = fullPath + Path.DirectorySeparatorChar + "*"; } // Open a Find handle using (SafeFindHandle hnd = Win32Native.FindFirstFile(searchPath, data)) { if (hnd.IsInvalid) { hr = Marshal.GetLastWin32Error(); __Error.WinIOError(hr, userPath); } do { bool isDir = (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY)); if (isDir) { // Skip ".", "..". if (data.cFileName.Equals(".") || data.cFileName.Equals("..")) { continue; } // Recurse for all directories, unless they are // reparse points. Do not follow mount points nor // symbolic links, but do delete the reparse point // itself. bool shouldRecurse = (0 == (data.dwFileAttributes & (int)FileAttributes.ReparsePoint)); if (shouldRecurse) { String newFullPath = LongPath.InternalCombine(fullPath, data.cFileName); String newUserPath = LongPath.InternalCombine(userPath, data.cFileName); try { DeleteHelper(newFullPath, newUserPath, recursive, false); } catch (Exception e) { if (ex == null) { ex = e; } } } else { // Check to see if this is a mount point, and // unmount it. if (data.dwReserved0 == Win32Native.IO_REPARSE_TAG_MOUNT_POINT) { // Use full path plus a trailing '\' String mountPoint = LongPath.InternalCombine(fullPath, data.cFileName + Path.DirectorySeparatorChar); r = Win32Native.DeleteVolumeMountPoint(mountPoint); if (!r) { hr = Marshal.GetLastWin32Error(); if (hr != Win32Native.ERROR_PATH_NOT_FOUND) { try { __Error.WinIOError(hr, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } // RemoveDirectory on a symbolic link will // remove the link itself. String reparsePoint = LongPath.InternalCombine(fullPath, data.cFileName); r = Win32Native.RemoveDirectory(reparsePoint); if (!r) { hr = Marshal.GetLastWin32Error(); if (hr != Win32Native.ERROR_PATH_NOT_FOUND) { try { __Error.WinIOError(hr, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } else { String fileName = LongPath.InternalCombine(fullPath, data.cFileName); r = Win32Native.DeleteFile(fileName); if (!r) { hr = Marshal.GetLastWin32Error(); if (hr != Win32Native.ERROR_FILE_NOT_FOUND) { try { __Error.WinIOError(hr, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } while (Win32Native.FindNextFile(hnd, data)); // Make sure we quit with a sensible error. hr = Marshal.GetLastWin32Error(); } if (ex != null) { throw ex; } if (hr != 0 && hr != Win32Native.ERROR_NO_MORE_FILES) { __Error.WinIOError(hr, userPath); } } r = Win32Native.RemoveDirectory(fullPath); if (!r) { hr = Marshal.GetLastWin32Error(); if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // A dubious error code. { hr = Win32Native.ERROR_PATH_NOT_FOUND; } // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. if (hr == Win32Native.ERROR_ACCESS_DENIED) { throw new IOException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", userPath)); } // don't throw the DirectoryNotFoundException since this is a subdir and there could be a ---- // between two Directory.Delete callers if (hr == Win32Native.ERROR_PATH_NOT_FOUND && !throwOnTopLevelDirectoryNotFound) { return; } __Error.WinIOError(hr, userPath); } }