Jump to content

Monitoring Folders with ReadDirectoryChangesW


ame1011
 Share

Recommended Posts

Hi there,

I'm using the following code by zophnog to monitor a folder for file changes:

#cs ----------------------------------------------------------------------------

 AutoIt Version: 3.2.10.0
 Author:         zorphnog (M. Mims)

 Script Function:
    Monitors the user defined directories for file activity.

#ce ----------------------------------------------------------------------------


#include <Constants.au3>
#include <WinAPI.au3>
#include <Date.au3>
#include <GUIConstants.au3>
#include <GuiListBox.au3>
#include <GuiListView.au3>

Global Const _
        $FILE_FLAG_BACKUP_SEMANTICS = 0x02000000, _
        $FILE_FLAG_OVERLAPPED       = 0x40000000
Global Const _
        $FILE_NOTIFY_CHANGE_ALL         = 0x17F, _
        $FILE_NOTIFY_CHANGE_FILE_NAME   = 0x001, _
        $FILE_NOTIFY_CHANGE_DIR_NAME    = 0x002, _
        $FILE_NOTIFY_CHANGE_ATTRIBUTES  = 0x004, _
        $FILE_NOTIFY_CHANGE_SIZE        = 0x008, _
        $FILE_NOTIFY_CHANGE_LAST_WRITE  = 0x010, _
        $FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x020, _
        $FILE_NOTIFY_CHANGE_CREATION    = 0x040, _
        $FILE_NOTIFY_CHANGE_SECURITY    = 0x100
Global Const _
        $FILE_ACTION_ADDED            = 0x1, _
        $FILE_ACTION_REMOVED          = 0x2, _
        $FILE_ACTION_MODIFIED         = 0x3, _
        $FILE_ACTION_RENAMED_OLD_NAME = 0x4, _
        $FILE_ACTION_RENAMED_NEW_NAME = 0x5
Global Const _
        $MWMO_ALERTABLE      = 0x0002, _
        $MWMO_INPUTAVAILABLE = 0x0004, _
        $MWMO_WAITALL        = 0x0001
Global Const $FILE_LIST_DIRECTORY = 0x0001
Global Const $QS_ALLINPUT = 0x04FF
Global Const $INFINITE = 0xFFFF
Global Const $tagFNIIncomplete = "dword NextEntryOffset;dword Action;dword FileNameLength"
Global $bMonitorDone, $bSelected, $bMonitor
AutoItSetOption("GUIOnEventMode", 1)

$gFileMon = GUICreate("Directory Monitor", 731, 385, 194, 126)
GUISetOnEvent($GUI_EVENT_CLOSE, "_OnEvent_Close")
GUICtrlCreateGroup("Monitored Directories", 8, 0, 713, 105)
$btAdd = GUICtrlCreateButton("Add", 16, 24, 75, 25, 0)
GUICtrlSetOnEvent(-1, "_OnEvent_Add")
$btRemove = GUICtrlCreateButton("Remove", 16, 56, 75, 25, 0)
GUICtrlSetOnEvent(-1, "_OnEvent_Remove")
GUICtrlSetState(-1, $GUI_DISABLE)
$lbDirectories = GUICtrlCreateList("", 104, 16, 506, 71)
$btMonitor = GUICtrlCreateButton("Start Monitor", 632, 24, 75, 25, 0)
GUICtrlSetOnEvent(-1, "_OnEvent_Monitor")
GUICtrlSetState(-1, $GUI_DISABLE)
$btClear = GUICtrlCreateButton("Clear", 632, 56, 75, 25, 0)
GUICtrlSetOnEvent(-1, "_OnEvent_Clear")
GUICtrlCreateGroup("", -99, -99, 1, 1)
$lvNotifications = GUICtrlCreateListView("Action|Time|File", 8, 112, 714, 262)
GUICtrlSendMsg(-1, 0x101E, 0, Int(.1*710))
GUICtrlSendMsg(-1, 0x101E, 1, Int(.2*710))
GUICtrlSendMsg(-1, 0x101E, 2, Int(.7*710)-20)
GUISetState(@SW_SHOW)

_Main()

