Jump to content

Request: ReadDirectoryChangesExW example


Go to solution Solved by Nine,

Recommended Posts

Out of this help request, I saw that there is $FILE_NOTIFY_CHANGE_SIZE. Now, if the OS knows that it changed, where is the size ?
Looking around, starting with Win10/Server2016, there is ReadDirectoryChangesExW with:

typedef struct _FILE_NOTIFY_EXTENDED_INFORMATION {
  DWORD         NextEntryOffset;
  DWORD         Action;
  LARGE_INTEGER CreationTime;
  LARGE_INTEGER LastModificationTime;
  LARGE_INTEGER LastChangeTime;
  LARGE_INTEGER LastAccessTime;
  LARGE_INTEGER AllocatedLength;
  LARGE_INTEGER FileSize;
  DWORD         FileAttributes;
  union {
    DWORD ReparsePointTag;
    DWORD EaSize;
  } DUMMYUNIONNAME;
  LARGE_INTEGER FileId;
  LARGE_INTEGER ParentFileId;
  DWORD         FileNameLength;
  WCHAR         FileName[1];
} FILE_NOTIFY_EXTENDED_INFORMATION, *PFILE_NOTIFY_EXTENDED_INFORMATION;

/* https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-file_notify_extended_information */

Saw a couple of examples in C ( this one and this one ) and my request is to have an AutoIt implementation. TIA

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Link to comment
Share on other sites

Based on the example I made you would need to modify/add the following :

Global Const $tag_FILE_NOTIFY_EXTENDED_INFORMATION = "dword NextEntryOffset;dword Action;int64 CreationTime;" & _
  "int64 LastModificationTime;int64 LastChangeTime;int64 LastAccessTime;int64 AllocatedLength;" & _
  "int64 FileSize;dword FileAttributes;dword EaSize;int64 FileId;int64 ParentFileId;dword FileNameLength;"

....

While True
    $tNotify = DllStructCreate($tag_FILE_NOTIFY_EXTENDED_INFORMATION, $pBuffer)
    $nLength = $tNotify.FileNameLength
    $tNotify = DllStructCreate($tag_FILE_NOTIFY_EXTENDED_INFORMATION & "wchar FileName[" & $nLength / 2 & "]", $pBuffer)

    FileNotifyUser($tNotify.Action, $tNotify.FileName, $tNotify.FileSize)

....

Local $aRet = DllCall('kernel32.dll', 'bool', 'ReadDirectoryChangesExW', 'handle', $hDirectory, 'struct*', $pBuffer, _
      'dword', $iLength - Mod($iLength, 4), 'bool', $bSubtree, 'dword', $iFilter, 'dword*', 0, 'struct*', $tOverlapped, _
      'ptr', $pCompletion, 'dword', 2)

FileNotifyUser should be changed accordingly.

Link to comment
Share on other sites

Posted (edited)
4 hours ago, Nine said:

Based on the example I made you would need to modify/add the following :

..I did but it did not work for me. No clue of what am doing wrong. Please post a running script when you get a chance. Thanks

Edit: $FILE_ACTION_RENAMED_NEW_NAME don't trigger

Edit2: also, instead of FileNotifyUser($tNotify.Action, $tNotify.FileName, $tNotify.FileSize), could FileNotifyUser($tNotify) work ?

Edit3: might as well share what I did:

Spoiler
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseX64=n ; trying x32 and x64
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <Constants.au3>
#include <WinAPI.au3>

Opt("MustDeclareVars", True)

HotKeySet("{ESC}", Terminate)

Global Const $WATCH_PATH = "C:\Apps\Temp"
Global Const $BUFFER_LENGTH = 0xFFFF ; 64k
;~ Global Const $NOTIFY_FLAGS = BitOR($FILE_NOTIFY_CHANGE_FILE_NAME, $FILE_NOTIFY_CHANGE_DIR_NAME)
Global Const $NOTIFY_FLAGS = BitOR($FILE_NOTIFY_CHANGE_FILE_NAME, $FILE_NOTIFY_CHANGE_DIR_NAME, $FILE_NOTIFY_CHANGE_ATTRIBUTES, _
        $FILE_NOTIFY_CHANGE_SIZE, $FILE_NOTIFY_CHANGE_LAST_WRITE, $FILE_NOTIFY_CHANGE_LAST_ACCESS, $FILE_NOTIFY_CHANGE_CREATION, $FILE_NOTIFY_CHANGE_SECURITY)


Global $tOverlapped, $hDirectory, $hCompletion, $pCompletion, $pDataBuffer, $hEvent

Global Const $tag_FILE_NOTIFY_EXTENDED_INFORMATION = "dword NextEntryOffset;dword Action;int64 CreationTime;" & _
        "int64 LastModificationTime;int64 LastChangeTime;int64 LastAccessTime;int64 AllocatedLength;" & _
        "int64 FileSize;dword FileAttributes;dword EaSize;int64 FileId;int64 ParentFileId;dword FileNameLength;"

DirectoryChange()

