1 (изменено: Александр_, 2011-10-22 22:03:34)

Тема: AHK: Аудит файловой системы и вызов wait-функции в отдельном потоке

Вариант 1, используем функции FindFirstChangeNotification/FindFirstChangeNotification.
Метод позволяет отлавливать изменения, но не предоставляет никакой информации о типах изменений и о конкретных файлах.

DetectHiddenWindows, on ; чтоб найти скрытое окно AHK
dir := "C:\Users\Александр\Desktop\test\cur" ; директория для мониторинга
FILE_NOTIFY_CHANGE_FILE_NAME :=  1  ; мониторинг изменений имён файлов(функция FindFirstChangeNotification)
INFINITE                     := -1  ; неограниченное время ожидания(функция WaitForSingleObject)
WM_USER                      := 0x400 ; начало диапазона пользовательских сообщений
Unicode                      := A_IsUnicode ? "W" : "A"
 
FindFirstChangeNotification(PathName, WatchSubtree, NotifyFilter)
; функция начинает отслеживать изменения в файловой системе
; PathName- имя директории
; WatchSubtree- мониторить ли поддиректории?(0- нет, любое другое значение- да)
; NotifyFilter- набор флагов, указывающих какие изменения нужно отслеживать
{
   global Unicode
   return DllCall("FindFirstChangeNotification" . Unicode, "str", PathName, "int", WatchSubtree, "int", NotifyFilter)
}
 
FindNextChangeNotification(hChangeHandle)
; функция продолжает мониторинг, начатый FindFirstChangeNotification
; hChangeHandle- хэндл, возвращённый функцией FindFirstChangeNotification
{
   return DllCall("FindNextChangeNotification", "int", hChangeHandle)
}
 
FindCloseChangeNotification(hChangeHandle)
; функция заканчивает мониторинг, начатый FindFirstChangeNotification
; hChangeHandle- хэндл, возвращённый функцией FindFirstChangeNotification
{
   return DllCall("FindCloseChangeNotification", "int", hChangeHandle)
}
 
CreateThread(ThreadAttributes, StackSize, StartAddress, Parameter, CreationFlags, ThreadId)
; функция создаёт поток
; ThreadAttributes- атрибуты безопасности, почти всегда 0
; StackSize- начальный размер стека, обычно тоже 0
; StartAddress- адрес функции, которую будет исполнять поток
; Parameter- параметр, который получит функция StartAddress
; CreationFlags- флаги создания, обычно 0, иногда 4(тогда поток создаётся, но не начинает выполняться)
; ThreadId- ссылка на переменную, в которую будет помещён идентификатор потока
{
   return DllCall("CreateThread","int",ThreadAttributes,"int",StackSize,"int",StartAddress,"int",Parameter,"int",CreationFlags,"int",ThreadId)
}
 
CloseHandle(Handle)
; функция закрывает хэндл Handle
{
   return DllCall("CloseHandle","int",Handle)
}
 
TerminateThread(hThread, ExitCode)
; функция убивает поток по дескриптору hThread
; ЭТУ ФУНКЦИЮ НЕЛЬЗЯ ИСПОЛЬЗОВАТЬ В ОБЫЧНЫХ ПРИЛОЖЕНИЯХ
{
   return DllCall("TerminateThread", "int", hThread, "int", ExitCode)
}
 
IfUnicodeThenUnicodeToAnsi(ByRef wStr, ByRef str)
; функция конвертирует Unicode-строку в Ascii в unicode-скриптах
; в ascii-скриптах ничего не делает
{
   if (!A_IsUnicode)
      return wStr
   Size := StrLen(wStr)*2
   VarSetCapacity(str, Size)
   DllCall("WideCharToMultiByte", "Uint", 0, "Uint", 0, "Uint", &wStr, "int",  -1, "str",  str, "int",  Size, "Uint", 0, "Uint", 0)
   return str
}
 
VarSetCapacity(hThread, 4)
;дескриптор потока
 
VarSetCapacity(hChangeHandle, 4)
;хэндл из FindFirstChangeNotification
 
VarSetCapacity(hWnd, 4)
WinGet, hWnd, ID, ahk_class AutoHotkey
;хэндл окна
 