Func _DisplayFileMessages($hBuffer, $sDir)
    Local $hFileNameInfo, $pBuffer, $hTime
    Local $nFileNameInfoOffset = 12, $nOffset = 0, $nNext = 1
    $pBuffer = DllStructGetPtr($hBuffer)
    While $nNext <> 0
        $hFileNameInfo = DllStructCreate($tagFNIIncomplete, $pBuffer + $nOffset)
        $hFileName = DllStructCreate("wchar FileName[" & DllStructGetData($hFileNameInfo, "FileNameLength")/2 & "]", $pBuffer + $nOffset + $nFileNameInfoOffset)
        $hTime = _Date_Time_GetSystemTime()
        Switch DllStructGetData($hFileNameInfo, "Action")
            Case $FILE_ACTION_ADDED
                _GUICtrlListView_InsertItem($lvNotifications, "Created", 0)
            Case $FILE_ACTION_REMOVED
                _GUICtrlListView_InsertItem($lvNotifications, "Deleted", 0)
            Case $FILE_ACTION_MODIFIED
                _GUICtrlListView_InsertItem($lvNotifications, "Modified", 0)
            Case $FILE_ACTION_RENAMED_OLD_NAME
                _GUICtrlListView_InsertItem($lvNotifications, "Rename-", 0)
            Case $FILE_ACTION_RENAMED_NEW_NAME
                _GUICtrlListView_InsertItem($lvNotifications, "Rename+", 0)
            Case Else
                _GUICtrlListView_InsertItem($lvNotifications, "Unknown", 0)
        EndSwitch
        _GUICtrlListView_AddSubItem($lvNotifications, 0, _Date_Time_SystemTimeToDateTimeStr($hTime), 1)
        _GUICtrlListView_AddSubItem($lvNotifications, 0, $sDir & DllStructGetData($hFileName, "FileName"), 2)
        $nNext = DllStructGetData($hFileNameInfo, "NextEntryOffset")
        $nOffset += $nNext
    WEnd
EndFunc

Func _GetBufferHandle ()
    Return DllStructCreate("ubyte[2048]")
EndFunc

Func _GetDirectoryChanges($aDirHandles, $hBuffer, $aOverlapped, $hEvents, $aDirs, $bAsync = Default, $nTimeout = Default)
    Local $aMsg, $i, $nBytes = 0
    If $nTimeout = -1 Or IsKeyword($nTimeout) Then $nTimeout = 250
    If Not $bAsync Then $nTimeout = $INFINITE
    $aMsg = DllCall("User32.dll", "dword", "MsgWaitForMultipleObjectsEx", _
        "dword", UBound($aOverlapped), _
        "ptr", DllStructGetPtr($hEvents), _
        "dword", $nTimeout, _
        "dword", 0, _
        "dword", 0x6)
    $i = $aMsg[0]
    Switch $i
        Case 0 To UBound($aDirHandles)-1
            If Not _WinAPI_GetOverlappedResult($aDirHandles[$i], DllStructGetPtr($aOverlapped[$i]), $nBytes, True) Then
                ConsoleWrite("!>  GetOverlappedResult Error(" & @error & "): " & _WinAPI_GetLastErrorMessage() & @LF)
                Return 0
            EndIf
            DllCall("Kernel32.dll", "Uint", "ResetEvent", "uint", DllStructGetData($aOverlapped[$i], "hEvent"))
            _DisplayFileMessages($hBuffer, $aDirs[$i])
            _SetReadDirectory($aDirHandles[$i], $hBuffer, $aOverlapped[$i],False,True)
            Return $nBytes
    EndSwitch
    Return 0
EndFunc

Func _GetDirHandle($sDir)
    Local $aResult
    $aResult = DllCall("Kernel32.dll", "hwnd", "CreateFile", _
        "str", $sDir, _
        "int", $FILE_LIST_DIRECTORY, _
        "int", BitOR($FILE_SHARE_DELETE,$FILE_SHARE_READ,$FILE_SHARE_WRITE), _
        "ptr", 0, _
        "int", $OPEN_EXISTING, _
        "int", BitOR($FILE_FLAG_BACKUP_SEMANTICS,$FILE_FLAG_OVERLAPPED), _
        "int", 0)
    If $aResult[0] = 0 Then
        ConsoleWrite("!>  CreateFile Error (" & @error & "): " & _WinAPI_GetLastErrorMessage() & @LF)
        Exit
    EndIf
    Return $aResult[0]
EndFunc

Func _GetEventHandles ($aOverlapped)
    Local $i, $hEvents
    $hEvents = DllStructCreate("hwnd hEvent[" & UBound($aOverlapped) & "]")
    For $i=1 To UBound($aOverlapped)
        DllStructSetData($hEvents, "hEvent", DllStructGetData($aOverlapped[$i-1], "hEvent"), $i)
    Next
    Return $hEvents
EndFunc

Func _GetOverlappedHandle ()
    Local $hOverlapped = DllStructCreate($tagOVERLAPPED)
    For $i=1 To 5
        DllStructSetData($hOverlapped, $i, 0)
    Next
    Return $hOverlapped
EndFunc

