Update script for file handles
This commit is contained in:
@@ -1,122 +1,299 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Specifies a directory and lists all processes that have open file handles within that directory and its
|
||||
subdirectories.
|
||||
Find processes that have open file handles to a specific file or directory.
|
||||
|
||||
.DESCRIPTION
|
||||
This script utilizes the handle.exe tool from Microsoft's Sysinternals suite to find open file handles.
|
||||
It filters the results to show only file handles within the specified target directory path.
|
||||
The script requires handle.exe to be downloaded and its path correctly configured in the $handleExePath
|
||||
variable.
|
||||
For best results, run this script with Administrator privileges.
|
||||
Uses Sysinternals handle.exe (NtHandle v5.0) to find all processes holding handles
|
||||
to the specified file or directory path. Supports detailed output and process information.
|
||||
|
||||
.PARAMETER TargetDirectory
|
||||
The path to the directory to scan for open file handles. Defaults to the Windows directory (C:\Windows).
|
||||
.PARAMETER Path
|
||||
The file or directory path to search for. If not specified, prompts for input.
|
||||
|
||||
.PARAMETER HandleExePath
|
||||
Path to handle.exe. If not specified, auto-detects from common locations.
|
||||
|
||||
.PARAMETER ShowDetails
|
||||
Show detailed handle information including handle ID, type, and user.
|
||||
|
||||
.PARAMETER OutputFile
|
||||
Save results to the specified file (e.g., handle.out).
|
||||
|
||||
.EXAMPLE
|
||||
.\Find-FileHandles.ps1
|
||||
(Scans the default C:\Windows directory)
|
||||
.\Find-FileHandles.ps1 -Path "D:\Project\video-av1\vav2\docs"
|
||||
|
||||
.EXAMPLE
|
||||
.\Find-FileHandles.ps1 -TargetDirectory "D:\Project\video-av1"
|
||||
(Scans the D:\Project\video-av1 directory)
|
||||
.\Find-FileHandles.ps1 -Path "D:\Project\video-av1\sample\test.webm" -ShowDetails
|
||||
|
||||
.EXAMPLE
|
||||
.\Find-FileHandles.ps1 -Path "D:\Project" -OutputFile "handle.out"
|
||||
|
||||
.EXAMPLE
|
||||
.\Find-FileHandles.ps1 -Path "D:\Project" -ShowDetails -OutputFile "handle.out"
|
||||
#>
|
||||
param (
|
||||
[string]$TargetDirectory = $env:SystemRoot # Defaults to C:\Windows
|
||||
[Parameter(Position=0)]
|
||||
[string]$Path,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$HandleExePath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$ShowDetails,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$OutputFile
|
||||
)
|
||||
|
||||
# 콘솔 인코딩 설정 (한글 깨짐 방지)
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
# handle.exe 경로 자동 탐지
|
||||
$possiblePaths = @(
|
||||
"C:\Sysinternals\handle.exe",
|
||||
"$env:ProgramFiles\Sysinternals\handle.exe",
|
||||
"$env:USERPROFILE\Downloads\handle.exe",
|
||||
"$env:TEMP\handle.exe",
|
||||
"handle.exe" # PATH에 있는 경우
|
||||
)
|
||||
|
||||
$handleExePath = $null
|
||||
foreach ($path in $possiblePaths) {
|
||||
if (Test-Path $path) {
|
||||
$handleExePath = $path
|
||||
break
|
||||
# Path 파라미터가 없으면 사용자에게 입력 받기
|
||||
if (-not $Path) {
|
||||
$Path = Read-Host "Enter the file or directory path to search for handles"
|
||||
if (-not $Path) {
|
||||
Write-Host "[ERROR] No path specified." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# PATH에서 handle.exe 찾기
|
||||
if (-not $handleExePath) {
|
||||
try {
|
||||
$handleExePath = (Get-Command "handle.exe" -ErrorAction Stop).Source
|
||||
} catch {
|
||||
$handleExePath = $null
|
||||
# 경로 검증 및 정규화
|
||||
if (Test-Path $Path) {
|
||||
$TargetPath = (Resolve-Path $Path).Path
|
||||
} else {
|
||||
Write-Host "[WARNING] Path does not exist: $Path" -ForegroundColor Yellow
|
||||
Write-Host "Searching anyway (path might be locked or inaccessible)..." -ForegroundColor Gray
|
||||
$TargetPath = $Path
|
||||
}
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
# handle.exe 경로 자동 탐지
|
||||
if (-not $HandleExePath) {
|
||||
$possiblePaths = @(
|
||||
"C:\Sysinternals\handle.exe",
|
||||
"$env:ProgramFiles\Sysinternals\handle.exe",
|
||||
"$env:USERPROFILE\Downloads\handle.exe",
|
||||
"$env:TEMP\handle.exe",
|
||||
"handle.exe" # PATH에 있는 경우
|
||||
)
|
||||
|
||||
foreach ($path in $possiblePaths) {
|
||||
if (Test-Path $path) {
|
||||
$HandleExePath = $path
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# PATH에서 handle.exe 찾기
|
||||
if (-not $HandleExePath) {
|
||||
try {
|
||||
$HandleExePath = (Get-Command "handle.exe" -ErrorAction Stop).Source
|
||||
} catch {
|
||||
$HandleExePath = $null
|
||||
}
|
||||
}
|
||||
}
|
||||
# ---------------------
|
||||
|
||||
# handle.exe 파일 존재 여부 확인
|
||||
if (-not $handleExePath -or -not (Test-Path $handleExePath)) {
|
||||
Write-Host "[ERROR] handle.exe not found in any of the following locations:" -ForegroundColor Red
|
||||
$possiblePaths | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
|
||||
if (-not $HandleExePath -or -not (Test-Path $HandleExePath)) {
|
||||
Write-Host "[ERROR] handle.exe not found!" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Please download handle.exe from:" -ForegroundColor Cyan
|
||||
Write-Host "https://learn.microsoft.com/en-us/sysinternals/downloads/handle" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "And place it in one of the above locations, or add it to your PATH." -ForegroundColor Green
|
||||
return
|
||||
Write-Host "And place it in one of these locations:" -ForegroundColor Green
|
||||
@(
|
||||
"C:\Sysinternals\handle.exe",
|
||||
"$env:ProgramFiles\Sysinternals\handle.exe",
|
||||
"Or add it to your PATH"
|
||||
) | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Scanning for open file handles in '$TargetDirectory'..."
|
||||
Write-Host "This may take a moment..."
|
||||
# 타이틀 박스 동적 생성
|
||||
$title = "Scanning for handles to: $TargetPath"
|
||||
$boxWidth = [Math]::Max($title.Length + 4, 60)
|
||||
$padding = $boxWidth - $title.Length - 2
|
||||
|
||||
Write-Host "╔$('═' * $boxWidth)╗" -ForegroundColor Cyan
|
||||
Write-Host "║ $title$(' ' * $padding)║" -ForegroundColor Cyan
|
||||
Write-Host "╚$('═' * $boxWidth)╝" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Execute handle.exe to get all open file handle information
|
||||
$handleOutput = ""
|
||||
$outputLines = @()
|
||||
|
||||
try {
|
||||
Write-Host "Executing handle.exe..." -ForegroundColor Gray
|
||||
$handleOutput = & $handleExePath -nobanner -a -u "$TargetDirectory" 2>$null | Out-String
|
||||
Write-Host "[*] Executing handle.exe (this may take 10-30 seconds)..." -ForegroundColor Gray
|
||||
|
||||
# 진행 상황 표시를 위한 타이머
|
||||
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
|
||||
# handle.exe를 비동기로 실행하고 진행 표시
|
||||
$job = Start-Job -ScriptBlock {
|
||||
param($exePath, $targetPath)
|
||||
& $exePath -accepteula -nobanner "$targetPath" 2>&1
|
||||
} -ArgumentList $HandleExePath, $TargetPath
|
||||
|
||||
# 진행 표시
|
||||
$spinnerChars = @('|', '/', '-', '\')
|
||||
$spinnerIndex = 0
|
||||
|
||||
while ($job.State -eq 'Running') {
|
||||
$elapsed = $stopwatch.Elapsed.TotalSeconds
|
||||
Write-Host "`r[*] Scanning system handles... $($spinnerChars[$spinnerIndex]) [$([math]::Round($elapsed, 1))s]" -NoNewline -ForegroundColor Yellow
|
||||
$spinnerIndex = ($spinnerIndex + 1) % 4
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
|
||||
$stopwatch.Stop()
|
||||
Write-Host "`r[✓] Scan completed in $([math]::Round($stopwatch.Elapsed.TotalSeconds, 1))s " -ForegroundColor Green
|
||||
|
||||
# 결과 가져오기
|
||||
$handleOutput = Receive-Job -Job $job
|
||||
Remove-Job -Job $job
|
||||
|
||||
$outputLines = $handleOutput
|
||||
}
|
||||
catch {
|
||||
Write-Host "[ERROR] Failed to execute handle.exe: $($_.Exception.Message)" -ForegroundColor Red
|
||||
return
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 결과를 저장할 배열 초기화
|
||||
$foundProcesses = @()
|
||||
# 결과를 저장할 배열 초기화 (ArrayList 사용으로 성능 향상)
|
||||
$foundProcesses = New-Object System.Collections.ArrayList
|
||||
$currentProcess = $null
|
||||
|
||||
# 디버깅: 출력 라인 수 표시
|
||||
Write-Host "[DEBUG] Total output lines: $($outputLines.Count)" -ForegroundColor DarkGray
|
||||
|
||||
# Analyze handle.exe output line by line
|
||||
if ($handleOutput) {
|
||||
# Use regex to extract process name, PID, and file path
|
||||
# handle.exe output format variations:
|
||||
# "ProcessName.exe pid: 1234 type: File C:\path\to\file"
|
||||
# "ProcessName.exe pid: 1234 user: DOMAIN\User type: File C:\path\to\file"
|
||||
$regex = "^(.+?)\s+pid:\s*(\d+)\s+.*?\s+type:\s+File\s+(.+?)\s*$"
|
||||
foreach ($line in $outputLines) {
|
||||
# "No matching handles found." 메시지 체크
|
||||
if ($line -match "No matching handles found") {
|
||||
Write-Host "✓ No processes are holding handles to this path." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
$handleOutput.Split([Environment]::NewLine) | ForEach-Object {
|
||||
if ($_ -match $regex) {
|
||||
$processName = $matches[1].Trim()
|
||||
$processId = [int]$matches[2]
|
||||
$filePath = $matches[3].Trim()
|
||||
|
||||
# 지정된 디렉토리 경로에 포함되는 결과만 필터링
|
||||
if ($filePath.StartsWith($TargetDirectory, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
# PSCustomObject를 생성
|
||||
$customObject = [PSCustomObject]@{
|
||||
ProcessName = $processName
|
||||
PID = $processId
|
||||
FilePath = $filePath
|
||||
}
|
||||
$foundProcesses += $customObject
|
||||
}
|
||||
# Process line format: "processname.exe pid: 1234 type: File user: DOMAIN\User"
|
||||
# 또는: "processname.exe pid: 1234 user: DOMAIN\User"
|
||||
if ($line -match '^(\S+\.exe)\s+pid:\s+(\d+)\s+(.*)$') {
|
||||
$processName = $matches[1]
|
||||
$processId = [int]$matches[2]
|
||||
$remainder = $matches[3]
|
||||
|
||||
# user 정보 추출
|
||||
$userName = ""
|
||||
if ($remainder -match 'user:\s+([^\s]+)') {
|
||||
$userName = $matches[1]
|
||||
}
|
||||
|
||||
$currentProcess = [PSCustomObject]@{
|
||||
ProcessName = $processName
|
||||
PID = $processId
|
||||
User = $userName
|
||||
Handles = New-Object System.Collections.ArrayList
|
||||
}
|
||||
[void]$foundProcesses.Add($currentProcess)
|
||||
|
||||
Write-Host "[DEBUG] Found process: $processName (PID: $processId)" -ForegroundColor DarkGray
|
||||
}
|
||||
# Handle detail line format: " 1A4: File (RWD) C:\path\to\file"
|
||||
# 또는 간단한 형식: " 1A4: C:\path\to\file"
|
||||
elseif ($line -match '^\s+([0-9A-F]+):\s+(.+)$' -and $currentProcess) {
|
||||
$handleId = $matches[1]
|
||||
$handleDetails = $matches[2].Trim()
|
||||
|
||||
# Type 정보가 있는지 확인
|
||||
$handleType = "File"
|
||||
if ($handleDetails -match '^(\w+)\s+(.+)$') {
|
||||
$handleType = $matches[1]
|
||||
$handleDetails = $matches[2].Trim()
|
||||
}
|
||||
|
||||
$handleInfo = [PSCustomObject]@{
|
||||
HandleID = $handleId
|
||||
Type = $handleType
|
||||
Details = $handleDetails
|
||||
}
|
||||
[void]$currentProcess.Handles.Add($handleInfo)
|
||||
|
||||
Write-Host "[DEBUG] Handle: $handleId -> $handleDetails" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
|
||||
# 핸들이 없는 프로세스 제거
|
||||
$processesWithHandles = New-Object System.Collections.ArrayList
|
||||
foreach ($proc in $foundProcesses) {
|
||||
if ($proc.Handles.Count -gt 0) {
|
||||
[void]$processesWithHandles.Add($proc)
|
||||
} else {
|
||||
Write-Host "[DEBUG] Removing process $($proc.ProcessName) (no handles found)" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
$foundProcesses = $processesWithHandles
|
||||
|
||||
# 파일로 출력 (옵션)
|
||||
if ($OutputFile) {
|
||||
$outputLines | Out-File -FilePath $OutputFile -Encoding UTF8
|
||||
Write-Host "[+] Raw output saved to: $OutputFile" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# 결과 출력
|
||||
if ($foundProcesses.Count -gt 0) {
|
||||
Write-Host "`n[+] Found $($foundProcesses.Count) open file handles in '$TargetDirectory':"
|
||||
$foundProcesses | Sort-Object -Property ProcessName, FilePath | Format-Table -AutoSize
|
||||
} else {
|
||||
Write-Host "`n[-] No open file handles were found for the specified directory."
|
||||
}
|
||||
if ($foundProcesses.Count -eq 0) {
|
||||
Write-Host "✓ No processes are holding handles to this path." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "⚠ Found $($foundProcesses.Count) process(es) with open handles:" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
foreach ($proc in $foundProcesses) {
|
||||
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
|
||||
Write-Host "Process: " -NoNewline
|
||||
Write-Host "$($proc.ProcessName)" -ForegroundColor White -NoNewline
|
||||
Write-Host " (PID: " -NoNewline
|
||||
Write-Host "$($proc.PID)" -ForegroundColor Cyan -NoNewline
|
||||
Write-Host ")"
|
||||
|
||||
if ($proc.User) {
|
||||
Write-Host " User: " -NoNewline -ForegroundColor Gray
|
||||
Write-Host "$($proc.User)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host " Handles: " -NoNewline -ForegroundColor Gray
|
||||
Write-Host "$($proc.Handles.Count)" -ForegroundColor Magenta
|
||||
|
||||
# 항상 핸들 상세 정보 표시
|
||||
if ($proc.Handles.Count -gt 0) {
|
||||
Write-Host ""
|
||||
foreach ($handle in $proc.Handles) {
|
||||
if ($ShowDetails) {
|
||||
# 상세 모드: Handle ID와 Type도 표시
|
||||
Write-Host " ├─ Handle ID: " -NoNewline -ForegroundColor DarkGray
|
||||
Write-Host "$($handle.HandleID)" -ForegroundColor Cyan
|
||||
Write-Host " │ Type: " -NoNewline -ForegroundColor DarkGray
|
||||
Write-Host "$($handle.Type)" -ForegroundColor Green
|
||||
Write-Host " │ Path: " -NoNewline -ForegroundColor DarkGray
|
||||
Write-Host "$($handle.Details)" -ForegroundColor White
|
||||
Write-Host " │" -ForegroundColor DarkGray
|
||||
} else {
|
||||
# 기본 모드: 경로만 표시
|
||||
Write-Host " • " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "$($handle.Details)" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
Write-Host "💡 To close these handles:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Close the applications manually" -ForegroundColor Gray
|
||||
Write-Host " 2. Use Task Manager to end the processes" -ForegroundColor Gray
|
||||
Write-Host " 3. Run: " -NoNewline -ForegroundColor Gray
|
||||
Write-Host "Stop-Process -Id <PID> -Force" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Reference in New Issue
Block a user