Skip to content

Instantly share code, notes, and snippets.

@jborean93
Last active July 1, 2023 21:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jborean93/34e691559c8d4a20992b9a19ec22706e to your computer and use it in GitHub Desktop.
Save jborean93/34e691559c8d4a20992b9a19ec22706e to your computer and use it in GitHub Desktop.
Removes a file/dir using direct Win32 calls
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