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:
- I can move and rename the file without issues.
- 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)\App\TestFile.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 behaviour 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)\App\TestFile.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)\App\TestFile.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)\App\TestFile.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.
Update #2:
I tried following the example here https://stackoverflow.com/a/19875330/4816919 but I still could not reproduce it. (It is the same stackoverflow post mentioned by @HansPassant) I changed the lines that create the FileStream object to where the Timer is enabled to this:
TestFileStream = New FileStream("C:\Program Files (x86)\App\TestFile.exe", FileMode.Open, FileAccess.Read, FileShare.Read Or FileShare.Delete, 4096, FileOptions.SequentialScan)
lblStatus.Text &= "File is opened with FileShare.Delete permissions." & vbLf
TestFileStream.Read(New Byte(99){}, 1, 1)
GC.KeepAlive(TestFileStream)
FileReadTimer.Enabled = True
I am at a loss as to how to do this so far.