Тема: 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]