Files
video-v1/Find-FileHandles.ps1
2025-10-06 09:37:03 +09:00

299 lines
10 KiB
PowerShell

<#
.SYNOPSIS
Find processes that have open file handles to a specific file or directory.
.DESCRIPTION
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 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 -Path "D:\Project\video-av1\vav2\docs"
.EXAMPLE
.\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 (
[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
# 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
}
}
# 경로 검증 및 정규화
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!" -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 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
}
# 타이틀 박스 동적 생성
$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 (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
exit 1
}
# 결과를 저장할 배열 초기화 (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
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
}
# 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 -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 ""