VarSetCapacity(PostMessageAddr, 4)
NumPut(DllCall("GetProcAddress","int",DllCall("GetModuleHandle" . Unicode,"str","user32.dll"),"str",IfUnicodeThenUnicodeToAnsi("PostMessage" . Unicode, tempstr)),PostMessageAddr,0)
; адрес PostMessage
 
VarSetCapacity(WaitForSingleObjectAddr, 4)
NumPut(DllCall("GetProcAddress","int",DllCall("GetModuleHandle" . Unicode,"str","Kernel32.dll"),"str",IfUnicodeThenUnicodeToAnsi("WaitForSingleObject",tempstr)),WaitForSingleObjectAddr,0)
; адрес WaitForSingleObject
 
; Функция потока
; Ожидает возврата из WaitForSingleObject и отправляет сообщение окну
; lParam содержит возвращаемое WaitForSingleObject значение
VarSetCapacity(springboard, 38)
NumPut(0x68, springboard, 0, "Char")
NumPut(INFINITE, springboard, 1)
;push -1
NumPut(0x68, springboard, 5, "Char")
;NumPut(hChangeHandle, springboard, 6)
;push hChangeHandle
NumPut(0x15ff, springboard, 10, "Short")
NumPut(&WaitForSingleObjectAddr, springboard, 12)
;call WaitForsingleObject
NumPut(0x50, springboard, 16, "Char")
;push EAX
NumPut(0x6A, springboard, 17, "Char")
NumPut(0x00, springboard, 18, "Char")
;push 0
NumPut(0x68, springboard, 19, "Char")
NumPut(WM_USER, springboard, 20)
;push 0x400
NumPut(0x68, springboard, 24, "Char")
NumPut(hWnd, springboard, 25)
;push hWnd
NumPut(0x15ff, springboard, 29, "Short")
NumPut(&PostMessageAddr, springboard, 31)
;Call PostMessage
NumPut(0xC2, springboard, 35,"char")
NumPut(4, springboard, 36,"short")
;ret 4
 
ThreadFunc(wParam, lParam)
{
   global hChangeHandle, springboard, hThread
   CloseHandle(hThread)
   if lParam
   {
      ;случился какой-то косяк
      return 0
   }
   MsgBox 4, , Был создан, удалён или переименован какой-то файл`r`nВы хотите продолжить мониторинг?
   IfMsgBox  No
      FindCloseChangeNotification(hChangeHandle)
   else
   {
      FindNextChangeNotification(hChangeHandle)
      hThread:=CreateThread(0, 64*1024, &springboard, 0, 0, 0)
   }
   return 0
}
 
;будем обрабатывать сообщение WM_USER, функцией ThreadFunc
OnMessage(WM_USER,"ThreadFunc")
 
hChangeHandle:=FindFirstChangeNotification( dir, 0, FILE_NOTIFY_CHANGE_FILE_NAME)
NumPut(hChangeHandle, springboard, 6)
hThread:=CreateThread(0, 64*1024, &springboard, 0, 0, 0)
return
 
F12::
   ; Так делать не надо, этот код исключительно для удобства тестирования
   TerminateThread(hThread, -1)
   CloseHandle(hThread)
   FindCloseChangeNotification(hChangeHandle)
return

Вариант 2, используем функцию ReadDirectoryChangesW.
Метод позволяет отлавливать изменения, а также сообщает имена файлов и различает пять видов действий.

#include %A_ScriptDir%\windows.ahk
 
DetectHiddenWindows, on ; чтоб найти скрытое окно AHK
WM_USER                      := 0x400 ; начало диапазона пользовательских сообщений
WM_DIRECTORYCHANGE           := WM_USER + 1
 
IfAnsiThenUnicodeToAnsi(ByRef wStr, ByRef str, len)
{
   if A_IsUnicode
      return wStr
   len := len/2+1
   VarSetCapacity(str, len)
   DllCall("WideCharToMultiByte", "Uint", 0, "Uint", 0, "Uint", &wStr, "int",  -1, "str",  str, "int",  len, "Uint", 0, "Uint", 0)
   return str
}
 
VarSetCapacity(hThread, 4)
 
VarSetCapacity(hDirectory, 4)
 
VarSetCapacity(hWnd, 4)
PID := GetCurrentProcessId()
WinGet, hWnd, ID, ahk_pid %PID% ahk_class AutoHotkey
 
VarSetCapacity(PostMessageAddr, 4)
NumPut(GetProcAddress(GetModuleHandle("user32.dll"), "PostMessage" . Unicode),PostMessageAddr,0)
 
