====== Surveiller modification Fichier/Répertoire ====== ===== Pistes de recherches ===== ==== FindFirstChangeNotification ==== Cette méthode est documentée ici -> [[https://docs.microsoft.com/fr-fr/windows/win32/fileio/obtaining-directory-change-notifications|Obtention de notifications de modification de répertoire]] Apparemment cette méthode permet de savoir si un dossier a été modifier, mais pas de connaitre les modification apportées. Quel fichier modifié ? Quel fichier ajouté ou supprimé ? === Comment ça marche ? === Pour faire simple : On récupere les Handle des répértoires a surveiller avec [[https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstchangenotificationa|FindFirstChangeNotification]] function FindFirstChangeNotification(lpPathName:LPCSTR; bWatchSubtree:WINBOOL; dwNotifyFilter:DWORD):HANDLE; * ''lpPathname'' = PChar('C:\MonRepertoire\Etc\') * ''bWatchSubtree'' = boolean -> on surveille juste le répertoire (False) ou le répertoire et ses enfants ? (True) * ''dwNotifyFilter'' = dWord -> Changement que l'on veut capturer (Filename, Dirname, Attributes, Size...) [[https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstchangenotificationa#parameters|plus d'infos ici]] On met ces Handle dans un array de ce type : (par exemple) THandleList = array [0..MAXIMUM_WAIT_OBJECTS - 1] of THandle; Ensuite on attends une modification avec [[https://docs.microsoft.com/fr-fr/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects|WaitForMultipleObjects]]: function WaitForMultipleObjects( nCount:DWORD; lpHandles : PWOHandleArray; bWaitAll:WINBOOL; dwMilliseconds:DWORD):DWORD * ''nCount'' = nombre de handle dans la liste * ''lpHandles'' = pointer vers le premier enregistrement de la liste de type ''THandleList'' (voir ci-dessus) (dans notre cas=''@HandleList[0]'') * ''bWaitAll'' = Boolean -> On attend toutes les modifs ??? (''False'' dans notre cas) * ''dwMilliseconds'' = Temps d'attente en Millisecondes La fonction '' WaitForMultipleObjects'' retourne un DWord qui peut être interprété de la façon suivante: * $102 (258) = TIMEOUT, le temps d'attente (''dwMilliseconds'') s'est écoulé. * $FFFFFFFF (-1) = FAILED ! * $0..$40 = Index (+WAIT_OBJECT_0) du Handle dans la liste correspondant au répertoire qui a été modifié * $80..$C0 = Index (+WAIT_ABANDONED_0) du Handle qui... je sais pas trop Plus d'infos -> [[https://docs.microsoft.com/fr-fr/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects#return-value|Valeur de retour de la fonction WaitForMultipleObjects]] On traite la valeur retournée par la fonction '' WaitForMultipleObjects''.\\ Puis on retourne a la surveillance par un [[https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextchangenotification| FindNextChangeNotification]] et rebouclage sur [[https://docs.microsoft.com/fr-fr/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects|WaitForMultipleObjects]]: function FindNextChangeNotification(hChangeHandle:HANDLE):WINBOOL; ou le parametre ''hChangeHandle'' est le Handle du répertoire modifié precedement.\\ Ce Handle est récuperable dans la liste des Handles THandleList passé a la fonction ''WaitForMultipleObjects'' a l'index retourné par cette méme fonction... (suis-je bien claire ???) Exemple de code (Dans un Thread, car ''WaitForMultipleObjects'' est une fonction bloquante...) : // vIndex: integer; // vHandleList : THandleList (voir ci-dessus) // FCount = nombre de handle dans la liste vHandleList // FWaitMs = nombre de millisecondes a attendre // FDirChangedHandle = Handle du Find Change Notifier du répertoire qui a été modifié // Le Handle du Find Change Notifier est retourné par la function FindFirstChangeNotification (voir ci-dessus) while not Terminated do begin vIndex := WaitForMultipleObjects(FCount, vHandleList[0], False, FWaitMs); if (vIndex >= WAIT_OBJECT_0) and (vIndex < WAIT_OBJECT_0 + FNHandleList.Count) then begin FDirChangedHandle :=FHandleList[vIndex - WAIT_OBJECT_0]; ... FindNextChangeNotification( FDirChangedHandle); end; end; ==== ReadDirectoryChangesW ==== === Exemple de code === Voici un exemple de code pour l'utilisation de ''ReadDirectoryChangesW'': Documentation utile pour mieux comprendre ce code : * [[prog:lazarus:cas:files:createfile|Documentation sur la function CreateFile]] * [[https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw|Documentation sur la function ReadDirectoryChangesW]] * [[https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getoverlappedresult|Documentation sur la function GetOverlappedResult]] * [[https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-file_notify_information|Documentation sur le type FILE_NOTIFY_INFORMATION]] program DirMonDOS; {$mode objfpc}{$H+} uses {$IFDEF UNIX} {$IFDEF UseCThreads} cthreads, {$ENDIF} {$ENDIF} Classes, crt, JwaWindows, SysUtils; var FPathName: string; FDirHandle: THandle; FBuffer: array[0..9999] of byte; FBytesReturned: dword; FOverLapped: TOverlapped; FOverResult: boolean; FFileNotifyInformation: PFileNotifyInformation; FOffset: dword; FAction: dword; FActionStr: string; FFilename: string; FLen: dword; FTerminate: boolean; FLastError: dword; FNotifyFilter: DWord; FLooping: boolean; begin { Répertoire à surveiller } FPathName := 'J:\'; { Initialisation des variables } FTerminate := False; FLooping := False; ZeroMemory(@FBuffer, sizeof(FBuffer)); FNotifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME or FILE_NOTIFY_CHANGE_LAST_WRITE; { FILE_NOTIFY_CHANGE_FILE_NAME = 1; FILE_NOTIFY_CHANGE_DIR_NAME = 2; FILE_NOTIFY_CHANGE_ATTRIBUTES = 4; FILE_NOTIFY_CHANGE_SIZE = 8; FILE_NOTIFY_CHANGE_LAST_WRITE = 16; FILE_NOTIFY_CHANGE_LAST_ACCESS = 32; FILE_NOTIFY_CHANGE_CREATION = 64; FILE_NOTIFY_CHANGE_SECURITY = 256; } writeln('Press [s] to exit'); WriteLn('Surveillance du dossier [', FPathName, ']'); { Récuperation du Handle du Répertoire a surveiller } FDirHandle := CreateFile(PChar(FPathName), FILE_LIST_DIRECTORY, FILE_SHARE_READ or FILE_SHARE_DELETE or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0); WriteLn('Handle du dossier [', FDirHandle, ']'); if FDirHandle <> INVALID_HANDLE_VALUE then begin try { Bouclage jusqu'a ce que Terminate=True } repeat { Mise en place de la surveillance } if ReadDirectoryChangesW(FDirHandle, @FBuffer, sizeOf(FBuffer), True, FNotifyFilter, @FBytesReturned, @FOverLapped, nil) then begin { Bouclage jusqu'a ce que FOverResult=True } repeat { Si FOverResult = True, cela signifie qu'il y a eu des modifications } FOverResult := GetOverlappedResult(FDirHandle, FOverLapped, FBytesReturned, False); { Je capture GetLastError ci-dessous, car il peut etre modifié par d'autres instructions, comme un WriteLn par exemple } FLastError := GetLastError; { Affichage des informations pour faire beau } if not FOverResult and not FLooping then begin Writeln('Looping...'); FLooping := True; end; sleep(100); { Verification si on a pressé la touche [S] pour arreter le processus } if KeyPressed and (ReadKey = 's') then FTerminate := True; until FOverResult or (FLastError <> ERROR_IO_INCOMPLETE) or FTerminate; FLooping := False; if FOverResult then begin Writeln('--- begin modification ---'); { Positionnement sur le debut du Buffer } pointer(FFileNotifyInformation) := @FBuffer[0]; repeat FOffset := FFileNotifyInformation^.NextEntryOffset; FAction := FFileNotifyInformation^.Action; { FLen = FileNameLength/2 car FileName est codé en WideString et WideString est 2x plus long que AnsiString ->https://wiki.freepascal.org/Character_and_string_types/fr } FLen := FFileNotifyInformation^.FileNameLength div 2; WideCharLenToStrVar(@(FFileNotifyInformation^.FileName), FLen, FFilename); case FAction of FILE_ACTION_ADDED: FActionStr := 'Created'; FILE_ACTION_REMOVED: FActionStr := 'Deleted'; FILE_ACTION_MODIFIED: FActionStr := 'Modified'; FILE_ACTION_RENAMED_OLD_NAME: FActionStr := 'RenamedFrom'; FILE_ACTION_RENAMED_NEW_NAME: FActionStr := 'RenamedTo'; else FActionStr := '???'; end; { Affichage du résultat } WriteLn('Action [', FAction, '|', FActionStr, '] len[', FLen, '] sur fichier [', FFilename, ']'); { Positionnement sur le prochain FFileNotifyInformation } pointer(FFileNotifyInformation) := pointer(FFileNotifyInformation) + FOffset; until FOffset = 0; { Nettoyage du Buffer pour eviter d'accumuler des parasites surtout dans Filename } ZeroMemory(@FBuffer, sizeof(FBuffer)); Writeln('--- end modification ---'); end; end; until FTerminate; finally CloseHandle(FDirHandle); end; end else Writeln('Dir Handle invalid'); Writeln('Press to exit'); Readln; end. === Ressources pour ReadDirectoryChangesW === * [[https://www.osnews.com/story/7376/a-directory-monitor-class-for-delphi/]] * [[https://www.experts-exchange.com/questions/24440582/Using-ReadDirectoryChangesW-in-Delphi.html]] * [[https://nono40.developpez.com/sources/source0045/]] ==== A la Procmon ==== [[softs:sysinternals:procmon|Procmon]] est un excellent utilitaire qui montre en temps réel les accès aux fichiers par les processus.\\ Reste a savoir comment il fait ? Apparemment cette solution impliquerai l'utilisation d'un drivers annexe, ce qui reste relativement fastidieux... === Piste OpenFilesView de Nirsoft === Une piste peut etre sur la page->[[https://www.nirsoft.net/utils/opened_files_view.html]].\\ Un programme de Nirsoft apparement similaire a Procmon qui fonctionne de la facon suivante: >[[https://www.nirsoft.net/utils/opened_files_view.html|OpenedFilesView]] utilise l'API [[https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation|NtQuerySystemInformation]] pour énumérer tous les handles dans le système. >Après avoir filtré les handles non-fichiers, il utilise un driver temporaire - NirSoftOpenedFilesDriver.sys pour lire les informations sur chaque handle depuis la mémoire du noyau. >Ce driver est automatiquement déchargé du système lorsque vous quittez l'utilitaire OpenedFilesView. === Projet de ShareVB === Sur cette page : [[https://codes-sources.commentcamarche.net/source/36281-savoir-quel-fichier-est-utilise-par-telle-application-la-liste-des-fichiers-ouverts-sur-le-systeme-pour-2k-xp-2k3|Savoir quel fichier est utilisé par telle application : la liste des fichiers ouverts sur le système (pour 2k/xp/2k3)]] [[https://codes-sources.commentcamarche.net/profile/user/ShareVB|ShareVB]] partage un projet en VB qui ferait apparemment le taf... a étudier. Je met ce projet sur mon site, on sait jamais dés fois qu'il disparaisse !!! {{ :prog:lazarus:cas:files:36281-1110458-savoir-quel-fichier-est-utilise-par-telle-application-la-liste-des-fichiers-ouverts-sur-le-systeme-pour-2k-xp-2k3.zip |}} ==== En vrac à étudier==== * [[https://stackoverflow.com/questions/8726906/delphi-finding-the-process-that-is-accessing-a-file-from-my-program|Delphi - finding the process that is accessing a file from my program]] ====== Sources & Ressources ====== * [[https://docs.microsoft.com/fr-fr/windows/win32/fileio/obtaining-directory-change-notifications]] * Voir aussi -> JvChangeNotify.TJvChangeNotify class in JCL. * Voir aussi -> [[prog:lazarus:cas:disks:disks]]