====== 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]]