Func _Main ()
    $bSelected = False
    $bMonitorDone = True
    $bMonitor = False
    While 1
        If Not $bMonitorDone Then _MonitorDirs()
        If $bMonitor And _GUICtrlListBox_GetCount($lbDirectories) = 0 Then
            $bMonitor = Not $bMonitor
            GUICtrlSetState($btMonitor, $GUI_DISABLE)
        ElseIf Not $bMonitor And _GUICtrlListBox_GetCount($lbDirectories) > 0 Then
            $bMonitor = Not $bMonitor
            GUICtrlSetState($btMonitor, $GUI_ENABLE)
        EndIf
        If $bSelected And _GUICtrlListBox_GetCurSel($lbDirectories) = -1 Then
            $bSelected = Not $bSelected
            GUICtrlSetState($btRemove, $GUI_DISABLE)
        ElseIf Not $bSelected And _GUICtrlListBox_GetCurSel($lbDirectories) <> -1 Then
            $bSelected = Not $bSelected
            GUICtrlSetState($btRemove, $GUI_ENABLE)
        EndIf
    WEnd    
EndFunc

Func _MonitorDirs ()
    Local $i, $nMax, $hBuffer, $hEvents
    $nMax = _GUICtrlListBox_GetCount($lbDirectories)
    Local $aDirHandles[$nMax], $aOverlapped[$nMax], $aDirs[$nMax]
    $hBuffer = _GetBufferHandle()
    For $i = 0 To $nMax-1
        $aDirs[$i] = _GUICtrlListBox_GetText($lbDirectories, $i)
        $aDirHandles[$i] = _GetDirHandle($aDirs[$i])
        $aOverlapped[$i] = _GetOverlappedHandle()
        _SetReadDirectory($aDirHandles[$i], $hBuffer, $aOverlapped[$i], True, True)
    Next
    $hEvents = _GetEventHandles($aOverlapped)
    While Not $bMonitorDone
        _GetDirectoryChanges($aDirHandles, $hBuffer, $aOverlapped, $hEvents, $aDirs)
    WEnd
EndFunc

Func _OnEvent_Add ()
    Local $sDir, $nMax, $i
    $sDir = FileSelectFolder("Select directory to monitor", "")
    If $sDir <> "" Then
        If StringRight($sDir, 1) <> "\" Then $sDir &= "\"
        $nMax = _GUICtrlListBox_GetCount($lbDirectories)-1
        For $i = 0 To $nMax
            If _GUICtrlListBox_GetText($lbDirectories, $i) = $sDir Then Return
        Next
        _GUICtrlListBox_AddString($lbDirectories, $sDir)
    EndIf
EndFunc

Func _OnEvent_Clear ()
    _GUICtrlListView_DeleteAllItems(GUICtrlGetHandle($lvNotifications))
EndFunc

Func _OnEvent_Close ()
    Exit
EndFunc

Func _OnEvent_Monitor ()
    If $bMonitorDone Then
        $bMonitorDone = False
        GUICtrlSetData($btMonitor, "Stop Monitor")
        GUICtrlSetState($btAdd, $GUI_DISABLE)
        GUICtrlSetState($btRemove, $GUI_DISABLE)
        GUICtrlSetState($lbDirectories, $GUI_DISABLE)
        $bSelected = False
    Else
        $bMonitorDone = True
        GUICtrlSetState($lbDirectories, $GUI_ENABLE)
        GUICtrlSetState($btAdd, $GUI_ENABLE)
        GUICtrlSetData($btMonitor, "Start Monitor")
    EndIf
EndFunc

Func _OnEvent_Remove ()
    _GUICtrlListBox_DeleteString($lbDirectories, _GUICtrlListBox_GetCurSel($lbDirectories))
EndFunc

Func _SetReadDirectory($hDir, $hBuffer, $hOverlapped, $bInitial = False, $bSubtree = False)
    Local $hEvent, $pBuffer, $nBufferLength, $pOverlapped
    $pBuffer = DllStructGetPtr($hBuffer)
    $nBufferLength = DllStructGetSize($hBuffer)
    $pOverlapped = DllStructGetPtr($hOverlapped)
    If $bInitial Then
        $hEvent = DllCall("Kernel32.dll", "hwnd", "CreateEvent", _
            "uint", 0, _
            "int", True, _
            "int", False, _
            "uint", 0)
        If $hEvent[0] = 0 Then
            ConsoleWrite("!>  CreateEvent Failed (" & _WinAPI_GetLastError() & "): " & _WinAPI_GetLastErrorMessage() & @LF)
            Exit
        EndIf
        DllStructSetData($hOverlapped, "hEvent", $hEvent[0])
    EndIf
    $aResult = DllCall("Kernel32.dll", "int", "ReadDirectoryChangesW", _
        "hwnd", $hDir, _
        "ptr", $pBuffer, _
        "dword", $nBufferLength, _
        "int", $bSubtree, _
        "dword", BitOR($FILE_NOTIFY_CHANGE_FILE_NAME, _
            $FILE_NOTIFY_CHANGE_SIZE,$FILE_NOTIFY_CHANGE_DIR_NAME), _
        "uint", 0, _
        "uint", $pOverlapped, _
        "uint", 0)
    If $aResult[0] = 0 Then
        ConsoleWrite("!>  ReadDirectoryChangesW Error(" & @error & "): " & _WinAPI_GetLastErrorMessage() & @LF)
        Exit
    EndIf
    Return $aResult[0]
