How to Find the Process Locking a File Using PowerShell

To find the process locking a file using PowerShell, ask the Windows Restart Manager. It’s the same API installers use to see which apps hold a file, and it works without admin rights:

. .\FileLock.ps1
Get-FileLockProcess -Path C:\Reports\budget-2026.xlsx

FileLock.ps1 is a short script you’ll copy from this page. Below I also test whether a file is locked, scan a whole folder, and stop the process holding the file. I ran it all in PowerShell 7 and Windows PowerShell 5.1.

The file is being used by another process

When a program opens a file without sharing, Windows blocks other programs from changing it. For my tests, a hidden PowerShell window holds budget-2026.xlsx open, just like Excel would. Deleting it fails:

try {
    Remove-Item -Path .\q3-reports\budget-2026.xlsx -ErrorAction Stop
}
catch {
    "Failed. $($_.Exception.Message)"
}

Output:

Failed. The process cannot access the file 'C:\psfaqs\q3-reports\budget-2026.xlsx' because it is being used by another process.
PowerShell error the process cannot access the file because it is being used by another process
Remove-Item can’t delete a file another process holds open (PowerShell 7)

Rename, copy-over and Remove-Item all fail the same way. The message never says which process it is, so we have to find out.

Check if a file is locked with PowerShell

The quickest test is to open the file with no sharing allowed. If another process has it open, .NET throws an IOException:

function Test-FileLock ([string]$Path) {
    $full = (Resolve-Path -LiteralPath $Path).ProviderPath
    try {
        $stream = [System.IO.File]::Open($full, 'Open', 'Read', 'None')
        $stream.Close()
        $false
    }
    catch [System.IO.IOException] {
        $true
    }
}

foreach ($file in Get-ChildItem -Path .\q3-reports -File) {
    '{0,-18} locked: {1}' -f $file.Name, (Test-FileLock $file.FullName)
}

Output:

budget-2026.xlsx   locked: True
invoice-1042.pdf   locked: False
notes.txt          locked: False

Resolve-Path matters here. .NET methods resolve a relative path against the PowerShell process folder, not your current location. More on handling the exception in try and catch in PowerShell.

Find which process is locking a file

Save the script below as FileLock.ps1. It compiles a small C# class that calls the Restart Manager functions in rstrtmgr.dll, then wraps it in a Get-FileLockProcess function:

if (-not ('FileLock' -as [type])) {
    Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

public static class FileLock
{
    [StructLayout(LayoutKind.Sequential)]
    struct RM_UNIQUE_PROCESS
    {
        public int dwProcessId;
        public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct RM_PROCESS_INFO
    {
        public RM_UNIQUE_PROCESS Process;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string strAppName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string strServiceShortName;
        public int ApplicationType;
        public uint AppStatus;
        public uint TSSessionId;
        [MarshalAs(UnmanagedType.Bool)] public bool bRestartable;
    }

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
    static extern int RmStartSession(out uint handle, int flags, string key);
    [DllImport("rstrtmgr.dll")]
    static extern int RmEndSession(uint handle);
    [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
    static extern int RmRegisterResources(uint handle, uint nFiles, string[] files,
        uint nApps, RM_UNIQUE_PROCESS[] apps, uint nServices, string[] services);
    [DllImport("rstrtmgr.dll")]
    static extern int RmGetList(uint handle, out uint needed, ref uint count,
        [In, Out] RM_PROCESS_INFO[] info, ref uint rebootReasons);

    public static List<Process> GetLockingProcesses(string path)
    {
        uint handle;
        var result = new List<Process>();
        int rc = RmStartSession(out handle, 0, Guid.NewGuid().ToString());
        if (rc != 0) throw new Exception("RmStartSession failed: " + rc);
        try
        {
            rc = RmRegisterResources(handle, 1, new string[] { path }, 0, null, 0, null);
            if (rc != 0) throw new Exception("RmRegisterResources failed: " + rc);
            uint needed = 0, count = 0, reasons = 0;
            rc = RmGetList(handle, out needed, ref count, null, ref reasons);
            if (rc == 234)
            {
                var info = new RM_PROCESS_INFO[needed];
                count = needed;
                rc = RmGetList(handle, out needed, ref count, info, ref reasons);
                if (rc != 0) throw new Exception("RmGetList failed: " + rc);
                for (int i = 0; i < count; i++)
                {
                    try { result.Add(Process.GetProcessById(info[i].Process.dwProcessId)); }
                    catch (ArgumentException) { }   // the process already exited
                }
            }
            else if (rc != 0) throw new Exception("RmGetList failed: " + rc);
        }
        finally { RmEndSession(handle); }
        return result;
    }
}
'@
}

function Get-FileLockProcess {
    param([Parameter(Mandatory)][string]$Path)
    $full = (Resolve-Path -LiteralPath $Path).ProviderPath
    [FileLock]::GetLockingProcesses($full) | Select-Object Id, ProcessName, Path
}

The C# is plain enough for the older compiler in Windows PowerShell 5.1, so the same file works in both versions. Now dot-source it and pass the locked file:

. .\FileLock.ps1

Get-FileLockProcess -Path .\q3-reports\budget-2026.xlsx | Format-List

Output:

Id          : 57680
ProcessName : powershell
Path        : C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe
PowerShell find the process locking a file with Restart Manager
Get-FileLockProcess names the process and its PID (PowerShell 7)

It found my hidden powershell.exe and its process ID. With Excel, you’d see EXCEL here instead. Get-Process gives you more details about that PID.

Find locked files in a folder

Restart Manager won’t accept a folder path, so check each file in it. This lists every file in the folder and who holds it:

. .\FileLock.ps1

Get-ChildItem -Path .\q3-reports -File | ForEach-Object {
    $p = Get-FileLockProcess -Path $_.FullName
    [pscustomobject]@{
        File     = $_.Name
        LockedBy = $(if ($p) { '{0} (PID {1})' -f $p.ProcessName, $p.Id } else { '-' })
    }
} | Format-Table -AutoSize

Output:

File             LockedBy
----             --------
budget-2026.xlsx powershell (PID 47280)
invoice-1042.pdf -
notes.txt        -