RemoveFileZ.psm1
#Requires -RunAsAdministrator function Remove-FileZ { <# .SYNOPSIS Remove file or directory with trusted installer privileges .DESCRIPTION This will attempt to remove a file or directory using Trusted Installer privileges by taking ownership, removing file attributes, and killing any processes using the file/folder. .PARAMETER Path Path to file or directory, accepts wildcards. .PARAMETER Recurse Recurse through directory. .EXAMPLE Remove-FileZ -Path "C:\Windows\Speech" -Recurse # Delete Speech App folder .EXAMPLE Remove-FileZ -Path "C:\Windows\Speech" -Recurse *>$null # Delete Speech App folder with no output .EXAMPLE Remove-FileZ -Path "C:\Windows\Speech\*" -Recurse # Delete all files/folders inside of speech folder but keep directory .LINK https://github.com/zoicware/RemoveFileZ .NOTES Author: Zoic Twitter: https://twitter.com/1zoic GitHub: https://github.com/zoicware #> [CmdletBinding()] Param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $false)] [switch]$Recurse ) <# function DownloadHandleApp { $Path = "$env:ProgramData\Handle" if (!(Test-path $Path)) { $ProgressPreference = 'SilentlyContinue' $ZipFile = 'Handle.zip' $ZipFilePath = "$Path\$ZipFile" $Uri = "https://download.sysinternals.com/files/$ZipFile" try { Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $Path -Force -ErrorAction Stop | Out-Null Invoke-RestMethod -Method Get -Uri $Uri -OutFile $ZipFilePath -ErrorAction Stop Expand-Archive -Path $ZipFilePath -DestinationPath $Path -Force -ErrorAction Stop Remove-Item -Path $ZipFilePath -ErrorAction SilentlyContinue } catch { Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue Throw "Failed to download dependency: handle.exe from: $Uri" } } } #> # https://github.com/pldmgg/misc-powershell/blob/master/MyFunctions/PowerShellCore_Compatible/Get-FileLockProcess.ps1 function Get-FileLockProcess { [CmdletBinding()] Param( [Parameter(Mandatory = $true)] [string[]] $FilePath ) $AssembliesFullInfo = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq 'Microsoft.CSharp' -or $_.GetName().Name -eq 'mscorlib' -or $_.GetName().Name -eq 'System' -or $_.GetName().Name -eq 'System.Collections' -or $_.GetName().Name -eq 'System.Core' -or $_.GetName().Name -eq 'System.IO' -or $_.GetName().Name -eq 'System.Linq' -or $_.GetName().Name -eq 'System.Runtime' -or $_.GetName().Name -eq 'System.Runtime.Extensions' -or $_.GetName().Name -eq 'System.Runtime.InteropServices' } | Where-Object { $_.IsDynamic -eq $False } $ReferencedAssemblies = $AssembliesFullInfo.FullName | Sort-Object | Get-Unique $TypeDefinition = @' using Microsoft.CSharp; using System.Collections.Generic; using System.Collections; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Runtime; using System; using System.Diagnostics; namespace MyCore.Utils { static public class FileLockUtil { [StructLayout(LayoutKind.Sequential)] struct RM_UNIQUE_PROCESS { public int dwProcessId; public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; } const int RmRebootReasonNone = 0; const int CCH_RM_MAX_APP_NAME = 255; const int CCH_RM_MAX_SVC_NAME = 63; enum RM_APP_TYPE { RmUnknownApp = 0, RmMainWindow = 1, RmOtherWindow = 2, RmService = 3, RmExplorer = 4, RmConsole = 5, RmCritical = 1000 } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct RM_PROCESS_INFO { public RM_UNIQUE_PROCESS Process; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] public string strAppName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] public string strServiceShortName; public RM_APP_TYPE ApplicationType; public uint AppStatus; public uint TSSessionId; [MarshalAs(UnmanagedType.Bool)] public bool bRestartable; } [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] static extern int RmRegisterResources(uint pSessionHandle, UInt32 nFiles, string[] rgsFilenames, UInt32 nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, UInt32 nServices, string[] rgsServiceNames); [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); [DllImport("rstrtmgr.dll")] static extern int RmEndSession(uint pSessionHandle); [DllImport("rstrtmgr.dll")] static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons); /// Find out what process(es) have a lock on the specified file. /// <param name="path">Path of the file.</param> /// <returns>Processes locking the file</returns> /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing) static public List<Process> WhoIsLocking(string path) { uint handle; string key = Guid.NewGuid().ToString(); List<Process> processes = new List<Process>(); int res = RmStartSession(out handle, 0, key); if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker."); try { const int ERROR_MORE_DATA = 234; uint pnProcInfoNeeded = 0, pnProcInfo = 0, lpdwRebootReasons = RmRebootReasonNone; // Just checking on one resource string[] resources = new string[] { path }; res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); if (res != 0) throw new Exception("Could not register resource."); //Note: there's a race condition here -- the first call to RmGetList() returns // the total number of process. However, when we call RmGetList() again to get // the actual processes this number may have increased. res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); if (res == ERROR_MORE_DATA) { // Create an array to store the process results RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; pnProcInfo = pnProcInfoNeeded; // Get the list res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); if (res == 0) { processes = new List<Process>((int)pnProcInfo); // Enumerate all of the results and add them to the // list to be returned for (int i = 0; i < pnProcInfo; i++) { try { processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); } // catch the error -- in case the process is no longer running catch (ArgumentException) { } } } else throw new Exception("Could not list processes locking resource."); } else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result."); } finally { RmEndSession(handle); } return processes; } } } '@ if (-not ('MyCore.Utils.FileLockUtil' -as [type])) { Add-Type -ReferencedAssemblies $ReferencedAssemblies -TypeDefinition $TypeDefinition } [MyCore.Utils.FileLockUtil]::WhoIsLocking($FilePath) } #run powershell as trusted installer credit : https://github.com/AveYo/LeanAndMean #added -wait to prevent script from continuing too fast function RunAsTI($cmd, $arg) { $id = 'RunAsTI'; $key = "Registry::HKU\$(((whoami /user)-split' ')[-1])\Volatile Environment"; $code = @' $I=[int32]; $M=$I.module.gettype("System.Runtime.Interop`Services.Mar`shal"); $P=$I.module.gettype("System.Int`Ptr"); $S=[string] $D=@(); $T=@(); $DM=[AppDomain]::CurrentDomain."DefineDynami`cAssembly"(1,1)."DefineDynami`cModule"(1); $Z=[uintptr]::size 0..5|% {$D += $DM."Defin`eType"("AveYo_$_",1179913,[ValueType])}; $D += [uintptr]; 4..6|% {$D += $D[$_]."MakeByR`efType"()} $F='kernel','advapi','advapi', ($S,$S,$I,$I,$I,$I,$I,$S,$D[7],$D[8]), ([uintptr],$S,$I,$I,$D[9]),([uintptr],$S,$I,$I,[byte[]],$I) 0..2|% {$9=$D[0]."DefinePInvok`eMethod"(('CreateProcess','RegOpenKeyEx','RegSetValueEx')[$_],$F[$_]+'32',8214,1,$S,$F[$_+3],1,4)} $DF=($P,$I,$P),($I,$I,$I,$I,$P,$D[1]),($I,$S,$S,$S,$I,$I,$I,$I,$I,$I,$I,$I,[int16],[int16],$P,$P,$P,$P),($D[3],$P),($P,$P,$I,$I) 1..5|% {$k=$_; $n=1; $DF[$_-1]|% {$9=$D[$k]."Defin`eField"('f' + $n++, $_, 6)}}; 0..5|% {$T += $D[$_]."Creat`eType"()} 0..5|% {nv "A$_" ([Activator]::CreateInstance($T[$_])) -fo}; function F ($1,$2) {$T[0]."G`etMethod"($1).invoke(0,$2)} $TI=(whoami /groups)-like'*1-16-16384*'; $As=0; if(!$cmd) {$cmd='control';$arg='admintools'}; if ($cmd-eq'This PC'){$cmd='file:'} if (!$TI) {'TrustedInstaller','lsass','winlogon'|% {if (!$As) {$9=sc.exe start $_; $As=@(get-process -name $_ -ea 0|% {$_})[0]}} function M ($1,$2,$3) {$M."G`etMethod"($1,[type[]]$2).invoke(0,$3)}; $H=@(); $Z,(4*$Z+16)|% {$H += M "AllocHG`lobal" $I $_} M "WriteInt`Ptr" ($P,$P) ($H[0],$As.Handle); $A1.f1=131072; $A1.f2=$Z; $A1.f3=$H[0]; $A2.f1=1; $A2.f2=1; $A2.f3=1; $A2.f4=1 $A2.f6=$A1; $A3.f1=10*$Z+32; $A4.f1=$A3; $A4.f2=$H[1]; M "StructureTo`Ptr" ($D[2],$P,[boolean]) (($A2 -as $D[2]),$A4.f2,$false) $Run=@($null, "powershell -win 1 -nop -c iex `$env:R; # $id", 0, 0, 0, 0x0E080600, 0, $null, ($A4 -as $T[4]), ($A5 -as $T[5])) F 'CreateProcess' $Run; return}; $env:R=''; rp $key $id -force; $priv=[diagnostics.process]."GetM`ember"('SetPrivilege',42)[0] 'SeSecurityPrivilege','SeTakeOwnershipPrivilege','SeBackupPrivilege','SeRestorePrivilege' |% {$priv.Invoke($null, @("$_",2))} $HKU=[uintptr][uint32]2147483651; $NT='S-1-5-18'; $reg=($HKU,$NT,8,2,($HKU -as $D[9])); F 'RegOpenKeyEx' $reg; $LNK=$reg[4] function L ($1,$2,$3) {sp 'HKLM:\Software\Classes\AppID\{CDCBCFCA-3CDC-436f-A4E2-0E02075250C2}' 'RunAs' $3 -force -ea 0 $b=[Text.Encoding]::Unicode.GetBytes("\Registry\User\$1"); F 'RegSetValueEx' @($2,'SymbolicLinkValue',0,6,[byte[]]$b,$b.Length)} function Q {[int](gwmi win32_process -filter 'name="explorer.exe"'|?{$_.getownersid().sid-eq$NT}|select -last 1).ProcessId} $11bug=($((gwmi Win32_OperatingSystem).BuildNumber)-eq'22000')-AND(($cmd-eq'file:')-OR(test-path -lit $cmd -PathType Container)) if ($11bug) {'System.Windows.Forms','Microsoft.VisualBasic' |% {[Reflection.Assembly]::LoadWithPartialName("'$_")}} if ($11bug) {$path='^(l)'+$($cmd -replace '([\+\^\%\~\(\)\[\]])','{$1}')+'{ENTER}'; $cmd='control.exe'; $arg='admintools'} L ($key-split'\\')[1] $LNK ''; $R=[diagnostics.process]::start($cmd,$arg); if ($R) {$R.PriorityClass='High'; $R.WaitForExit()} if ($11bug) {$w=0; do {if($w-gt40){break}; sleep -mi 250;$w++} until (Q); [Microsoft.VisualBasic.Interaction]::AppActivate($(Q))} if ($11bug) {[Windows.Forms.SendKeys]::SendWait($path)}; do {sleep 7} while(Q); L '.Default' $LNK 'Interactive User' '@; $V = ''; 'cmd', 'arg', 'id', 'key' | ForEach-Object { $V += "`n`$$_='$($(Get-Variable $_ -val)-replace"'","''")';" }; Set-ItemProperty $key $id $($V, $code) -type 7 -force -ea 0 Start-Process powershell -args "-win 1 -nop -c `n$V `$env:R=(gi `$key -ea 0).getvalue(`$id)-join''; iex `$env:R" -verb runas -Wait } # lean & mean snippet by AveYo, 2022.01.28 #make sure file or folder exists try { #check if wildcard was passed if ($Path -match '\*' -or $Path -match '\?') { $hasWildCard = $true } else { $hasWildCard = $false $type = Get-Item -Path $Path -Force -ErrorAction Stop } if ($type.Attributes -like '*Directory*' -or $hasWildCard) { #if path is a file set dir $dir = Split-Path $Path #check dir for wildcard passed Get-Item -Path $dir -Force -ErrorAction Stop | Out-Null } } catch { Write-Error 'File or Directory NOT Found!' return } #set path to global so that other powershell windows can use it $Global:Path = $Path #try to take ownership if ($hasWildCard) { #put all code in command for trusted installer to avoid lots of powershell sessions $command = @" `$files = Get-ChildItem -Path "`'$Path`'" -File -Force foreach(`$file in `$files){ `$filepath = `$file.FullName takeown /f `$filepath icacls `$filepath /grant administrators:F /t } `$dirs = Get-ChildItem -Path "`'$Path`'" -Directory -Force foreach(`$dir in `$dirs){ `$dirpath = `$dir.FullName takeown /f `$dirpath /r /d Y icacls `$dirpath /grant administrators:F /t } "@ RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" } else { if ($type.Attributes -like '*Directory*') { #recurse if path is directory RunAsTI cmd "/c takeown /f `'$Path`' /r /d Y && icacls `'$Path`' /grant administrators:F /t" } else { RunAsTI cmd "/c takeown /f `'$Path`' && icacls `'$Path`' /grant administrators:F /t" } } #remove read-only attribute if ($hasWildCard) { $command = @" `$files = Get-ChildItem -Path "`'$Path`'" -File -Force -Exclude 'desktop.ini' foreach(`$file in `$files){ `$file.Attributes = 'Normal' } `$dirs = Get-ChildItem -Path "`'$Path`'" -Directory -Force foreach(`$dir in `$dirs){ `$dir.Attributes = 'Directory' } "@ RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" } else { if ($Recurse) { #script block for runasti $command = @" `$files = Get-ChildItem -Path `'$Path`' -File -Recurse -Force -Exclude 'desktop.ini' foreach (`$file in `$files) { `$file.Attributes = 'Normal' } `$dirs = Get-ChildItem -Path `'$Path`' -Directory -Recurse -Force foreach (`$dir in `$dirs) { `$dir.Attributes = 'Directory' } "@ #reset all attributes with trusted installer RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" } else { #get single file $command = @" `$file = Get-Item -Path `'$Path`' -Force `$file.Attributes = 'Normal' "@ #remove file attributes as trusted installer RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" } } #create error txt file New-Item "$env:ProgramData\error.txt" -ItemType File -Force | Out-Null #try to remove with trusted installer if ($Recurse -or $hasWildCard) { $command = @" Remove-Item -Path `'$Path`' -Recurse -Force foreach(`$err in `$Error){ if (`$err.Exception -like '*being used by another process*'){ Add-Content "`$env:ProgramData\error.txt" -Value `$err.Exception -Force } } "@ RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" } else { $command = @" Remove-Item -Path `'$Path`' -Force foreach(`$err in `$Error){ if (`$err.Exception -like '*being used by another process*'){ Add-Content "`$env:ProgramData\error.txt" -Value `$err.Exception -Force } } "@ RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" } Start-Sleep 2 #get errors for files in use $errors = Get-Content "$env:ProgramData\error.txt" -Force | Select-String 'System.IO.IOException:' #if path is dir and -recurse do not compare to error list file name instead just kill all processes in the dir if ($errors -ne $null) { #extract file paths from errors $openFiles = @() foreach ($line in $errors) { ($line -match "'(.*)'") | Out-Null $openFiles += $matches[1] } #download handle app from sys internals to get which process is using the file #DownloadHandleApp #$handlePath = "$env:ProgramData\Handle\handle.exe" $files = @() $pids = @{} #get each file if path has wildcard or is dir $files = Get-ChildItem -Path $Path -Recurse -Force foreach ($file in $files) { #hastable with pid and file path $pids[$file.FullName] = (Get-FileLockProcess -FilePath $file.FullName).Id } foreach ($item in $pids.GetEnumerator()) { $procId = $item.Value $path = $item.Key $command = "Stop-Process -id $procID -Force; Remove-Item -Path $path -Force" RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" <# #remove quotes $file = $file -replace "'" , ' ' #check the path from error file with handle output if ($line -like "*$file*") { #get the pid and try to stop with trusted installer ($line -match 'pid:\s+(\d+)') | Out-Null $procID = $matches[1] $command = "Stop-Process -id $procID -Force" RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" Start-Sleep 1 #now try to remove file #kill and remove in one command $command = "Remove-Item -Path $file -Force" RunAsTI powershell "-NoLogo -WindowStyle Hidden -Command $command" } #> } } #cleanup error file #Remove-Item -Path "$env:ProgramData\error.txt" -Force -ErrorAction SilentlyContinue Start-Sleep 2 #check that file / folder is deleted Write-Host 'Removed File/Folder ' -NoNewline $files = Get-ChildItem -Path $Path -Force -ErrorAction SilentlyContinue if ($files -eq $null) { Write-Host '[SUCCESS]' -ForegroundColor Green } else { #write the names of files not deleted Write-Host '[FAIL]' -ForegroundColor Red Write-Host '----- File Names -----' foreach ($file in $files) { Write-Host $file.Name -ForegroundColor Red } } } Export-ModuleMember -Function Remove-FileZ |