Func DirectoryChange()
    $tOverlapped = DllStructCreate($tagOVERLAPPED)
    $hEvent = _WinAPI_CreateEvent(0, False, False)
    $tOverlapped.hEvent = $hEvent

    $hDirectory = _WinAPI_CreateFileEx($WATCH_PATH, $OPEN_EXISTING, $FILE_LIST_DIRECTORY, _
            BitOR($FILE_SHARE_READ, $FILE_SHARE_WRITE, $FILE_SHARE_DELETE), _
            BitOR($FILE_FLAG_BACKUP_SEMANTICS, $FILE_FLAG_OVERLAPPED))

    $hCompletion = DllCallbackRegister(CompletionRoutineExW, "none", "dword;dword;ptr")
    $pCompletion = DllCallbackGetPtr($hCompletion)
    $pDataBuffer = _WinAPI_CreateBuffer($BUFFER_LENGTH)

    _WinAPI_ReadDirectoryChangesExW($hDirectory, $NOTIFY_FLAGS, $pDataBuffer, $BUFFER_LENGTH, True, $tOverlapped, $pCompletion)
    OnAutoItExitRegister(CleanUp)

    While True
        _WinAPI_WaitForSingleObjectEx($hEvent, 0, 1)
        Sleep(100)
    WEnd
EndFunc   ;==>DirectoryChange

;~ Make decisions on file changes in here
Func FileNotifyUserExW($nFileAction, $sFileName, $iFileSize)
    Switch $nFileAction
        Case $FILE_ACTION_ADDED
            ConsoleWrite($iFileSize & " - File added: " & $sFileName & @CRLF)
        Case $FILE_ACTION_REMOVED
            ConsoleWrite($iFileSize & " - File removed: " & $sFileName & @CRLF)
        Case 3
            ConsoleWrite($iFileSize & " - File changed something: " & $sFileName & @CRLF)
        Case $FILE_ACTION_RENAMED_OLD_NAME
            ConsoleWrite($iFileSize & " - File renamed from: " & $sFileName & @CRLF)
        Case $FILE_ACTION_RENAMED_NEW_NAME
            ConsoleWrite($iFileSize & " - File renamed to: " & $sFileName & @CRLF)
    EndSwitch
EndFunc   ;==>FileNotifyUserExW

Func CleanUp()
    _WinAPI_CancelIoEx($hDirectory, $tOverlapped)
    _WinAPI_CloseHandle($hDirectory)
    _WinAPI_FreeMemory($pDataBuffer)
    _WinAPI_CloseHandle($hEvent)
    DllCallbackFree($hCompletion)
EndFunc   ;==>CleanUp

Func CompletionRoutineExW($nError, $nTransfered, $pOverlapped)
    Local $pBuffer = $pDataBuffer, $tNotify, $nLength

    While True
        $tNotify = DllStructCreate($tag_FILE_NOTIFY_EXTENDED_INFORMATION, $pBuffer)
        $nLength = $tNotify.FileNameLength
        $tNotify = DllStructCreate($tag_FILE_NOTIFY_EXTENDED_INFORMATION & "wchar FileName[" & $nLength / 2 & "]", $pBuffer)

        FileNotifyUserExW($tNotify.Action, $tNotify.FileName, $tNotify.FileSize)

        If Not $tNotify.NextOffset Then ExitLoop
        $pBuffer += $tNotify.NextOffset
    WEnd

    _WinAPI_ReadDirectoryChangesExW($hDirectory, $NOTIFY_FLAGS, $pDataBuffer, $BUFFER_LENGTH, 0, $tOverlapped, $pCompletion)
EndFunc   ;==>CompletionRoutineExW

Func _WinAPI_ReadDirectoryChangesExW($hDirectory, $iFilter, $pBuffer, $iLength, $bSubtree = 0, $tOverlapped = 0, $pCompletion = 0)
    Local $aRet = DllCall('kernel32.dll', 'bool', 'ReadDirectoryChangesExW', 'handle', $hDirectory, 'struct*', $pBuffer, _
            'dword', $iLength - Mod($iLength, 4), 'bool', $bSubtree, 'dword', $iFilter, 'dword*', 0, 'struct*', $tOverlapped, _
            'ptr', $pCompletion, 'dword', 2)
    If @error Or Not $aRet[0] Then Return SetError(@error + 10, @extended, 0)
    Return SetExtended(_WinAPI_GetLastError(), $aRet[0])
EndFunc   ;==>_WinAPI_ReadDirectoryChangesExW

Func _WinAPI_CancelIoEx($hFile, $tOverlapped)
    Local $aRet = DllCall('kernel32.dll', 'bool', 'CancelIoEx', 'handle', $hFile, 'struct*', $tOverlapped)
    If @error Then Return SetError(@error, @extended, 0)
    Return $aRet[0]
EndFunc   ;==>_WinAPI_CancelIoEx

Func _WinAPI_WaitForSingleObjectEx($hEvent, $nMilliseconds, $bAlertable = 0)
    Local $aRet = DllCall('kernel32.dll', 'dword', 'WaitForSingleObjectEx', 'handle', $hEvent, 'dword', $nMilliseconds, 'bool', $bAlertable)
    If @error Then Return SetError(@error, @extended, 0)
    Return $aRet[0]
EndFunc   ;==>_WinAPI_WaitForSingleObjectEx

Func Terminate()
    Exit
EndFunc   ;==>Terminate

 

Edited by argumentum
more

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Link to comment
Share on other sites

  • Solution

Definitely buggy on the rename, got same issue as you.  I added a Case Else on action and there is no other, so the bug is not about a change in action.

Didn't test much the new EX, but size works well.  IDK about the other triggers.

In any case, this API is not well written.  We do not know what has been modified.  Won't be using it for sure...

Passing $tNofify as a parameter is a good idea.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...