It’s an old problem when doing installations: some directory or file are locked by some process, and the installation fails.
As I’ve had this exact problem in the context of a web application, I’ve written this powershell script to take care of it. The idea was taken from the Octopus Deploy Blog.
So what the script does is:
– downloads the Microsoft Handle utility (in %temp%)
– uses it to scan the $PathToCheck (can be a file or directory; if it’s a dir, it scans subdirs and all files as well)
– parses the output
– if needed, kills all processes mentioned
#
# Source: DotJim blog (http://dandraka.com)
# Jim Andrakakis, August 2019
#
param([string]$PathToCheck = "c:\temp")
Clear-Host
$ErrorActionPreference = "Stop"
$url = "https://download.sysinternals.com/files/Handle.zip"
# === download handle.exe from microsoft ===
$tempDir = [System.IO.Path]::GetTempPath()
$handlePath = [System.IO.Path]::Combine($tempDir, "handle64.exe")
if (-not (Test-Path $handlePath)) {
$output = [System.IO.Path]::Combine($tempDir, "handle.zip")
Invoke-WebRequest -Uri $url -OutFile $output
Expand-Archive -LiteralPath $output -OutputPath $tempDir
}
# === run handle.exe ===
# see https://octopus.com/blog/how-to-handle-locked-files to see why the reg entry is needed
& reg.exe ADD "HKCU\Software\Sysinternals\Handle" /v EulaAccepted /t REG_DWORD /d 1 /f | Out-Null
$handleOutput = & $handlePath -a $PathToCheck
# === do we have to kill anything? ===
if ($handleOutput -match "no matching handles found") {
Write-Host "Nothing to kill, exiting"
exit
}
# === get process ids from handle.exe output ===
$pidList = New-Object System.Collections.ArrayList
$lines = $handleOutput | Split-String -RemoveEmptyStrings -separator "`n"
foreach($line in $lines) {
# sample line:
# chrome.exe pid: 11392 type: File 5BC: C:\Windows\Fonts\timesbd.ttf
# regex to get pid and process name: (.*)\b(?:.*)(?:pid: )(\d*)
$matches = $null
$line -match "(.*)\b(?:.*)(?:pid: )(\d*)" | Out-Null
if (-not $matches) { continue }
if ($matches.Count -eq 0) { continue }
$pidName = $matches[1]
$pidStr = $matches[2]
if ($pidList -notcontains $pidStr) {
Write-Host "Will kill process $pidStr $pidName"
$pidList.Add($pidStr) | Out-Null
}
}
# === DIE PROCESS DIE ===
foreach($pidStr in $pidList) {
$pidInt = [int]::Parse($pidStr)
Stop-Process -Id $pidInt -Force
Write-Host "Killed process $pidInt"
}
Write-Host "Finished"
Hope that helps!