Skip to content

Instantly share code, notes, and snippets.

@SimonEast
Last active November 24, 2022 16:21
Show Gist options
  • Save SimonEast/15dba0422f6bb7e6cf0f6e9a8657f1cd to your computer and use it in GitHub Desktop.
Save SimonEast/15dba0422f6bb7e6cf0f6e9a8657f1cd to your computer and use it in GitHub Desktop.
Watching a Folder for Changed Files Using AutoIt Script

Watching a Folder for Changed Files Using AutoIt Script

The example scripts below show a clean way of monitoring a folder for changes and responding to those changes.

The _WinAPI_ReadDirectoryChanges() function provided by AutoIt is quite helpful for this, but unfortunately it is a "blocking" synchronous function, which means that once it is called, it hangs the script and users cannot exit it via the tray icon (well not until a file change is triggered). To workaround this, the scripts below run as a "parent" script and a "child" script. The parent runs the child in a separate process and if the parent is exited, it also exits the child process, which is clean and better for usability.

There's also some helpful comments included in the script, as well as an option of filtering file changes based on a specific file extension, if that's what you need.

Comments and feedback welcome.

Known Issues

Unfortunately the _WinAPI_ReadDirectoryChanges() function often returns the same filename multiple times when a change is made. If you're moving the file to a new destination, any subsequent operations will fail as the file is no longer present. You will need to handle this situation yourself (unless someone can offer a graceful handling of this).

#include "WatchFolderForChanges.au3"
WatchFolderForChanges(@ScriptDir, DoSomething, "zip,txt")
Func DoSomething($filename)
ConsoleWrite("The following file was changed: " & $filename & @CRLF)
EndFunc
; This script can be used in other scripts to monitor new/changed files in a specific folder.
; Simply include it like this and provide a function to be called:
;
; #include "WatchFolderForChanges.au3"
;
; WatchFolderForChanges("C:\MyFolder", MyCallback, "txt,zip")
; Func MyCallback($filename)
; ; Do something with $filename
; EndFunc
; TODO: Would be good to filter out duplicate events for the same file here. But I'm not sure of the best way to do that.
#include <AutoItConstants.au3>
#include <StringConstants.au3>
Func WatchFolderForChanges($folder, $callback, $fileExtensionList = "")
; Start child process
; The important flag here is to use $STDOUT_CHILD which allows us to receive data
; We cannot use ShellExecute() here because of the need to set this flag.
ConsoleWrite("Starting child process..." & @CRLF)
Global $childProcessID = Run(@AutoItExe & " " & "WatchFolderForChangesChild.au3" & " """ & $folder & """", "", @SW_HIDE, $STDOUT_CHILD)
; Close child process when parent exits
OnAutoItExitRegister("OnAutoItExit")
$fileExtensionList = StringSplit($fileExtensionList, ",", $STR_NOCOUNT)
; Read data from child, while it exists
; Increase the sleep time to use less CPU, or decrease it to make it more responsive
While 1
Sleep(100)
$data = StdoutRead($childProcessID)
If @error Then
ConsoleWrite("Child has exited." & @CRLF)
ExitLoop
EndIf
If $data Then
; Clean up any whitespace, and process each line of output separately
$data = StringStripWS($data, $STR_STRIPLEADING + $STR_STRIPTRAILING)
$data = StringSplit($data, @CRLF, $STR_ENTIRESPLIT + $STR_NOCOUNT)
For $item In $data
; If a list of file extensions was provided, check if the file matches one in the list
If $fileExtensionList[0] = "" Or _ArraySearch($fileExtensionList, FileExtension($item)) > -1 Then
ConsoleWrite("File change registered: " & $item & ", with file extension: " & FileExtension($item) & @CRLF)
$callback($item)
Else
ConsoleWrite("File change ignored: " & $item & ", with file extension: " & FileExtension($item) & @CRLF)
EndIf
Next
EndIf
WEnd
EndFunc
Func OnAutoItExit()
ConsoleWrite("Parent script told to exit. Now exiting child script also." & @CRLF)
ProcessClose($childProcessID)
EndFunc
#include <File.au3>
#include <FileConstants.au3>
Func FileExtension($fullPath)
Local $x = ""
Return StringTrimLeft(_PathSplit($fullPath, $x, $x, $x, $x)[$PATH_EXTENSION], 1)
EndFunc
; This script is meant to run standalone, NOT included.
; It will watch a specific folder, as passed in via command line parameters
; And it will put the filename of any new/edited/removed file into the default console output (StdOut) on its own line
; This output is designed to be handled by the parent script WatchFolderForChanges.au3
#include <WinAPIFiles.au3>
$monitorSubfolders = false
$path = $CmdLine[1]
; ConsoleWrite("Folder monitoring started." & @CRLF)
; Get a handle to a specific folder
Local $hDirectory = _WinAPI_CreateFileEx($path, $OPEN_EXISTING, $FILE_LIST_DIRECTORY, BitOR($FILE_SHARE_READ, $FILE_SHARE_WRITE), $FILE_FLAG_BACKUP_SEMANTICS)
If @error Then
_WinAPI_ShowLastError('', 1)
EndIf
Local $pBuffer = _WinAPI_CreateBuffer(2000000)
Local $aData
While 1
; Wait until a file is created/modified/deleted in our watched folder.
; This function is BLOCKING meaning the script will hang on this line until an event occurs.
; This particular script cannot be exited via the tray icon until a file event occurs, which is why it's best used
; with a parent script that controls it and can kill it when it's time to exit.
; The function returns an array where there are possibly multiple file events if many files are
; created/copied/deleted in quick succession.
; Also note that certain file events like new/changed files sometimes trigger 2 or 3 events for the same
; file. If you want to exclude those duplicates, you'll need to handle that yourself.
$aData = _WinAPI_ReadDirectoryChanges($hDirectory, BitOR($FILE_NOTIFY_CHANGE_FILE_NAME, $FILE_NOTIFY_CHANGE_LAST_WRITE), $pBuffer, 2000000, $monitorSubfolders)
If Not @error Then
; Uncomment this if you'd like to see the full array that is returned
; _ArrayDisplay($aData, '_WinAPI_ReadDirectoryChanges')
For $i = 1 To $aData[0][0]
; $aData[$i][1] will equal one of the following:
; $FILE_ACTION_ADDED = 0x0001
; $FILE_ACTION_REMOVED = 0x0002
; $FILE_ACTION_MODIFIED = 0x0003
; $FILE_ACTION_RENAMED_OLD_NAME = 0x0004
; $FILE_ACTION_RENAMED_NEW_NAME = 0x0005
ConsoleWrite($aData[$i][0] & @CRLF)
Next
Else
_WinAPI_ShowLastError('', 1)
EndIf
WEnd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment