Tag Archives: deployments

Find and kill processes that lock a file or directory

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

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

# === 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

foreach($pidStr in $pidList) {
    $pidInt = [int]::Parse($pidStr)    
    Stop-Process -Id $pidInt -Force
    Write-Host "Killed process $pidInt"

Write-Host "Finished"

Hope that helps!