Last active
July 1, 2023 21:03
-
-
Save jborean93/34e691559c8d4a20992b9a19ec22706e to your computer and use it in GitHub Desktop.
Removes a file/dir using direct Win32 calls
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add-Type -TypeDefinition @' | |
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.IO; | |
using System.Runtime.InteropServices; | |
namespace Kernel32 | |
{ | |
public enum FileInfoLevel | |
{ | |
Standard = 0, | |
Basic = 1, | |
} | |
public enum FileSearchOperation | |
{ | |
NameMatch = 0, | |
LimitToDirectories = 1, | |
LimitToDevices = 2, | |
} | |
[Flags] | |
public enum FileSearchFlags | |
{ | |
None = 0, | |
CaseSensitive = 1, | |
LargeFetch = 2, | |
OnDiskEntriesOnly = 4, | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
internal struct WIN32_FIND_DATAW | |
{ | |
public FileAttributes dwFileAttributes; | |
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; | |
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; | |
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; | |
public uint nFileSizeHigh; | |
public uint nFileSizeLow; | |
public uint dwReserved0; | |
public uint dwReserved1; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)] | |
public string cFileName; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=14)] | |
public string cAlternateFileName; | |
public uint dwFileType; | |
public uint dwCreatorType; | |
public uint wFinderFlags; | |
} | |
public class FindData | |
{ | |
private WIN32_FIND_DATAW _raw; | |
public FileAttributes FileAttributes | |
{ | |
get { return _raw.dwFileAttributes; } | |
} | |
public string FileName | |
{ | |
get { return _raw.cFileName; } | |
} | |
internal FindData(WIN32_FIND_DATAW data) | |
{ | |
_raw = data; | |
} | |
} | |
public class Native | |
{ | |
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern bool DeleteFileW(string lpFileName); | |
public static void DeleteFile(string fileName) | |
{ | |
if (!DeleteFileW(fileName)) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
[DllImport("Kernel32.dll", SetLastError = true)] | |
private static extern bool FindClose( | |
IntPtr hFindFile); | |
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern IntPtr FindFirstFileExW( | |
string lpFileName, | |
FileInfoLevel fInfoLevelId, | |
out WIN32_FIND_DATAW lpFindFileData, | |
FileSearchOperation fSearchOp, | |
IntPtr lpSearchFilter, | |
FileSearchFlags dwAdditionalFlags); | |
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern bool FindNextFileW( | |
IntPtr hFindFile, | |
out WIN32_FIND_DATAW lpFindFileData); | |
public static IEnumerable<FindData> FindFileBasic(string fileName, FileSearchOperation searchOp, | |
FileSearchFlags flags) | |
{ | |
WIN32_FIND_DATAW findData; | |
IntPtr findHandle = FindFirstFileExW(fileName, FileInfoLevel.Basic, out findData, searchOp, IntPtr.Zero, | |
flags); | |
if (findHandle == (IntPtr)(-1)) | |
{ | |
throw new Win32Exception(); | |
} | |
try | |
{ | |
yield return new FindData(findData); | |
while (FindNextFileW(findHandle, out findData)) | |
{ | |
yield return new FindData(findData); | |
} | |
int err = Marshal.GetLastWin32Error(); | |
if (err != 0x00000012) // ERROR_NO_MORE_FILES | |
{ | |
throw new Win32Exception(err); | |
} | |
} | |
finally | |
{ | |
FindClose(findHandle); | |
} | |
} | |
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern int GetFileAttributesW(string lpFileName); | |
public static FileAttributes GetFileAttributes(string fileName) | |
{ | |
int res = GetFileAttributesW(fileName); | |
if (res == -1) | |
{ | |
throw new Win32Exception(); | |
} | |
return (FileAttributes)res; | |
} | |
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern bool RemoveDirectoryW(string lpPathName); | |
public static void RemoveDirectory(string dirName) | |
{ | |
if (!RemoveDirectoryW(dirName)) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern bool SetFileAttributesW( | |
string lpFileName, | |
int dwFileAttributes); | |
public static void SetFileAttributes(string fileName, int attributes) | |
{ | |
if (!SetFileAttributesW(fileName, attributes)) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
} | |
} | |
'@ | |
Function Remove-FileEntry { | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory, ValueFromPipeline)] | |
[string[]]$Path | |
) | |
begin { | |
$findFlags = [Kernel32.FileSearchFlags]::None | |
if ([System.Environment]::OSVersion.Version -gt [Version]"6.0") { | |
$findFlags = $findFlags -bor [Kernel32.FileSearchFlags]::LargeFetch | |
} | |
} | |
process { | |
foreach ($itemPath in $Path) { | |
# Ensure the path starts with the NT path prefix to override the | |
# MAX_PATH limits. | |
if (-not $itemPath.StartsWith("\\?\")) { | |
if ($itemPath.StartsWith("\\")) { | |
# UNC paths need to be prefixed with \\?\UNC and leading slash removed | |
$itemPath = "\\?\UNC" + $itemPath.Substring(1) | |
} | |
else { | |
$itemPath = "\\?\$itemPath" | |
} | |
} | |
foreach ($entry in [Kernel32.Native]::FindFileBasic($itemPath, "NameMatch", $findFlags)) { | |
if ($entry.FileName -in @('.', '..')) { | |
continue | |
} | |
$filePath = $itemPath.Substring(0, $itemPath.LastIndexOf('\')) + "\$($entry.FileName)" | |
Write-Verbose "Processing $filePath" | |
# Windows will error if the file has ReadOnly set on it so we unset it | |
if ($entry.FileAttributes -band [System.IO.FileAttributes]::ReadOnly) { | |
[Kernel32.Native]::SetFileAttributes($filePath, | |
($entry.FileAttributes -band -bnot [System.IO.FileAttributes]::ReadOnly)) | |
} | |
if ($entry.FileAttributes -band [System.IO.FileAttributes]::Directory) { | |
if (-not ($entry.FileAttributes -band [System.IO.FileAttributes]::ReparsePoint)) { | |
# FUTURE: Avoid recursion | |
$filePath + "\*" | Remove-FileEntry | |
} | |
Write-Verbose -Message "Removing dir $filePath" | |
[Kernel32.Native]::RemoveDirectory($filePath) | |
} | |
else { | |
Write-Verbose -Message "Removing file $filePath" | |
[Kernel32.Native]::DeleteFile($filePath) | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment