<< Back to Script Library

Show processes locking a file

For any file on the target computer for which the user provides the full path, displays a list of processes with open handles to the file. This is useful for determining which process is locking the file, preventing its deletion or editing in another program.
The action makes use of Sysinternals handle.exe, which is downloaded, extracted into a temporary location and deleted after completion.
Version: 1.3.3
Created: 2018-12-11
Modified: 2019-04-03
Creator: Guy Leech
Downloads: 163
Tags: disk handle parallels ras troubleshooting
The Script Copy Script Copied to clipboard
<#
    Use SysInternals handle.exe tool to find who has a certain file open

    @guyrleech 2018
#>

[string]$fileName = $args[0]
[string]$pathToHandle = $null
[bool]$downloadedHandle = $false
[int]$outputWidth = 400

if( $args.Count -ge 2 -and $args[1] )
{
    $pathToHandle = $args[1]
    if( ! ( Test-Path -Path $pathToHandle -ErrorAction SilentlyContinue ) )
    {
        Throw "Unable to find handle.exe at `"$pathToHandle`""
    }
}
else
{
    $pathToHandle = Join-Path $env:temp 'handle.controlup.exe'
    Write-Verbose "Downloading handle.exe to `"$pathToHandle`" ..."
    [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
    (New-Object System.Net.WebClient).DownloadFile( 'https://live.sysinternals.com/handle.exe' , $pathToHandle )
    if( ! ( Test-Path $pathToHandle -ErrorAction SilentlyContinue -PathType Leaf ) )
    {
        Throw "Failed to download handle to `"$pathToHandle`""
    }
    Unblock-File -Path $pathToHandle
    $downloadedHandle = $true
    $signing = Get-AuthenticodeSignature -FilePath $pathToHandle -ErrorAction SilentlyContinue
    if( ! $signing )
    {
        Throw "Could not get signing information from `"$pathToHandle`""
    }
    if( ! $signing.Status -ne 'Valid' )
    {
        Throw "Certificate status for `"$pathToHandle`" is $($signing.Status), not `"Valid`""
    }
    if( $signing.SignerCertificate.Subject -notmatch '^CN=Microsoft Corporation,' )
    {
        Throw "`"$pathToHandle`" is not signed by Microsoft Corporation, found $($signing.SignerCertificate.Subject)"
    }
}

[string]$escapedFileName = [regex]::Escape( $filename )
## an apparent bug in handle.exe where it fails to match on a full path means we get the whole output and grab our lines from them
[hashtable]$processLine = $null
[array]$results = @( & $pathToHandle -accepteula -nobanner -u | ForEach-Object `
{
    ## username could be "\<unable to open process>"
    ## SCService64.exe pid: 3468 NT AUTHORITY\NETWORK SERVICE
    if( $_ -match "^(?<Process>.*)\s+pid:\s+(?<Pid>\d+)\s(?<Username>[a-z0-9_\s<>\-\.]*\\[a-z0-9_\s<>\-\.]+)" )
    {
        $processLine = $Matches.Clone()
    }
    ##  420: File  (R--)   C:\Windows\assembly\pubpol3.dat
    elseif( $_ -match "(?<Handle>[0-9A-F]+):\s*File\s+\((?<Flags>[^\)]*)\)\s+(?<File>.*$escapedFileName.*)$" )
    {
        $fileProperties = Get-ItemProperty -Path $Matches[ 'File' ] -ErrorAction SilentlyContinue
        [pscustomobject][ordered]@{
            'File' = $Matches[ 'File' ]
            'File Owner' = ( Get-Acl -Path $Matches[ 'File' ] -ErrorAction SilentlyContinue | Select -ExpandProperty Owner )
            'Last Modified' = $( if( $fileProperties ) { (Get-Date $fileProperties.LastWriteTime -Format G) } )
            'Process' = $processLine[ 'Process' ]
            'Flags' = $Matches[ 'Flags' ]
            'Pid' = $processLine[ 'Pid' ]
            'User' = $(if( $processLine[ 'username' ] -notlike '*unable to open process*' ) { $processLine[ 'username' ] } )
            'Handle' = $Matches[ 'Handle' ] }
    }
})

# Altering the size of the PS Buffer
$PSWindow = (Get-Host).UI.RawUI
$WideDimensions = $PSWindow.BufferSize
$WideDimensions.Width = $outputWidth
$PSWindow.BufferSize = $WideDimensions

"Found $($results.Count) open instances of $fileName"

$results | Sort -Property 'File','Process' | Select-Object -Property * -ExcludeProperty Handle | Format-Table -AutoSize

if( $downloadedHandle )
{
    Remove-Item -Path $pathToHandle -Force
}