locked
How do I detect a file is already open? RRS feed

  • Question

  • I inherited a large legacy program that uses code similar to this for accessing text files

    Dim Fnum As Long = = FreeFile()
    FileOpen(Fnum, myFilename, OpenMode.Output)
    
    ... do stuff ...
    
    FileClose(Fnum)
    Is there a way to detect if the file specified by myFilename is already open?
    Wednesday, January 29, 2014 3:40 PM

Answers

  • I inherited a large legacy program that uses code similar to this for accessing text files

    Dim Fnum As Long = = FreeFile()
    FileOpen(Fnum, myFilename, OpenMode.Output)
    
    ... do stuff ...
    
    FileClose(Fnum)
    Is there a way to detect if the file specified by myFilename is already open?

    Unless a file is locked by a program when the program opens it then it may not be possible to tell if the file is open. For example Notepad copies a textfiles data into itself but does not lock the file open. So any other program could access the file while it is in "use" by Notepad. Same with Paint.

    Apparently to detect if a file is already "open" or locked by another program I suppose programmers just attempt to open it and if it's locked by another program then it will not open and the error for trying to open it and failing to do so due to the lock is used to determine if the file is already open.

    This code, if a file is locked, will provide the process name and Id of the process locking the file. And other information about the process if the code is changed for that.

    In the image below Windows Media Player has a file locked while it is playing the file.

    Option Strict On
    
    Imports System.Runtime.InteropServices
    
    Public Class Form1
    
        ' Code converted from thread http://stackoverflow.com/questions/317071/how-do-i-find-out-which-process-is-locking-a-file-using-net
    
        <DllImport("rstrtmgr.dll", CharSet:=CharSet.Unicode)> _
        Private Shared Function RmRegisterResources(pSessionHandle As UInteger, nFiles As UInt32, rgsFilenames As String(), nApplications As UInt32, <[In]> rgApplications As RM_UNIQUE_PROCESS(), nServices As UInt32, _
            rgsServiceNames As String()) As Integer
        End Function
    
        <DllImport("rstrtmgr.dll", CharSet:=CharSet.Auto)> _
        Private Shared Function RmStartSession(ByRef pSessionHandle As UInteger, dwSessionFlags As Integer, strSessionKey As String) As Integer
        End Function
    
        <DllImport("rstrtmgr.dll")> _
        Private Shared Function RmEndSession(pSessionHandle As UInteger) As Integer
        End Function
    
        <DllImport("rstrtmgr.dll")> _
        Private Shared Function RmGetList(dwSessionHandle As UInteger, ByRef pnProcInfoNeeded As UInteger, ByRef pnProcInfo As UInteger, <[In], Out> rgAffectedApps As RM_PROCESS_INFO(), ByRef lpdwRebootReasons As UInteger) As Integer
        End Function
    
        <StructLayout(LayoutKind.Sequential)> _
        Private Structure RM_UNIQUE_PROCESS
            Public dwProcessId As Integer
            Public ProcessStartTime As System.Runtime.InteropServices.ComTypes.FILETIME
        End Structure
    
        Const RmRebootReasonNone As Integer = 0
        Const CCH_RM_MAX_APP_NAME As Integer = 255
        Const CCH_RM_MAX_SVC_NAME As Integer = 63
    
        Private Enum RM_APP_TYPE
            RmUnknownApp = 0
            RmMainWindow = 1
            RmOtherWindow = 2
            RmService = 3
            RmExplorer = 4
            RmConsole = 5
            RmCritical = 1000
        End Enum
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
        Private Structure RM_PROCESS_INFO
            Public Process As RM_UNIQUE_PROCESS
    
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCH_RM_MAX_APP_NAME + 1)> _
            Public strAppName As String
    
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCH_RM_MAX_SVC_NAME + 1)> _
            Public strServiceShortName As String
    
            Public ApplicationType As RM_APP_TYPE
            Public AppStatus As UInteger
            Public TSSessionId As UInteger
            <MarshalAs(UnmanagedType.Bool)> _
            Public bRestartable As Boolean
        End Structure
    
        Dim LockedFileProcesses As New List(Of Process)
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Me.CenterToScreen()
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            LockedFileProcesses.Clear()
            RichTextBox1.Text = ""
            Dim OFD As New OpenFileDialog
            OFD.InitialDirectory = "C:\Users\John\Desktop"
            OFD.Title = "Check Locked File"
            OFD.Multiselect = False
            If OFD.ShowDialog = Windows.Forms.DialogResult.OK Then
                LockedFileProcesses = WhoIsLocking(OFD.FileName)
                If LockedFileProcesses.Count > 0 Then
                    For Each Item In LockedFileProcesses
                        RichTextBox1.AppendText(Item.ProcessName.ToString & " .. " & Item.Id.ToString & vbCrLf)
                    Next
                End If
            End If
        End Sub
    
        Private Function WhoIsLocking(path As String) As List(Of Process)
            Dim handle As UInteger
            Dim key As String = Guid.NewGuid().ToString()
            Dim processes As New List(Of Process)()
    
            Dim res As Integer = RmStartSession(handle, 0, key)
            If res <> 0 Then
                Throw New Exception("Could not begin restart session.  Unable to determine file locker.")
            End If
    
            Try
                Const ERROR_MORE_DATA As Integer = 234
                Dim pnProcInfoNeeded As UInteger = 0, pnProcInfo As UInteger = 0, lpdwRebootReasons As UInteger = RmRebootReasonNone
    
                Dim resources As String() = New String() {path}
                ' Just checking on one resource.
                res = RmRegisterResources(handle, CUInt(resources.Length), resources, 0, Nothing, 0, Nothing)
    
                If res <> 0 Then
                    Throw New Exception("Could not register resource.")
                End If
    
                'Note: there's a race condition here -- the first call to RmGetList() returns
                '      the total number of process. However, when we call RmGetList() again to get
                '      the actual processes this number may have increased.
                res = RmGetList(handle, pnProcInfoNeeded, pnProcInfo, Nothing, lpdwRebootReasons)
    
                If res = ERROR_MORE_DATA Then
                    ' Create an array to store the process results
                    Dim processInfo As RM_PROCESS_INFO() = New RM_PROCESS_INFO(CInt(pnProcInfoNeeded - 1)) {}
                    pnProcInfo = pnProcInfoNeeded
    
                    ' Get the list
                    res = RmGetList(handle, pnProcInfoNeeded, pnProcInfo, processInfo, lpdwRebootReasons)
                    If res = 0 Then
                        processes = New List(Of Process)(CInt(pnProcInfo))
    
                        ' Enumerate all of the results and add them to the 
                        ' list to be returned
                        For i As Integer = 0 To CInt(pnProcInfo - 1)
                            Try
                                processes.Add(Process.GetProcessById(processInfo(i).Process.dwProcessId))
                                ' catch the error -- in case the process is no longer running
                            Catch generatedExceptionName As ArgumentException
                            End Try
                        Next
                    Else
                        Throw New Exception("Could not list processes locking resource.")
                    End If
                ElseIf res <> 0 Then
                    Throw New Exception("Could not list processes locking resource. Failed to get size of result.")
                End If
            Finally
                RmEndSession(handle)
            End Try
    
            Return processes
        End Function
    
    End Class
    


    Please BEWARE that I have NO EXPERIENCE and NO EXPERTISE and probably onset of DEMENTIA which may affect my answers! Also, I've been told by an expert, that when you post an image it clutters up the thread and mysteriously, over time, the link to the image will somehow become "unstable" or something to that effect. :) I can only surmise that is due to Global Warming of the threads.

    • Proposed as answer by IronRazerz Wednesday, January 29, 2014 8:56 PM
    • Marked as answer by Leo (Apple) Yang Wednesday, February 5, 2014 9:04 AM
    Wednesday, January 29, 2014 4:14 PM
  • Hello,

    As per Mr. MonkeyBox nothing is guaranteed especially with utilities such as NotePad.

    Here is sample code where if you have E1.xlsx open this will indicate we cannot open it else we open it then immediately close it for this demo. If we leave the stream open other processes will not be able to use the file.

    Imports System.IO
    Imports System.Runtime.InteropServices
    Module FileOpenTesting
        Public Sub Testbelow()
            IsFileOpen(New FileInfo(IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "E1.xlsx")))
            IsFileOpen(New FileInfo(IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "T1.txt")))
        End Sub
        Public Sub IsFileOpen(ByVal file As FileInfo)
            Dim stream As FileStream = Nothing
            Try
                stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)
                Console.WriteLine("Open")
                '
                ' Do work or simply close the stream
                '
                stream.Close()
            Catch ex As Exception
    
                If TypeOf ex Is IOException AndAlso IsFileLocked(ex) Then
                    ' do something here, either close the file if you have a handle or as a last resort terminate the process - 
                    ' which could cause corruption and lose data
    
                    Console.WriteLine("Not open")
                End If
            End Try
        End Sub
        Private Function IsFileLocked(exception As Exception) As Boolean
            Dim errorCode As Integer = Marshal.GetHRForException(exception) And ((1 << 16) - 1)
            Return errorCode = 32 OrElse errorCode = 33
        End Function
    End Module
    


    Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem.

    • Proposed as answer by IronRazerz Wednesday, January 29, 2014 8:59 PM
    • Marked as answer by Leo (Apple) Yang Wednesday, February 5, 2014 9:04 AM
    Wednesday, January 29, 2014 6:51 PM

