Update script for file handles

This commit is contained in:
2025-10-06 09:37:03 +09:00
parent a3c723c1f2
commit 0019f2b106

View File

@@ -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 ""