windows – How to simulating a specific style of file locks?

I am trying to simulate an annoying file lock that occurs sometimes so I can test the affect on a tool I have created. So far I have been unable to simulate the file lock. The symptoms I experience with the file lock is the following:

  1. I can move and rename the file without issues.
  2. If I attempt to delete the file, it appears to delete, but when I refresh the folder, it re-appears. After the attempted deletion, I cannot create a new file with the same name in that folder until after I reboot.

Note that the file appears to be locked by a thread in the system process.

All attempts so far to lock a file in a similar way prevent me from being able to move or rename the locked file. I have used different Windows APIs such as LockFileEx and .NET libraries, but so far none have reproduced the behaviour. I appreciate any suggestions that could help determine a way to simulate this, even if not via the system process.

This is one example of the code I have unsuccessfully tried to attempt to simulate this locking behaviour.

    Private FileHandle As IntPtr
    Private LockAcquired As LockAcquiredEnum
    Private ResetEvent As ManualResetEvent = Nothing
    Private Overlapped As New System.Threading.NativeOverlapped

    Private Enum LockAcquiredEnum
        Failed
        Pending
        Succeeded
    End Enum

#Region "Declarations"
    Private Const INVALID_HANDLE_VALUE As Short = -1
    Private Const ERROR_SUCCESS As Short = 0
    Public Const ERROR_IO_PENDING As Integer = &H3E5

    <DllImport("kernel32.dll", CharSet:=CharSet.Unicode, SetLastError:=True, EntryPoint:="CreateFileW")>
    Private Shared Function CreateFile(<MarshalAs(UnmanagedType.LPWStr)> ByVal filename As String, <MarshalAs(UnmanagedType.U4)> ByVal access As FileAccess, <MarshalAs(UnmanagedType.U4)> ByVal share As FileShare, ByVal securityAttributes As IntPtr, <MarshalAs(UnmanagedType.U4)> ByVal creationDisposition As FileMode, <MarshalAs(UnmanagedType.U4)> ByVal OptionsAndAttributes As UInteger, ByVal templateFile As IntPtr) As IntPtr
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Private Shared Function LockFileEx(ByVal hFile As IntPtr, ByVal dwFlags As UInteger, ByVal dwReserved As UInteger, ByVal nNumberOfBytesToLockLow As UInteger, ByVal nNumberOfBytesToLockHigh As UInteger, <[In]> ByRef lpOverlapped As System.Threading.NativeOverlapped) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("kernel32.dll")>
    Shared Function UnlockFileEx(ByVal hFile As IntPtr, ByVal dwReserved As UInteger, ByVal nNumberOfBytesToUnlockLow As UInteger, ByVal nNumberOfBytesToUnlockHigh As UInteger, <[In]> ByRef lpOverlapped As System.Threading.NativeOverlapped) As Boolean
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Private Shared Function CloseHandle(ByVal hHandle As IntPtr) As Boolean
    End Function

    Private Enum FileLockEnum As UInteger
        LOCKFILE_FAIL_IMMEDIATELY = 1
        LOCKFILE_EXCLUSIVE_LOCK = 2
    End Enum