VarSetCapacity(ReadDirectoryChangesWAddr, 4)
NumPut(GetProcAddress(GetModuleHandle("Kernel32.dll"),"ReadDirectoryChangesW"),ReadDirectoryChangesWAddr,0)
 
VarSetCapacity(BytesReturned, 4)
VarSetCapacity(outBuffer, 0x400)
 
VarSetCapacity(springboard, 100)
NumPut(0x68, springboard, 0)
NumPut(0x00, springboard, 1)
NumPut(0x68, springboard, 5)
NumPut(0x00, springboard, 6)
NumPut(0x68, springboard, 10)
NumPut(&BytesReturned, springboard, 11)
NumPut(0x68, springboard, 15)
;NumPut(FILE_NOTIFY_CHANGE_FILE_NAME, springboard, 16)
NumPut(0x68, springboard, 20)
;NumPut(0x00, springboard, 21)
NumPut(0x68, springboard, 25)
NumPut(0x400, springboard, 26)
NumPut(0x68, springboard, 30)
NumPut(&outBuffer, springboard, 31)
NumPut(0x68, springboard, 35, "Char")
;NumPut(hDirectory, springboard, 36)
NumPut(0x15ff, springboard, 40, "Short")
NumPut(&ReadDirectoryChangesWAddr, springboard, 42)
NumPut(0x50, springboard, 46, "Char")
NumPut(0x6A, springboard, 47, "Char")
NumPut(0x00, springboard, 48, "Char")
NumPut(0x68, springboard, 49, "Char")
NumPut(WM_DIRECTORYCHANGE, springboard, 50)
NumPut(0x68, springboard, 54, "Char")
NumPut(hWnd, springboard, 55)
NumPut(0x15ff, springboard, 59, "Short")
NumPut(&PostMessageAddr, springboard, 61)
NumPut(0xC2, springboard, 65,"char")
NumPut(4, springboard, 66,"short")
 
ThreadFunc(wParam, lParam)
{
   global
   CloseHandle(hThread)
   if (!lParam)
   {
      msgbox "exit", %lParam%, %A_lasterror%
      return 0
   }
   next := 0
   loop
   {
   type := NumGet(outBuffer,4+next)
   if (type == FILE_ACTION_ADDED)
      msg := "Создан файл "
   else if (type = FILE_ACTION_REMOVED)
      msg := "Удалён файл "
   else if (type = FILE_ACTION_MODIFIED)
      msg := "Изменён файл "
   else if (type = FILE_ACTION_RENAMED_OLD_NAME)
      msg := "Файл переименован из "
   else if (type = FILE_ACTION_RENAMED_NEW_NAME)
      msg := "Файл переименован в "
   else 
      msg := type
   NameLen := NumGet(outBuffer,8+next)
   VarSetCapacity(fn, NameLen+2)
   WriteProcessMemory(GetCurrentProcess(), &fn, &outBuffer+12+next, NameLen, &BytesReturned)
   NumPut(0x0000, fn, NameLen ,"short")
   next := NumGet(outBuffer, next)
   LV_Add("", msg, IfAnsiThenUnicodeToAnsi(fn, str, NameLen) )
   if (!next)
      break
   }
   hThread:=CreateThread(0, 64*1024, &springboard, 0, 0, 0)
   return 0
}
 
OnMessage(WM_DIRECTORYCHANGE,"ThreadFunc")
 
 
PathName := A_ScriptDir
Status := 0
Options := 0
Gui Add, Edit, w200 ReadOnly vE_PathName, %PathName%
Gui Add, Button, x210 y5 w50 vB_PathName gG_PathName, ...
Gui Add, Button, x300 y5 w150 vB_Start gG_Start, Начать слежение
Gui Add, Text, x10, Типы изменений:
Gui Add, Checkbox, x220 y35 vCh_SubDir gG_FILE_NOTIFY_CHANGE, Следить за поддиректориями
Gui Add, Checkbox, x10 y55 w110 vCh_FILE_NAME gG_FILE_NOTIFY_CHANGE, Имена файлов
Gui Add, Checkbox, x120 y55 w110 vCh_DIR_NAME gG_FILE_NOTIFY_CHANGE, Имена папок
Gui Add, Checkbox, x230 y55 w110 vCh_ATTRIBUTES gG_FILE_NOTIFY_CHANGE, Атрибуты
Gui Add, Checkbox, x340 y55 w110 vCh_SIZE gG_FILE_NOTIFY_CHANGE, Размер
Gui Add, Checkbox, x10 y75 w110 vCh_LAST_WRITE gG_FILE_NOTIFY_CHANGE, Запись
Gui Add, Checkbox, x120 y75 w110 vCh_LAST_ACCESS gG_FILE_NOTIFY_CHANGE, Доступ
Gui Add, Checkbox, x230 y75 w110 vCh_CHANGE_CREATION gG_FILE_NOTIFY_CHANGE, Время создания
Gui Add, Checkbox, x340 y75 w110 vCh_CHANGE_SECURITY gG_FILE_NOTIFY_CHANGE, Безопасность
Gui Add, ListView, x10 r20 w450 NoSort, Действие|Имя файла
LV_ModifyCol(1, 146)
LV_ModifyCol(2, 300)
Gui Show
return 
 