All replies

  • I inherited a large legacy program that uses code similar to this for accessing text files

    Dim Fnum As Long = = FreeFile()
    FileOpen(Fnum, myFilename, OpenMode.Output)
    
    ... do stuff ...
    
    FileClose(Fnum)
    Is there a way to detect if the file specified by myFilename is already open?

    Unless a file is locked by a program when the program opens it then it may not be possible to tell if the file is open. For example Notepad copies a textfiles data into itself but does not lock the file open. So any other program could access the file while it is in "use" by Notepad. Same with Paint.

    Apparently to detect if a file is already "open" or locked by another program I suppose programmers just attempt to open it and if it's locked by another program then it will not open and the error for trying to open it and failing to do so due to the lock is used to determine if the file is already open.

    This code, if a file is locked, will provide the process name and Id of the process locking the file. And other information about the process if the code is changed for that.

    In the image below Windows Media Player has a file locked while it is playing the file.

    Option Strict On
    
    Imports System.Runtime.InteropServices
    
    Public Class Form1
    
        ' Code converted from thread http://stackoverflow.com/questions/317071/how-do-i-find-out-which-process-is-locking-a-file-using-net
    
        <DllImport("rstrtmgr.dll", CharSet:=CharSet.Unicode)> _
        Private Shared Function RmRegisterResources(pSessionHandle As UInteger, nFiles As UInt32, rgsFilenames As String(), nApplications As UInt32, <[In]> rgApplications As RM_UNIQUE_PROCESS(), nServices As UInt32, _
            rgsServiceNames As String()) As Integer
        End Function
    
        <DllImport("rstrtmgr.dll", CharSet:=CharSet.Auto)> _
        Private Shared Function RmStartSession(ByRef pSessionHandle As UInteger, dwSessionFlags As Integer, strSessionKey As String) As Integer
        End Function
    
        <DllImport("rstrtmgr.dll")> _
        Private Shared Function RmEndSession(pSessionHandle As UInteger) As Integer
        End Function
    
        <DllImport("rstrtmgr.dll")> _
        Private Shared Function RmGetList(dwSessionHandle As UInteger, ByRef pnProcInfoNeeded As UInteger, ByRef pnProcInfo As UInteger, <[In], Out> rgAffectedApps As RM_PROCESS_INFO(), ByRef lpdwRebootReasons As UInteger) As Integer
        End Function
    
        <StructLayout(LayoutKind.Sequential)> _
        Private Structure RM_UNIQUE_PROCESS
            Public dwProcessId As Integer
            Public ProcessStartTime As System.Runtime.InteropServices.ComTypes.FILETIME
        End Structure
    
        Const RmRebootReasonNone As Integer = 0
        Const CCH_RM_MAX_APP_NAME As Integer = 255
        Const CCH_RM_MAX_SVC_NAME As Integer = 63
    
        Private Enum RM_APP_TYPE
            RmUnknownApp = 0
            RmMainWindow = 1
            RmOtherWindow = 2
            RmService = 3
            RmExplorer = 4
            RmConsole = 5
            RmCritical = 1000
        End Enum
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
        Private Structure RM_PROCESS_INFO
            Public Process As RM_UNIQUE_PROCESS
    
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCH_RM_MAX_APP_NAME + 1)> _
            Public strAppName As String
    
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCH_RM_MAX_SVC_NAME + 1)> _
            Public strServiceShortName As String
    
            Public ApplicationType As RM_APP_TYPE
            Public AppStatus As UInteger
            Public TSSessionId As UInteger
            <MarshalAs(UnmanagedType.Bool)> _
            Public bRestartable As Boolean
        End Structure
    
        Dim LockedFileProcesses As New List(Of Process)
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Me.CenterToScreen()
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            LockedFileProcesses.Clear()
            RichTextBox1.Text = ""
            Dim OFD As New OpenFileDialog
            OFD.InitialDirectory = "C:\Users\John\Desktop"
            OFD.Title = "Check Locked File"
            OFD.Multiselect = False
            If OFD.ShowDialog = Windows.Forms.DialogResult.OK Then
                LockedFileProcesses = WhoIsLocking(OFD.FileName)
                If LockedFileProcesses.Count > 0 Then
                    For Each Item In LockedFileProcesses
                        RichTextBox1.AppendText(Item.ProcessName.ToString & " .. " & Item.Id.ToString & vbCrLf)
                    Next
                End If
            End If
        End Sub
    
        Private Function WhoIsLocking(path As String) As List(Of Process)
            Dim handle As UInteger
            Dim key As String = Guid.NewGuid().ToString()
            Dim processes As New List(Of Process)()
    
            Dim res As Integer = RmStartSession(handle, 0, key)
            If res <> 0 Then
                Throw New Exception("Could not begin restart session.  Unable to determine file locker.")
            End If
    
            Try
                Const ERROR_MORE_DATA As Integer = 234
                Dim pnProcInfoNeeded As UInteger = 0, pnProcInfo As UInteger = 0, lpdwRebootReasons As UInteger = RmRebootReasonNone
    
                Dim resources As String() = New String() {path}
                ' Just checking on one resource.
                res = RmRegisterResources(handle, CUInt(resources.Length), resources, 0, Nothing, 0, Nothing)
    
                If res <> 0 Then
                    Throw New Exception("Could not register resource.")
                End If
    
                'Note: there's a race condition here -- the first call to RmGetList() returns
                '      the total number of process. However, when we call RmGetList() again to get
                '      the actual processes this number may have increased.
                res = RmGetList(handle, pnProcInfoNeeded, pnProcInfo, Nothing, lpdwRebootReasons)
    
                If res = ERROR_MORE_DATA Then
                    ' Create an array to store the process results
                    Dim processInfo As RM_PROCESS_INFO() = New RM_PROCESS_INFO(CInt(pnProcInfoNeeded - 1)) {}
                    pnProcInfo = pnProcInfoNeeded
    
                    ' Get the list
                    res = RmGetList(handle, pnProcInfoNeeded, pnProcInfo, processInfo, lpdwRebootReasons)
                    If res = 0 Then
                        processes = New List(Of Process)(CInt(pnProcInfo))
    
                        ' Enumerate all of the results and add them to the 
                        ' list to be returned
                        For i As Integer = 0 To CInt(pnProcInfo - 1)
                            Try
                                processes.Add(Process.GetProcessById(processInfo(i).Process.dwProcessId))
                                ' catch the error -- in case the process is no longer running
                            Catch generatedExceptionName As ArgumentException
                            End Try
                        Next
                    Else
                        Throw New Exception("Could not list processes locking resource.")
                    End If
                ElseIf res <> 0 Then
                    Throw New Exception("Could not list processes locking resource. Failed to get size of result.")
                End If
            Finally
                RmEndSession(handle)
            End Try
    
            Return processes
        End Function
    
    End Class
    


    Please BEWARE that I have NO EXPERIENCE and NO EXPERTISE and probably onset of DEMENTIA which may affect my answers! Also, I've been told by an expert, that when you post an image it clutters up the thread and mysteriously, over time, the link to the image will somehow become "unstable" or something to that effect. :) I can only surmise that is due to Global Warming of the threads.

    • Proposed as answer by IronRazerz Wednesday, January 29, 2014 8:56 PM
    • Marked as answer by Leo (Apple) Yang Wednesday, February 5, 2014 9:04 AM
    Wednesday, January 29, 2014 4:14 PM
  • I inherited a large legacy program that uses code similar to this for accessing text files

    Dim Fnum As Long = = FreeFile()
    FileOpen(Fnum, myFilename, OpenMode.Output)
    
    ... do stuff ...
    
    FileClose(Fnum)
    Is there a way to detect if the file specified by myFilename is already open?

    I'd suggest that you update your code to dotNet standards and with that said, you might want to have a look here.

    Once you get that worked out, shroud the method in a Try/Catch and handle the exception (if one is thrown). Please don't leave the catch empty! You're just making it hard on yourself when an exception is thrown because leaving it empty, you have "swallowed the exeption" as is sometimes said.

    For what it's worth. :)


    Please call me Frank :)

    Wednesday, January 29, 2014 5:22 PM
  • Hello,

    As per Mr. MonkeyBox nothing is guaranteed especially with utilities such as NotePad.

    Here is sample code where if you have E1.xlsx open this will indicate we cannot open it else we open it then immediately close it for this demo. If we leave the stream open other processes will not be able to use the file.

    Imports System.IO
    Imports System.Runtime.InteropServices
    Module FileOpenTesting
        Public Sub Testbelow()
            IsFileOpen(New FileInfo(IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "E1.xlsx")))
            IsFileOpen(New FileInfo(IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "T1.txt")))
        End Sub
        Public Sub IsFileOpen(ByVal file As FileInfo)
            Dim stream As FileStream = Nothing
            Try
                stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)
                Console.WriteLine("Open")
                '
                ' Do work or simply close the stream
                '
                stream.Close()
            Catch ex As Exception
    
                If TypeOf ex Is IOException AndAlso IsFileLocked(ex) Then
                    ' do something here, either close the file if you have a handle or as a last resort terminate the process - 
                    ' which could cause corruption and lose data
    
                    Console.WriteLine("Not open")
                End If
            End Try
        End Sub
        Private Function IsFileLocked(exception As Exception) As Boolean
            Dim errorCode As Integer = Marshal.GetHRForException(exception) And ((1 << 16) - 1)
            Return errorCode = 32 OrElse errorCode = 33
        End Function
    End Module
    


    Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem.

    • Proposed as answer by IronRazerz Wednesday, January 29, 2014 8:59 PM
    • Marked as answer by Leo (Apple) Yang Wednesday, February 5, 2014 9:04 AM
    Wednesday, January 29, 2014 6:51 PM