The situation
Mid-deploy, mid-build, or just trying to delete a log file โ and Windows throws:
The process cannot access the file because it is being used by another process.
Something has an open handle to that file. Windows won't let you touch it until that handle is closed. Track down what's holding the lock, then either close it gracefully or kill it. That's the whole game.
Debug process
Option 1: Resource Monitor (no install needed)
Built into every Windows install. Good enough for most cases.
- Press
Win + R, typeresmon, hit Enter. - Go to the CPU tab โ expand Associated Handles.
- Type the filename (or part of it) in the search box.
- The list shows the PID and process name holding that handle.
Right-click any result to end the process directly from there.
Option 2: Process Explorer (Sysinternals)
Reaches handles that Resource Monitor misses โ worth grabbing if you hit this error regularly.
- Download from
learn.microsoft.com/sysinternals/downloads/process-explorer - Run as Administrator.
- Press
Ctrl+Fto open Find Handle or DLL. - Type the filename. Every process holding that file appears in the list.
- Double-click the entry โ right-click the handle โ Close Handle.
Close Handle releases the lock without killing the whole process. Handy when the locker is a background thread inside a service you'd rather not restart.
Option 3: Handle.exe (CLI, scriptable)
Also from Sysinternals. Runs in PowerShell or CMD, making it ideal for scripts and remote sessions.
# Download: https://learn.microsoft.com/sysinternals/downloads/handle
# Run as Administrator
handle.exe C:\path\to\locked\file.txt
Output looks like:
node.exe pid: 14832 type: File C:\path\to\locked\file.txt
java.exe pid: 9120 type: File C:\path\to\locked\file.txt
Close a specific handle by hex ID:
handle.exe -c 1A4 -p 14832 -y
The -y flag skips the confirmation prompt. The hex handle ID comes from the first column of the previous output.
Option 4: PowerShell (no third-party tools)
No Sysinternals required โ but this only identifies which process owns the file. You can't close individual handles this way.
$lockedFile = "C:\path\to\locked\file.txt"
Get-Process | ForEach-Object {
$proc = $_
try {
$proc.Modules | Where-Object { $_.FileName -eq $lockedFile } | ForEach-Object {
[PSCustomObject]@{ PID = $proc.Id; Name = $proc.Name; File = $_.FileName }
}
} catch { }
} | Format-Table -AutoSize
This catches DLL-level locks. For general file handles on networked shares, openfiles is another option:
# Enable first (requires reboot):
openfiles /local on
# Then query:
openfiles /query /fo table | findstr /i "filename.txt"
Common culprits
- Antivirus / Windows Defender โ scans files the moment they're written. Extremely common during builds and deployments. Add your project folder to exclusions.
- Windows Search indexer (
SearchIndexer.exe) โ jumps on new files immediately. Disable indexing on dev and build directories. - Node.js / webpack watch mode โ holds handles on every file in the watch tree. All of them.
- Java processes โ JVM locks on
.jarfiles and temp files. - IIS / w3wp.exe โ locks DLLs in the app directory. Stop the app pool before you deploy.
- Explorer.exe โ thumbnail generation can lock image and video files.
- VS Code โ holds handles on workspace files and git objects while a project is open.
Solutions by scenario
Scenario: Build/deploy fails because a .dll or .exe is locked
# Stop IIS app pool first
Import-Module WebAdministration
Stop-WebAppPool -Name "MyAppPool"
# Do your deploy...
# Restart
Start-WebAppPool -Name "MyAppPool"
Or stop the entire IIS service during deploy:
iisreset /stop
# ... deploy ...
iisreset /start
Scenario: Can't delete a file, locker unknown
# 1. Find the PID with handle.exe
handle.exe locked-file.log
# 2. Kill the process (if safe)
Stop-Process -Id 14832 -Force
# 3. Delete the file
Remove-Item locked-file.log
Scenario: Rename/move fails in a script
Sometimes the lock clears in milliseconds โ antivirus finishing a scan, for instance. A retry loop handles this cleanly:
$maxRetries = 5
$retryDelay = 1 # seconds
$attempt = 0
while ($attempt -lt $maxRetries) {
try {
Move-Item -Path "source.tmp" -Destination "output.txt" -Force -ErrorAction Stop
Write-Host "Moved successfully"
break
} catch {
$attempt++
Write-Warning "Attempt $attempt failed: $($_.Exception.Message)"
Start-Sleep -Seconds $retryDelay
}
}
if ($attempt -eq $maxRetries) {
Write-Error "Failed to move file after $maxRetries attempts"
}
Scenario: Antivirus is the locker
Go to Windows Security โ Virus & threat protection โ Manage settings โ Exclusions, then add your build output folder. PowerShell works too:
Add-MpPreference -ExclusionPath "C:\projects\myapp\dist"
Add-MpPreference -ExclusionPath "C:\projects\myapp\node_modules"
Verify the fix
After releasing the lock, confirm nothing is still holding it:
handle.exe C:\path\to\file.txt
# Expected output:
# No matching handles found.
Retry your original operation. If the error comes back immediately, the same process re-acquired the lock. Closing the handle isn't enough โ you need to stop the process itself.
What to take away
- Resource Monitor is already on your machine. Use it first before downloading anything.
- Keep Handle.exe in
C:\tools\permanently. It pays for itself the first time a deploy breaks at 2am. - Windows Defender quietly kills build pipelines on dev machines and CI agents. Add exclusions for build output directories โ it's a 30-second fix that saves hours of confusion.
- Closing a handle via Process Explorer or Handle.exe can crash the owning process. Fine for a one-off unlock; don't automate this in production without understanding what that process was in the middle of doing.
- If you control the code writing the file: set
FileShareexplicitly and always close streams infinallyblocks orusingstatements. Most file-lock bugs in custom code trace back to a stream left open after an exception.