#End Region

    Private Sub cmdLockTestFile_Click(sender As Object, e As EventArgs) Handles cmdLockTestFile.Click
        LockFile()
    End Sub

    Private Sub cmdUnlockTestFile_Click(sender As Object, e As EventArgs) Handles cmdUnlockTestFile.Click
        UnlockFile()
    End Sub

    Private Sub LockFile()
        ' Get the options
        Dim dwFileFlags As FileOptions
        Dim dwLockFlags As FileLockEnum

        dwFileFlags = FileOptions.Asynchronous

        ' Open the file
        lblStatus.Text = $"Opening the file 'TestFile.exe' as {If((dwFileFlags And FileOptions.Asynchronous) <> 0, "asynchronous", "synchronous")}" & vbLf

        FileHandle = CreateFile("C:Program Files (x86)AppTestFile.exe", FileAccess.Read, FileShare.ReadWrite, Nothing, FileMode.Open, FileAttributes.Normal Or dwFileFlags, Nothing)

        If FileHandle = INVALID_HANDLE_VALUE Then
            lblStatus.Text = $"Open failed, error = {Marshal.GetLastWin32Error()}" & vbLf
            Return
        End If

        'Optionally set this
        'dwLockFlags = FileLockEnum.LOCKFILE_EXCLUSIVE_LOCK

        ' Set the starting position in the OVERLAPPED structure
        Dim o As New System.Threading.NativeOverlapped
        o.OffsetLow = 0 ' we lock on byte zero

        ' Say what kind of lock we want
        If (dwLockFlags And FileLockEnum.LOCKFILE_EXCLUSIVE_LOCK) <> 0 Then
            lblStatus.Text = "Requesting exclusive lock" & vbLf
        Else
            lblStatus.Text = "Requesting shared lock" & vbLf
        End If

        ' Say whether we're going to wait to acquire
        If (dwLockFlags And FileLockEnum.LOCKFILE_FAIL_IMMEDIATELY) <> 0 Then
            lblStatus.Text = "Requesting immediate failure" & vbLf

        ElseIf dwFileFlags And FileOptions.Asynchronous Then
            lblStatus.Text = "Requesting notification on lock acquisition" & vbLf
            ' The event that will be signaled when the lock is acquired
            ' error checking deleted for expository purposes
            ResetEvent = New ManualResetEvent(False)
            o.EventHandle = ResetEvent.SafeWaitHandle.DangerousGetHandle
        Else
            lblStatus.Text = "Call will block until lock is acquired" & vbLf
        End If

        ' Okay, here we go.
        lblStatus.Text = "Attempting lock" & vbLf

        Dim IsLockAcquired As Boolean = LockFileEx(FileHandle, dwLockFlags, 0, 1, 0, o)

        ' If the lock failed, remember why.
        Dim dwError As UInteger = If(IsLockAcquired, ERROR_SUCCESS, Marshal.GetLastWin32Error())

        lblStatus.Text = $"Wait {If(IsLockAcquired, "succeeded", "failed")}, error code {dwError}" & vbLf

        If IsLockAcquired Then
            lblStatus.Text = "Lock acquired immediately" & vbLf
            LockAcquired = LockAcquiredEnum.Succeeded

        ElseIf dwError = ERROR_IO_PENDING Then
            LockAcquired = LockAcquiredEnum.Pending
            lblStatus.Text = "Waiting for lock" & vbLf

        Else
            LockAcquired = LockAcquiredEnum.Failed
        End If
    End Sub

    Private Sub UnlockFile()
        If LockAcquired = LockAcquiredEnum.Pending Then
            ResetEvent.WaitOne()
            LockAcquired = LockAcquiredEnum.Succeeded
        End If

        ' If we got the lock, then hold the lock until the user releases it.
        If LockAcquired = LockAcquiredEnum.Succeeded Then
            lblStatus.Text = "Unlocking" & vbLf
            UnlockFileEx(FileHandle, 0, 1, 0, Overlapped)
        End If

        ' Clean up
        If ResetEvent IsNot Nothing Then
            ResetEvent.Close()
            ResetEvent.Dispose()
        End If

        CloseHandle(FileHandle)
        lblStatus.Text = "Unlocked TestFile.exe" & vbLf
    End Sub

I am unable to reproduce the behavior I have experienced using this code. It seems to be caused by something running under Windows’ system process (pid 4)


Update: Based on Feedback from @HansPassant, I have tried to modify my code to simulate this behaviour, but I still cannot reproduce this. I first tried removing the code that makes use of the LockFileEx API, and only opening the file with the CreateFile API with the addition of the FileShare.Delete attribute to one parameter.

FileHandle = CreateFile("C:Program Files (x86)AppTestFile.exe", FileAccess.Read, FileShare.Read Or FileShare.Delete, Nothing, FileMode.Open, FileAttributes.Normal Or dwFileFlags, Nothing)

I then tried to completely rewrite it using a FileStream object like the other article mentions, but that did not work either.

Public Class Form1
    Private TestFileStream As FileStream

    Private Sub cmdLockTestFile_Click(sender As Object, e As EventArgs) Handles cmdLockTestFile.Click
        LockFile()
    End Sub

    Private Sub cmdUnlockTestFile_Click(sender As Object, e As EventArgs) Handles cmdUnlockTestFile.Click
        UnlockFile()
    End Sub

    Private Sub LockFile()
        ' Open the file
        lblStatus.Text = $"Opening the file 'TestFile.exe' with FileShare.Delete permissions." & vbLf
        Try
            TestFileStream = New FileStream("C:Program Files (x86)AppTestFile.exe", FileMode.Open, FileAccess.Read, FileShare.Read Or FileShare.Delete)
            lblStatus.Text &= "File is opened with FileShare.Delete permissions." & vbLf

            FileReadTimer.Enabled = True

        Catch ex As Exception
            lblStatus.Text &= $"Open failed, error = {ex.Message}" & vbLf
            Return
        End Try
    End Sub

    Private Sub UnlockFile()
        If TestFileStream IsNot Nothing Then 
            TestFileStream.Close()
        End If

        FileReadTimer.Enabled = False

        lblStatus.Text &= "Closed TestFile.exe"
    End Sub

    Private Sub FileReadTimer_Tick(sender As Object, e As EventArgs) Handles FileReadTimer.Tick
        Dim Value As Integer

        Value = TestFileStream.ReadByte()

        If Value = -1 Then
            TestFileStream.Position = 0
            Value = TestFileStream.ReadByte()
        End If
    End Sub
End Class

I even tried this, but it didn’t help.

TestFileStream = New FileStream("C:Program Files (x86)AppTestFile.exe", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite Or FileShare.Delete)

The only thing I did not try was modifying the file. It does not make sense to me that whatever process is locking the file would actually be modifying it, and I do not want to damage the file in any way.

So far, no matter what I have tried, while the file is open in code, I am still able to delete the file from the folder. A refresh of that folder does not make it re-appear, and I am able to create a new file with the same name.

Leave a Comment