EndFunc

So far everything is going great, I'm recording the operations and the files in a sqlite database which my main program reads on startup to make the required changes that it needs to.

My question is: how do I tell which items are folders and which are files? Checking for '.ext' using stringRight or something similar isn't really what I'm looking for as it'll fail if there are dots in the directory names. Also, actually doing a Filesearch for the folder/file isn't feasible because if the operation is a file removal, the file/folder would no longer be there.

I tried changing the parameter in the ReadDirectoryChangesW DllCall from:

"dword", BitOR($FILE_NOTIFY_CHANGE_FILE_NAME, $FILE_NOTIFY_CHANGE_SIZE,$FILE_NOTIFY_CHANGE_DIR_NAME)

to:

"dword", $FILE_NOTIFY_CHANGE_DIR_NAME

and as expected it began reporting only folder changes. However, I don't know how I would go about recording both file and folder changes while letting me know which are files and which are folders

thanks for your help

[font="Impact"] I always thought dogs laid eggs, and I learned something today. [/font]
Link to comment
Share on other sites

If it's a removal, I can't think of a way to determine... otherwise:

$filenameANDpath = @WindowsDir
if StringInStr(FileGetAttrib($filenameANDpath),"D") then
    ConsoleWrite($filenameANDpath & " is a directory..." & @crlf)
Else
    ConsoleWrite($filenameANDpath & " is a file..." & @crlf)
EndIf

$filenameANDpath = "c:\config.sys"
if StringInStr(FileGetAttrib($filenameANDpath),"D") then
    ConsoleWrite($filenameANDpath & " is a directory..." & @crlf)
Else
    ConsoleWrite($filenameANDpath & " is a file..." & @crlf)
EndIf
Link to comment
Share on other sites

Exactly what I was thinking...

I have a couple solutions but each of them have a least 1 limitation such as the one above, anyone know a method that can do everything?

[font="Impact"] I always thought dogs laid eggs, and I learned something today. [/font]
Link to comment
Share on other sites

Anyone with experience on the subject care to share their input?

Like I said above, I'm able to get only folder information specifically or only file information but I'm not sure how to combine the two while being able to differentiate between the two.

[font="Impact"] I always thought dogs laid eggs, and I learned something today. [/font]
Link to comment
Share on other sites

  • 2 months later...

I too have been checking out this au3 app and have been having a lot of fun with it. I was wondering if anyone knew how to modify a few things. I was attempting to accomplish the following:

Pop-up window when an item entered into a certain folder (as a notifier)

Minimize the application so it runs as a little icon by the icon tray next to the clock (and keeps running/monitoring the predetermined folder).

Has the ability to be brought back into "normal" view if you right click the icon and select "maximize" or some such word.

See just the file name not the "C:\print\INCOMING\" (or the directory). This because I have the file being changed to a date/time stamp and

scrolling over to see which is which is cumbersome.

I have found stand alone "popup" and "minimize" au3 doc's on the forums but I don't know how to work them into this one. I've been linking them by inserting run("popup.exe", "", @SW_SHOW) but there is lag time when I do that. I would like to have it all run together.

The purpose behind all of this:

If you set your printer (on Windows XP) to "Print to Text" the only option it gives you is to print as the same file ex. DOC.txt

I am acting as the router for our office and all of the printed documents DO NOT need to be printed and they come in at times in rapid succession and when my program is hanging up the newest print document writes over the last one and I completely miss a document!

I'm trying to get it so that when the DOC.txt arrives in the predesignated folder my program automatically time/date stamps it with a unique number, sends me audio noises and visual notification that I have a new DOC.txt that has been received (and renamed) for me in the print folder I'm monitoring.

Also, the "ZMV Monitor.au3" will not run unless you have "C:\print\INCOMING\" because it is set to monitor that location. When it finds that location does not exist it stops trying and closes out.

I hope this makes sense. I'll attach all of the different coding I've been working on and if anyone could help me that would be wonderful!!

Thank you so much in advance!

Matt

ZMV Monitor.au3

rename.au3

popup.au3

Link to comment
Share on other sites

  • 2 years later...

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...