Windows knows exactly which program has your file open. It just refuses to tell you.
The action can't be completed because the file is open in another program
That dialog is the single least helpful message in Windows. It knows the answer. It will not say it. So you close OBS, you close Premiere, you close Explorer, you give up and reboot.
filelock asks the Restart Manager - the same API Windows Setup uses to work
out what it has to close before an update - and prints the answer: the PID, the
image name, the account, how long it has been running, and what to do about it.
A single PowerShell script. No install, no dependencies, no driver, no admin needed for your own files. It is read-only: it never closes, kills or restarts anything.
.\filelock.ps1 "D:\Recordings\stream-2026-03-07.mkv"filelock 1.0.0 2026-09-13 22:14:58.213
PC\hayyz on PC (elevated)
Target C:\Users\hayyz\AppData\Local\Temp\fl-demo\clips\stream-2026-03-07.mkv
size 2.00 GB modified 2026-09-13 22:14:49.511
Status HELD by 1 process
One process has this file open. Details below.
[1] PID 15576 powershell.exe
App name Windows PowerShell
Type Console application (RmConsole)
Running as PC\hayyz
Started 2026-09-13 22:14:49.598 (up 8.6 s)
PID verified yes - the Restart Manager start time matches the live process
State running
Restartable no - Windows will not offer to restart it
Session 1
Image C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe
-> Close the console window running that command.
Summary 1 target, 1 held, 0 free
1 holding process found in 461.8 ms
That is the whole idea. PID 15576, powershell.exe, started 8.6 seconds ago, and here is what to do.
- OBS will not let you move or delete last night's recording.
- A render fails with "access denied" on a file nothing is visibly using.
- You cannot eject a drive and Windows will not say which file is busy.
- Premiere or Resolve keeps a media file locked after you closed the project.
- Explorer's preview pane is silently holding the clip you clicked on once.
- A backup or sync tool has a handle open and you cannot work out which one.
You could install Sysinternals Process Explorer, open it, elevate it, learn its
Find-Handle dialog and search by substring. filelock is one command and prints a
name.
There is nothing to install. Download filelock.ps1 and run it.
git clone https://github.com/appsmypass/filelock.git
cd filelock
.\filelock.ps1 "C:\path\to\your\file.mkv"If PowerShell blocks the script:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\filelock.ps1 "C:\path\to\file.mkv"That runs it once without changing any machine setting.
Requirements: Windows (any version with rstrtmgr.dll, which is Vista and
later) and Windows PowerShell 5.1, which ships in the box. Tested on Windows 11
build 10.0.26200 with PowerShell 5.1.26100.9444.
Every holder gets a full record, not just a PID:
| Field | What it is for |
|---|---|
PID |
The number you need for Task Manager or Stop-Process |
App name |
The friendly name Windows shows, e.g. OBS Studio |
Type |
Main window, console, service, Explorer, or critical system process |
Running as |
The account, so you know if it is yours or SYSTEM |
Started |
Exact start time, to the millisecond |
PID verified |
Whether the PID still belongs to the same process (see below) |
Restartable |
Whether Windows would be willing to close and reopen it |
Session |
Session 0 means a service; session 1 is your desktop |
Image |
Full path to the executable |
-> |
Plain-English advice for that specific kind of holder |
PIDs get recycled. Between the moment the Restart Manager answers and the moment
you read the screen, the process can exit and a completely unrelated program can
inherit that number. filelock compares the process start time reported by the
Restart Manager against the live process and tells you whether they still agree,
to within 20 milliseconds. If it says yes, that PID is still the process that
holds your file.
Point it at a folder to find which file in a batch is the problem:
.\filelock.ps1 D:\Recordings -Recurse -Include *.mkvfilelock 1.0.0 2026-09-13 22:15:00.962
PC\hayyz on PC (elevated)
Target C:\Users\hayyz\AppData\Local\Temp\fl-demo\clips\stream-2026-03-07.mkv
size 2.00 GB modified 2026-09-13 22:14:49.511
Status HELD by 1 process
One process has this file open. Details below.
[1] PID 15576 powershell.exe
App name Windows PowerShell
Type Console application (RmConsole)
Running as PC\hayyz
Started 2026-09-13 22:14:49.598 (up 11.4 s)
PID verified yes - the Restart Manager start time matches the live process
State running
Restartable no - Windows will not offer to restart it
Session 1
Image C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe
-> Close the console window running that command.
Summary 2 targets, 1 held, 1 free
1 holding process found in 412.3 ms
Free files are hidden by default so the held one cannot get lost in the noise.
Add -All to list everything:
.\filelock.ps1 D:\Recordings -AllTarget C:\Users\hayyz\AppData\Local\Temp\fl-demo\clips\intro.mp4
size 18.0 MB modified 2026-09-13 22:14:49.511
Status FREE
Nothing is holding this file. If Windows still refuses, the problem
is permissions, not a lock.
Target C:\Users\hayyz\AppData\Local\Temp\fl-demo\clips\raid-night.mkv
size 700 MB modified 2026-09-13 22:14:49.511
Status FREE
Nothing is holding this file. If Windows still refuses, the problem
is permissions, not a lock.
Target C:\Users\hayyz\AppData\Local\Temp\fl-demo\clips\stream-2026-03-07.mkv
size 2.00 GB modified 2026-09-13 22:14:49.511
Status HELD by 1 process
...
Summary 3 targets, 1 held, 2 free
1 holding process found in 437.0 ms
Note the FREE wording. If nothing holds the file and Windows still refuses to
let you touch it, you have a permissions problem, not a lock problem - and
knowing which of the two you are fighting is most of the battle.
A service holding a file is a different problem from an app holding one, and
filelock gives different advice for each. Here it is on a live registry hive:
.\filelock.ps1 C:\Windows\System32\config\SOFTWARETarget C:\Windows\System32\config\SOFTWARE
size 90.0 MB modified 2026-09-13 11:16:22.423
Status HELD by 2 processes
2 processes have this file open. Details below.
[1] PID 184 Registry
App name Registry
Type Critical process (RmCritical)
Running as NT AUTHORITY\SYSTEM
Started 2026-09-13 11:16:40.578 (up 10 h 58 m)
PID verified yes - the Restart Manager start time matches the live process
State running
Restartable no - Windows will not offer to restart it
Session 0
Image (not readable)
-> This is a critical system process. Do not end it. The file will be
-> released when Windows is ready.
[2] PID 4 System
App name System
Type Critical process (RmCritical)
Running as (not readable)
Started 2026-09-13 11:16:42.880 (up 10 h 58 m)
PID verified yes - the Restart Manager start time matches the live process
State running
Restartable no - Windows will not offer to restart it
Session 0
Image (not readable)
-> This is a critical system process. Do not end it. The file will be
-> released when Windows is ready.
Restart Manager notes
- a holder could not be inspected with the rights you have
- a holder belongs to a different session
Summary 1 target, 1 held, 0 free
2 holding processes found in 493.7 ms
When the holder is a service, the advice names the exact command:
net stop <servicename> - because killing a service process is how you get a
machine that will not shut down cleanly.
When it is Explorer, the advice reminds you about the preview pane, which is the real culprit far more often than anyone expects.
This is the part that matters, and it is where naive versions of this tool get it
wrong. The Restart Manager has several ways of saying "I cannot tell you", and
every one of them looks like "nothing is holding this file" if you do not check.
filelock reports each one honestly.
Core system DLLs. So many processes map ntdll.dll that the Restart Manager
gives up rather than enumerate them:
Target C:\Windows\System32\ntdll.dll
size 2.41 MB modified 2026-09-08 12:14:29.398
Status TOO MANY
ERROR_INVALID_HANDLE (6) - the Restart Manager gave up enumerating the holders
Too many processes map this file for the Restart Manager to
enumerate.
That is normal for core system DLLs such as ntdll.dll, which every
process maps. There is no way to get the list for a file in this
state.
Summary 1 target, 0 held, 0 free, 1 not queryable
0 holding processes found in 215.5 ms
A path that is not there.
Target C:\Users\hayyz\AppData\Local\Temp\fl-demo\clips\nope.mkv
Status NOT FOUND
The path does not exist. Check the spelling; a relative path is
resolved against the current folder.
Summary 1 target, 0 held, 0 free, 1 not queryable
0 holding processes found in 81.7 ms
A file your account may not read. Unelevated, a protected file returns access
denied. filelock says DENIED, never FREE. It also tells DENIED apart from
NOT FOUND, because Windows reports an unreadable file and a missing file the
same way and the fix is completely different.
A path longer than 260 characters. The Restart Manager rejects it with
ERROR_WRITE_FAULT (29), and the \\?\ long-path prefix does not lift that
limit. filelock reports PATH TOO LONG with the actual length.
A relative path. This one is a genuine trap in the API: hand the Restart
Manager a relative path and it cheerfully returns success with an empty holder
list. A tool that does not resolve paths first will tell you a locked file is
free. filelock always converts to a full path before it asks, and there is a
test that fails if that ever stops happening.
| Code | Meaning |
|---|---|
0 |
The query ran |
1 |
Bad arguments |
2 |
Nothing could be queried (missing, denied, too long, too many) |
3 |
-FailIfHeld was set and something was held |
Gate a script on a clean folder before you start a render or an upload:
.\filelock.ps1 D:\Recordings -Recurse -Quiet -FailIfHeld
if ($LASTEXITCODE -eq 3) { Write-Host "something still has a clip open"; exit 1 }-Json prints the whole report as JSON. -Save writes it to a file as UTF-8
without a BOM (not the UTF-16 you get from > file.json in PowerShell 5.1):
.\filelock.ps1 D:\Recordings -Recurse -Save locks.jsonsaved report to C:\Users\hayyz\AppData\Local\Temp\fl-demo\locks.json
-FromJson replays a saved report, and says so rather than passing a snapshot
off as a live reading:
.\filelock.ps1 -FromJson locks.jsonfilelock 1.0.0 2026-09-13 22:15:13.861
PC\hayyz on PC (elevated)
replayed from C:\Users\hayyz\AppData\Local\Temp\fl-demo\locks.json - a saved snapshot, not a live query
Target C:\Users\hayyz\AppData\Local\Temp\fl-demo\clips\stream-2026-03-07.mkv
size 2.00 GB modified 2026-09-13 22:14:49.511
Status HELD by 1 process
One process has this file open. Details below.
[1] PID 15576 powershell.exe
...
Summary 3 targets, 1 held, 2 free
1 holding process found in 476.1 ms
Aside from that one honest banner line, the replay is character-for-character identical to the original render. There is a test that asserts exactly that.
USAGE
filelock.ps1 <path> [<path> ...] [options]
OPTIONS
-Path <p[,p]> File(s) or folder(s) to inspect. Also accepted positionally.
-Recurse Scan sub-folders too when a folder is given.
-Include <glob> When a folder is scanned, only files matching this pattern.
-All Also list targets that nothing is holding.
-Top <n> Stop after n targets.
-Json Print the report as JSON instead of text.
-Save <file> Write the JSON report to a file (UTF-8, no BOM).
-FromJson <file> Render a previously saved report instead of querying.
-Info Show environment details and exit.
-Quiet Suppress the report; useful with -Save or -FailIfHeld.
-FailIfHeld Exit with code 3 if any target is held.
-Version Print the version and exit.
-Help Show this text.
-Info prints what it is running on, which is the first thing to paste into a bug
report:
filelock 1.0.0
Machine PC
User PC\hayyz (elevated)
OS Microsoft Windows 11 Pro build 10.0.26200.0
PowerShell 5.1.26100.9444
Architecture AMD64 64-bit process: True
rstrtmgr.dll 10.0.26100.1 (WinBuild.160101.0800)
RM path limit 260 characters
Working dir C:\Users\hayyz\.copilot\chats\e24acb27-52a2-46fc-97cb-68dd76caf860
filelock is read-only. It never closes, kills or restarts anything.
For your own files, no. Unelevated, filelock finds holders of your recordings,
your documents and your downloads, and it still names Explorer.
For protected files - registry hives, event logs, anything under
C:\Windows\System32\config - Windows returns access denied to a standard user.
filelock reports DENIED and tells you to re-run elevated. It does not quietly
report FREE, which would be worse than useless.
RmStartSessionopens a Restart Manager session.RmRegisterResourcesregisters the one file, by full path.RmGetListreturns an array ofRM_PROCESS_INFO, one per holder.RmEndSessioncloses the session.
Then, for each holder, OpenProcess plus QueryFullProcessImageName gets the
image path and OpenProcessToken plus GetTokenInformation gets the owning
account - both handled gracefully when the process is protected and neither is
available.
One session per file, because RmGetList does not attribute holders back to
individual files when several are registered at once. A single query takes about
10 milliseconds.
Nothing is written anywhere. RmEndSession leaves no registry key behind - the
suite asserts that too.
Three suites, because "it printed something" is not a test.
selftest.ps1 - 394 hermetic assertions. Every pure function - path
resolution, the 260-character limit, the Restart Manager error and bit-flag
tables, byte formatting, holder sorting, verdict wording, the JSON round trip,
the help text, ASCII hygiene - with no machine state involved. Runs in seconds.
selftest: 394 passed, 0 failed
realcheck.ps1 - 178 assertions against the live machine, each with ground
truth this suite constructs itself. It starts a child process, makes it hold a
known file, and demands that filelock names that exact PID; it cross-checks the
reported start time against Get-Process, the reported service name against the
Service Control Manager, and the reported Explorer PID against the running
explorer.exe. It checks that a relative path and an absolute path give the same
answer, that closing the handle flips held to free, that awkward file names
(spaces, brackets, apostrophes, non-ASCII) work, that the target's hash and
timestamp are untouched by a query, and it re-runs the tool unelevated via
runas /trustlevel:0x20000 to prove a protected file comes back DENIED rather
than FREE.
realcheck: 178 passed, 0 failed, 0 skipped
mutate.ps1 - the suites have to be able to fail. This plants 36 deliberate
bugs in filelock.ps1 one at a time - the 260 limit becomes 300, access denied is
reported as free, the relative path is passed through unresolved, the detected-self
bit moves, the plural is inverted - and requires a suite to catch every one. A
mutation that survives is a hole in the tests, not a pass. It also runs a
behaviour-free comment edit first as a tripwire: if that "fails", the suites are
flaky and every kill is suspect. The original is backed up beside itself with a
marker file, so an interrupted run is repaired on the next start instead of
leaving a deliberately broken tool on disk.
mutate: 36 killed, 0 survived, 0 harness errors
Run them yourself:
.\selftest.ps1
.\realcheck.ps1
.\mutate.ps1- obs-4k60-recorder - OBS settings for genuinely smooth 4K60 capture.
- diskbusy - find out what is hammering your disk while you record.
- diskscout - find the big files eating your recording drive.
- dupefind - find duplicate clips by content, not by name.
- pathfix - find and fix the long and illegal paths that break renders.
MIT. Copyright (c) 2026 appsmypass. See LICENSE.