GuiClose:
   ExitApp
return
 
G_PathName:
   if (Status=1)
   {
      MsgBox Перед сменой директории остановите текущее слежение!
      return
   }
   Gui Submit, NoHide
   FileSelectFolder PathName , *%E_PathName%, 3, Выбирите папку для наблюдения
   if (PathName!="")
   {
      GuiControl ,, E_PathName, %PathName%
   }
return
 
G_Start:
   if (Status=1) ;остановка
   {
      Status := 0
      TerminateThread(hThread, -1)
      CloseHandle(hThread)
      CloseHandle(hDirectory)
      GuiControl ,, B_Start, Начать слежение
      LV_Add("", "Слежение прервано", PathName)
   }
   else if (Status=0) ;начало
   {
      Gui Submit, NoHide
      Options := FILE_NOTIFY_CHANGE_FILE_NAME*Ch_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME*Ch_DIR_NAME|FILE_NOTIFY_CHANGE_ATTRIBUTES*Ch_ATTRIBUTES|FILE_NOTIFY_CHANGE_SIZE*Ch_SIZE|FILE_NOTIFY_CHANGE_LAST_WRITE*Ch_LAST_WRITE|FILE_NOTIFY_CHANGE_LAST_ACCESS*Ch_LAST_ACCESS|FILE_NOTIFY_CHANGE_CREATION*Ch_CHANGE_CREATION|FILE_NOTIFY_CHANGE_SECURITY*Ch_CHANGE_SECURITY
      if (Options=0)
      {
         MsgBox не выбран ни один тип слежения!
         return
      }
      Status := 1
      NumPut(Ch_SubDir, springboard, 21)
      NumPut(Options, springboard, 16)
      hDirectory := CreateFile(E_PathName, FILE_LIST_DIRECTORY, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
      if (hDirectory=-1)
      {
         msgbox CreateFile error, %a_lasterror%
         ExitApp
      }
      NumPut(hDirectory, springboard, 36)
      hThread:=CreateThread(0, 64*1024, &springboard, 0, 0, 0)
      GuiControl ,, B_Start, Прервать слежение
      LV_Add("", "Слежение начато", PathName)
   }
   else ;перезапуск
   {
      TerminateThread(hThread, -1)
      CloseHandle(hThread)
      CloseHandle(hDirectory)
      LV_Add("", "Слежение прервано", PathName)
      Status := 0
      Goto G_Start
   }
return
 
G_FILE_NOTIFY_CHANGE:
   if (Status=1)
   {
      Status = -1
      GuiControl ,, B_Start, Применить изменения
   }
return

вспомогательный файл "windows.ahk", который должен лежать в папке с основным(в нём только константы и оболочки WinApi функций):

Unicode          := A_IsUnicode ? "W" : "A"
 
; CreateFile, ShareMode
FILE_SHARE_READ  := 1
FILE_SHARE_WRITE := 2
 
; CreateFile, DesiredAccess
GENERIC_READ     := 0x80000000
GENERIC_WRITE    := 0x40000000
; дополнительные атрибуты доступа
; тут необходимо указать FILE_LIST_DIRECTORY
FILE_ADD_FILE             := 0x0002
FILE_ADD_SUBDIRECTORY     := 0x0004
FILE_APPEND_DATA          := 0x0004
FILE_CREATE_PIPE_INSTANCE := 0x0004
FILE_DELETE_CHILD         := 0x0040
FILE_EXECUTE              := 0x0020
FILE_LIST_DIRECTORY       := 0x0001 ;право просматривать содержание директории
FILE_READ_ATTRIBUTES      := 0x0080
FILE_READ_DATA            := 0x0001
FILE_READ_EA              := 0x0008
FILE_TRAVERSE             := 0x0020
FILE_WRITE_ATTRIBUTES     := 0x0100
FILE_WRITE_DATA           := 0x0002
FILE_WRITE_EA             := 0x0010
 
; события получаемые в структуре FILE_NOTIFY_INFORMATION
FILE_ACTION_ADDED            := 0x00000001
FILE_ACTION_REMOVED          := 0x00000002
FILE_ACTION_MODIFIED         := 0x00000003 
FILE_ACTION_RENAMED_OLD_NAME := 0x00000004
FILE_ACTION_RENAMED_NEW_NAME := 0x00000005 
 
; CreateFile, CreationDisposition
CREATE_NEW        := 1
CREATE_ALWAYS     := 2
OPEN_EXISTING     := 3
OPEN_ALWAYS       := 4
TRUNCATE_EXISTING := 5
 
; CreateFile, FlagsAndAttributes
FILE_ATTRIBUTE_READONLY   := 0x00000001
FILE_ATTRIBUTE_HIDDEN     := 0x00000002
FILE_ATTRIBUTE_SYSTEM     := 0x00000004
FILE_ATTRIBUTE_DIRECTORY  := 0x00000010
FILE_ATTRIBUTE_ARCHIVE    := 0x00000020
FILE_ATTRIBUTE_NORMAL     := 0x00000080
FILE_ATTRIBUTE_TEMPORARY  := 0x00000100
FILE_ATTRIBUTE_COMPRESSED := 0x00000800
FILE_FLAG_POSIX_SEMANTICS  := 0x01000000
FILE_FLAG_BACKUP_SEMANTICS := 0x02000000
FILE_FLAG_DELETE_ON_CLOSE  := 0x04000000
FILE_FLAG_SEQUENTIAL_SCAN  := 0x08000000
FILE_FLAG_RANDOM_ACCESS    := 0x10000000
FILE_FLAG_NO_BUFFERING     := 0x20000000
FILE_FLAG_OVERLAPPED       := 0x40000000
FILE_FLAG_WRITE_THROUGH    := 0x80000000
 
FILE_NOTIFY_CHANGE_FILE_NAME   := 0x00000001
FILE_NOTIFY_CHANGE_DIR_NAME    := 0x00000002
FILE_NOTIFY_CHANGE_ATTRIBUTES  := 0x00000004
FILE_NOTIFY_CHANGE_SIZE        := 0x00000008
FILE_NOTIFY_CHANGE_LAST_WRITE  := 0x00000010
FILE_NOTIFY_CHANGE_LAST_ACCESS := 0x00000020
FILE_NOTIFY_CHANGE_CREATION    := 0x00000040
FILE_NOTIFY_CHANGE_SECURITY    := 0x00000100 
 
GetCurrentProcessId()
{
   return DllCall("GetCurrentProcessId")
}
 
GetCurrentProcess()
{
   return DllCall("GetCurrentProcess")
}
 
GetModuleHandle(Name)
{
   global Unicode
   return DllCall("GetModuleHandle" . Unicode, "str", Name)
}
 
GetProcAddress(hModule, FuncName)
{
   if (!A_IsUnicode)
      return DllCall("GetProcAddress", "int", hModule, "str", FuncName)
   len := StrLen(FuncName)+1
   VarSetCapacity(str, len)
   WideCharToMultiByte(0, 0, &FuncName, -1, &str, len, 0, 0)
   ret := DllCall("GetProcAddress", "int", hModule, "int", &str)
   VarSetCapacity(str, 0)
   return ret
}
 
WideCharToMultiByte(CodePage, Flags, WideCharStr, WideChar, MultiByteStr, MultiByte, DefaultChar, UsedDefaultChar)
{
   return DllCall("WideCharToMultiByte", "Uint", CodePage, "Uint", Flags, "int", WideCharStr, "int",  WideChar, "int", MultiByteStr, "int", MultiByte, "Uint", DefaultChar, "Uint", UsedDefaultChar)
}
 
CreateFile(FileName, DesiredAccess, ShareMode, SecurityAttributes, CreationDisposition, FlagsAndAttributes, hTemplateFile)
; FileName- путь к файлу
; DesiredAccess- требуемый доступ(GENERIC_*)
; ShareMode- права на совместный доступ(FILE_SHARE_)
; SecurityAttributes- атрибуты защиты(обычно нуль)
; CreationDisposition- действия над файлом
; FlagsAndAttributes- атрибуты и флаги файла(FILE_ATTRIBUTE_* и FILE_FLAG_*, обычно FILE_ATTRIBUTE_NORMAL)
; hTemplateFile- файл, от которого наследуется FlagsAndAttributes(обычно 0)
{
   global Unicode
   return DllCall("CreateFile" . Unicode, "str", FileName, "int", DesiredAccess, "int", ShareMode, "int", SecurityAttributes, "int", CreationDisposition, "int", FlagsAndAttributes, "int", hTemplateFile)
}
 
CreateThread(ThreadAttributes, StackSize, StartAddress, Parameter, CreationFlags, ThreadId)
; функция создаёт поток
; ThreadAttributes- атрибуты безопасности, почти всегда 0
; StackSize- начальный размер стека, обычно тоже 0
; StartAddress- адрес функции, которую будет исполнять поток
; Parameter- параметр, который получит функция StartAddress
; CreationFlags- флаги создания, обычно 0, иногда 4(тогда поток создаётся, но не начинает выполняться)
; ThreadId- ссылка на переменную, в которую будет помещён идентификатор потока
{
   return DllCall("CreateThread","int",ThreadAttributes,"int",StackSize,"int",StartAddress,"int",Parameter,"int",CreationFlags,"int",ThreadId)
}
 
CloseHandle(Handle)
; функция закрывает хэндл Handle
{
   return DllCall("CloseHandle","int",Handle)
}
 
TerminateThread(hThread, ExitCode)
; функция убивает поток по дескриптору hThread
; ЭТУ ФУНКЦИЮ НЕЛЬЗЯ ИСПОЛЬЗОВАТЬ В ОБЫЧНЫХ ПРИЛОЖЕНИЯХ
{
   return DllCall("TerminateThread", "int", hThread, "int", ExitCode)
}
 
WriteProcessMemory(hProcess, BaseAddress, Buffer, Size, NumberOfBytesWritten)
; функция записывает Size байт с адреса Buffer в процесс hProcess по адресу BaseAddress
; в NumberOfBytesWritten помещает число записанный байтов
{
   return DllCall("WriteProcessMemory", "int", hProcess, "int", BaseAddress, "int", Buffer, "int", Size, "int", NumberOfBytesWritten)
}

см. также:
Ускорение скриптов машинным кодом
Obtaining Directory Change Notifications [ENG]

2 (изменено: YMP, 2014-06-14 13:01:30)

Re: AHK: Аудит файловой системы и вызов wait-функции в отдельном потоке

По мотивам второго скрипта Александра (см. пост выше) я написал версию, работающую для обеих битностей (32 и 64) и кодировок (но GUI нет). Алгоритм тот же: ReadDirectoryChangesW запускается через машинную функцию в отдельном потоке и посылает в скрытое окно скрипта сообщения о событиях в папке. Обработка сообщений идёт в функции WM_DIRECTORYCHANGE: она получает адрес выходного буфера с данными о событиях и количество записанных туда байт. Можно здесь прописать какие-то действия для нужных событий. В примере ниже просто формируется и выводится сообщение о типе действия и, если определена глобально переменная LogFile, в этот лог пишутся эти сообщения. Фильтр для событий можно задать в функции WatchDirectory в переменной NotifyFilter. Если повторно вызвать WatchDirectory с именем папки, предыдущее слежение будет остановлено автоматически и начато новое. Вызов с нулём — остановка.


DirName := A_ScriptDir          ; Папка для слежения.
LogFile := A_Desktop . "\DirLog.txt"

F10:: WatchDirectory(DirName)   ; Начать слежение.
F11:: WatchDirectory(0)         ; Остановить.


; ================ Функции =====================================================

WM_DIRECTORYCHANGE(BytesReturned, pOutBuf)
{
    Global LogFile
    ;FILE_ACTION_ADDED := 1, FILE_ACTION_REMOVED := 2, FILE_ACTION_MODIFIED := 3
    ;FILE_ACTION_RENAMED_OLD_NAME := 4, FILE_ACTION_RENAMED_NEW_NAME := 5
    Static Actions := ["Файл добавлен:  ", "Файл удалён:    ", "Файл изменён:   "
                     , "Файл переименован с имени: ", "Файл переименован на имя:  "]
    If (pOutBuf = 0) {
        MsgBox, Ошибка в ReadDirectoryChangeW
        Return
    }
    DateTime := A_DD "." A_MM "." A_YYYY " " A_Hour ":" A_Min ":" A_Sec
    Addr := pOutBuf, Next := 0  ; Адрес текущей записи и смещение до следующей.
    Loop    ; Чтение из буфера записей о событиях и реакция на них.
    {
        Addr += Next, Next := NumGet(Addr+0, 0, "uint") ; Смещение следующей записи.
        ActionCode := NumGet(Addr+0, 4, "uint") ; Код события (см. в начале функции).
        If (Action := Actions[ActionCode]) {    ; Описание события из массива.
            cbFile := NumGet(Addr+0, 8, "uint") ; Длина имени файла в байтах.
            FileName := StrGet(Addr+12, cbFile // 2, "utf-16")
            Msg .= DateTime " " Action FileName "`n"
        }
        If (!Next)  ; Если смещение равно 0, больше записей в буфере нет.
            Break
    }
    If (Msg) {
        ToolTip, %Msg%
        If (LogFile)
            FileAppend, %Msg%, %LogFile%
        Sleep, 1000
        ToolTip
    }
    WatchDirectory(-1)  ; Продолжить слежение.
}

WatchDirectory(DirName)
{
    Static hDir, hThread, pData, pThreadStart, OutBuf, OutBufSize := 0x400  ; 1 KB
    Static BytesReturned, WM_DIRECTORYCHANGE := 0x401
    ;FILE_NOTIFY_CHANGE_FILE_NAME := 0x1, FILE_NOTIFY_CHANGE_DIR_NAME := 0x2
    ;FILE_NOTIFY_CHANGE_ATTRIBUTES := 0x4, FILE_NOTIFY_CHANGE_SIZE := 0x8
    ;FILE_NOTIFY_CHANGE_LAST_WRITE := 0x10, FILE_NOTIFY_CHANGE_LAST_ACCESS := 0x20
    ;FILE_NOTIFY_CHANGE_CREATION := 0x40, FILE_NOTIFY_CHANGE_SECURITY := 0x100
    Static NotifyFilter := 0x11     ; Комбинация из флагов выше (сумма).
    If (DirName = -1) {
        DllCall("CloseHandle", "ptr", hThread)
        Goto NewThread
    }
    Else If (DirName = 0) {
        If (hThread) {
            DllCall("TerminateThread", "ptr", hThread, "int", 0)
            DllCall("CloseHandle", "ptr", hThread), hThread := 0
        }
        If (hDir)
            DllCall("CloseHandle", "ptr", hDir), hDir := 0
        Return
    }
    If (hDir)
        WatchDirectory(0) ; Остановить текущее слежение.
    If (!OutBuf) {
        VarSetCapacity(OutBuf, OutBufSize, 0), VarSetCapacity(BytesReturned, 4, 0)
        OnMessage(WM_DIRECTORYCHANGE, "WM_DIRECTORYCHANGE")
        If !(pReadDirectoryChanges := GetProcAddress("kernel32.dll", "ReadDirectoryChangesW"))
            Return Error("GetProcAddress - ReadDirectoryChangesW")
        If !(pPostMessage := GetProcAddress("user32.dll", "PostMessage" . (A_IsUnicode? "W":"A")))
            Return Error("GetProcAddress - PostMessage")
        If !(pThreadStart := CreateMachineFunc())
            Return Error("CreateMachineFunc")
        pData := CreateStruct(pReadDirectoryChanges, hDir, &OutBuf, OutBufSize, 0
                            , NotifyFilter, &BytesReturned, 0, 0
                            , pPostMessage, A_ScriptHwnd, WM_DIRECTORYCHANGE)
    }
    If !(hDir := OpenDirectory(DirName))
        Return Error("OpenDirectory")
    NumPut(hDir, pData+0, A_PtrSize, "ptr")
NewThread:
    If !(hThread := CreateThread(pThreadStart, pData))
        Return Error("CreateThread")
    Return True
}

OpenDirectory(Dir)
{
    Static FILE_LIST_DIRECTORY := 1, FILE_SHARE_READ := 1, FILE_SHARE_WRITE := 2
    Static OPEN_EXISTING := 3, FILE_FLAG_BACKUP_SEMANTICS := 0x02000000
    Static INVALID_HANDLE_VALUE := -1
    hDir := DllCall("CreateFile", "str", Dir, "uint", FILE_LIST_DIRECTORY
            , "uint", FILE_SHARE_READ | FILE_SHARE_WRITE, "ptr", 0, "uint", OPEN_EXISTING
            , "uint", FILE_FLAG_BACKUP_SEMANTICS, "ptr", 0, "ptr")
    Return hDir = INVALID_HANDLE_VALUE? 0:hDir      
}

CreateStruct(Members*)
{
    Static Struct
    cMembers := Members.MaxIndex()
    VarSetCapacity(Struct, cMembers * A_PtrSize, 0)
    addr := &Struct
    Loop, %cMembers%
        addr := NumPut(Members[A_Index], addr+0, 0, "ptr")
    Return &Struct
}

GetProcAddress(Lib, Func)
{
    hLib := DllCall("LoadLibrary", "str", Lib, "ptr")
    If (hLib = 0)
        Return 0
    Return DllCall("GetProcAddress", "ptr", hLib, "astr", Func, "ptr")
}

CreateMachineFunc()
{
    MEM_RESERVE := 0x2000, MEM_COMMIT := 0x1000, PAGE_EXECUTE_READWRITE := 0x40
    If (A_PtrSize = 8) {
        Hex = 
        ( Join LTrim
        488B0151FF7140FF7138FF7130FF71284883EC204C8B49204C8B4118488B5110488B4908FFD04
        C8B5424404D31C985C04D0F454A10498B4230448B00498B5258498B4A5041FF52484883C448C3
        )
    }
    Else {
        Hex =
        ( Join LTrim
        8B54240452FF7220FF721CFF7218FF7214FF7210FF720CFF7208FF7204FF125A85C00F4542088
        B4A1850FF31FF722CFF7228FF5224C20400
        )
    }
    Len := StrLen(Hex) // 2
    pFunc := DllCall("VirtualAlloc", "ptr", 0, "ptr", Len
                                   , "uint", MEM_RESERVE | MEM_COMMIT
                                   , "uint", PAGE_EXECUTE_READWRITE, "ptr")
    If (pFunc = 0)
        Return 0
    Loop, % Len
        NumPut("0x" . SubStr(Hex, A_Index * 2 - 1, 2), pFunc + 0
                                 , A_Index - 1, "uchar")
    Return pFunc
}

CreateThread(StartAddr, Param)
{
    Return DllCall("CreateThread", "ptr", 0, "ptr", 0, "ptr", StartAddr
                                 , "ptr", Param, "uint", 0, "ptr", 0, "ptr")
}

Error(Func)
{
    MsgBox, %Func% failed.
    Return False
}

Если кому интересно, вот исходный код машинных функций (на ассемблере GoAsm). Для работы скрипта, разумеется, он не нужен, т.к. он уже там в виде хекс-строк.

+ открыть спойлер

#if x64

    mov rax,[rcx]
    push rcx,[rcx+40h],[rcx+38h],[rcx+30h],[rcx+28h]
    sub rsp,20h
    mov r9,[rcx+20h]
    mov r8,[rcx+18h]
    mov rdx,[rcx+10h]
    mov rcx,[rcx+8h]
    call rax            ; Вызов ReadDirectoryChangeW
    mov r10,[rsp+40h]
    xor r9,r9
    test eax,eax
    cmovnz r9,[r10+10h]
    mov rax,[r10+30h]
    mov r8d,[rax]
    mov rdx,[r10+58h]
    mov rcx,[r10+50h]
    call [r10+48h]      ; Вызов PostMessage
    add rsp,48h
    ret

#else

    mov edx,[esp+4]
    push edx
    push [edx+20h],[edx+1Ch],[edx+18h],[edx+14h],[edx+10h],[edx+0Ch],[edx+8h],[edx+4h]
    call [edx]
    pop edx
    test eax,eax
    cmovnz eax,[edx+8h]
    mov ecx,[edx+18h]
    push eax,[ecx],[edx+2Ch],[edx+28h]
    call [edx+24h]
    ret 4

#endif