60 votes

Identifiant unique de fichier dans Windows

Existe-t-il un moyen d'identifier de manière unique un fichier (et éventuellement des répertoires) pendant toute la durée de vie du fichier, indépendamment des déplacements, des renommages et des modifications du contenu ? (Windows 2000 et ultérieur). Faire une copie d'un fichier devrait donner à la copie son propre identifiant unique.

Mon application associe diverses méta-données à des fichiers individuels. Si les fichiers sont modifiés, renommés ou déplacés, il serait utile de pouvoir détecter et mettre à jour automatiquement les associations de fichiers.

FileSystemWatcher peut fournir des événements qui informent de ces types de changements, mais il utilise un tampon de mémoire qui peut être facilement rempli (et les événements perdus) si de nombreux événements du système de fichiers se produisent rapidement.

Un hachage n'est pas utile car le contenu du fichier peut changer, et donc le hachage changera.

J'avais pensé à utiliser la date de création du fichier, mais il y a quelques situations où cette date ne sera pas unique (par exemple, lorsque plusieurs fichiers sont copiés).

J'ai également entendu parler d'un fichier SID (security ID ?) dans NTFS, mais je ne suis pas sûr que cela fasse ce que je recherche.

Des idées ?

36voto

Ash Points 31541

Voici un exemple de code qui renvoie un index de fichier unique.

ApproachA() est la solution que j'ai trouvée après quelques recherches. ApprocheB() est grâce aux informations dans les liens fournis par Mattias et Rubens. Pour un fichier spécifique, les deux approches retournent le même index de fichier (pendant mes tests de base).

Quelques mises en garde de MSDN :

La prise en charge des ID de fichiers est le fichier est spécifique au système de fichiers. Les ID de fichiers ne sont pas garantis d'être uniques dans le temps, car les systèmes de fichiers sont libres de les réutiliser réutiliser. Dans certains cas, l'ID de fichier d'un fichier peut changer au fil du temps.

Dans le système de fichiers FAT, l'ID du fichier est le suivant généré à partir du premier cluster du du répertoire qui le contient et du décalage dans le répertoire de l'entrée pour le fichier. Certains produits de défragmentation produits de défragmentation modifient ce décalage des octets. (La défragmentation en boîte de Windows ne le fait pas). Ainsi, une FAT peut changer au fil du temps. Renommer un fichier dans le système de fichiers FAT peut également changer l'ID du fichier, mais uniquement si le nouveau nom de fichier est plus long que l'ancien nom de fichier.

Dans le système de fichiers NTFS, un fichier conserve le même ID de fichier jusqu'à ce qu'il soit supprimé . Vous pouvez remplacer un fichier par un autre fichier sans changer l'ID du fichier en en utilisant la fonction ReplaceFile. Cependant, l'ID du fichier de fichier de remplacement, et non du fichier remplacé, est conservé comme identifiant du fichier fichier résultant.

Le premier commentaire en gras ci-dessus m'inquiète. Il n'est pas clair si cette déclaration s'applique uniquement à la FAT, elle semble contredire le deuxième texte en gras. Je suppose que des tests supplémentaires sont la seule façon d'en être sûr.

[Mise à jour : dans mes tests, l'index/identifiant du fichier change lorsqu'un fichier est déplacé d'un disque dur interne NTFS à un autre disque dur interne NTFS].

    public class WinAPI
    {
        [DllImport("ntdll.dll", SetLastError = true)]
        public static extern IntPtr NtQueryInformationFile(IntPtr fileHandle, ref IO_STATUS_BLOCK IoStatusBlock, IntPtr pInfoBlock, uint length, FILE_INFORMATION_CLASS fileInformation);

        public struct IO_STATUS_BLOCK
        {
            uint status;
            ulong information;
        }
        public struct _FILE_INTERNAL_INFORMATION {
          public ulong  IndexNumber;
        } 

        // Abbreviated, there are more values than shown
        public enum FILE_INFORMATION_CLASS
        {
            FileDirectoryInformation = 1,     // 1
            FileFullDirectoryInformation,     // 2
            FileBothDirectoryInformation,     // 3
            FileBasicInformation,         // 4
            FileStandardInformation,      // 5
            FileInternalInformation      // 6
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool GetFileInformationByHandle(IntPtr hFile,out BY_HANDLE_FILE_INFORMATION lpFileInformation);

        public struct BY_HANDLE_FILE_INFORMATION
        {
            public uint FileAttributes;
            public FILETIME CreationTime;
            public FILETIME LastAccessTime;
            public FILETIME LastWriteTime;
            public uint VolumeSerialNumber;
            public uint FileSizeHigh;
            public uint FileSizeLow;
            public uint NumberOfLinks;
            public uint FileIndexHigh;
            public uint FileIndexLow;
        }
  }

  public class Test
  {
       public ulong ApproachA()
       {
                WinAPI.IO_STATUS_BLOCK iostatus=new WinAPI.IO_STATUS_BLOCK();

                WinAPI._FILE_INTERNAL_INFORMATION objectIDInfo = new WinAPI._FILE_INTERNAL_INFORMATION();

                int structSize = Marshal.SizeOf(objectIDInfo);

                FileInfo fi=new FileInfo(@"C:\Temp\testfile.txt");
                FileStream fs=fi.Open(FileMode.Open,FileAccess.Read,FileShare.ReadWrite);

                IntPtr res=WinAPI.NtQueryInformationFile(fs.Handle, ref iostatus, memPtr, (uint)structSize, WinAPI.FILE_INFORMATION_CLASS.FileInternalInformation);

                objectIDInfo = (WinAPI._FILE_INTERNAL_INFORMATION)Marshal.PtrToStructure(memPtr, typeof(WinAPI._FILE_INTERNAL_INFORMATION));

                fs.Close();

                Marshal.FreeHGlobal(memPtr);   

                return objectIDInfo.IndexNumber;

       }

       public ulong ApproachB()
       {
               WinAPI.BY_HANDLE_FILE_INFORMATION objectFileInfo=new WinAPI.BY_HANDLE_FILE_INFORMATION();

                FileInfo fi=new FileInfo(@"C:\Temp\testfile.txt");
                FileStream fs=fi.Open(FileMode.Open,FileAccess.Read,FileShare.ReadWrite);

                WinAPI.GetFileInformationByHandle(fs.Handle, out objectFileInfo);

                fs.Close();

                ulong fileIndex = ((ulong)objectFileInfo.FileIndexHigh << 32) + (ulong)objectFileInfo.FileIndexLow;

                return fileIndex;   
       }
  }

10 votes

Bien, je l'ai essayé, mais j'ai trouvé un problème : il ne fonctionne pas pour les fichiers comme la suite Microsoft Office (doc, docx, xls...) parce que chaque fois que vous avez fait un changement, l'Office a tendance à supprimer le fichier, et de créer un nouveau fichier pour le remplacer, ce résultat dans le numéro de référence changé, bien que le numéro de référence toujours unique. Cela ne peut pas fonctionner pour détecter les changements dans ces fichiers, et peut-être que d'autres programmes auront une approche similaire. Je pense donc que je vais revenir à ma méthode CreationTime...

0 votes

Pour information, je n'ai pas réussi à faire fonctionner l'approche A d'Ashley Henderson, mais l'approche B a fonctionné. J'ai pu confirmer la note de VHanded selon laquelle le FileID change lorsque vous modifiez un document Word (docx), ce qui est dommage car les fichiers Office sont ceux que je voulais suivre.

2 votes

"à chaque fois que vous faites une modification, l'Office a tendance à supprimer le fichier"... bon sang, AutoCAD fait ça aussi. Retour à FileSystemWatcher

28voto

Mattias S Points 2674

Si vous appelez GetFileInformationByHandle vous obtiendrez un ID de fichier dans BY_HANDLE_FILE_INFORMATION.nFileIndexHigh/Low. Cet indice est unique au sein d'un volume, et reste le même même même si vous déplacez le fichier (au sein du volume) ou si vous le renommez.

Si vous pouvez supposer que NTFS est utilisé, vous pouvez également envisager d'utiliser des flux de données alternatifs pour stocker les métadonnées.

0 votes

Merci pour le lien. En fait, j'ai trouvé un autre appel d'API qui renvoie le même ID mais qui demande un peu plus de travail. Je vais peut-être poster le code que j'ai trouvé. Un flux de données alternatif pourrait également être utile, car le seul endroit où je rencontre la FAT est sur les clés USB / disques externes. Cependant, j'ai entendu dire que certains anti-virus / logiciels de sécurité peuvent avoir un problème avec l'ajout de données cachées dans les fichiers.

9 votes

La documentation de GetFileInformationByHandle indique : "nFileIndexLow : Partie d'ordre inférieur d'un identifiant unique associé à un fichier. Cette valeur est utile UNIQUEMENT LORSQUE LE FICHIER EST OUVERT par au moins un processus. Si aucun processus ne l'a ouvert, l'index peut changer lors de la prochaine ouverture du fichier."

0 votes

Hm, cette clause ne semble pas être dans la documentation sur MSDN (plus ?). Empiriquement, je constate que l'index du fichier reste unique à chaque redémarrage (sur un système de fichiers NTFS).

4voto

Rubens Farias Points 33357

Jetez un coup d'œil ici : Identifiants uniques de fichiers pour Windows . Ceci est également utile : ID unique pour les fichiers sur NTFS ?

1voto

user3244487 Points 1

Une chose que vous pouvez utiliser pour les identifiants de fichiers est l'horodatage de création, assurez-vous simplement que lorsque vous scannez les fichiers dans votre programme, vous modifiez les heures de création qui sont les mêmes que celles déjà rencontrées afin qu'elles soient minutieusement différentes, car évidemment, quelques fichiers, même sur NTFS, peuvent avoir le même TS s'ils ont été créés en même temps par une copie de fichiers en masse, mais dans le cours normal des choses, vous n'obtiendrez pas de doublons et les modifications seront rares, voire inexistantes.

0voto

Thomas Points 350

L'utilisateur mentionne également l'identification unique de l'annuaire. Ce processus est un peu plus compliqué que la récupération d'informations uniques pour un fichier, mais il est possible. Pour cela, vous devez appeler le programme approprié CREATE_FILE fonction dont un drapeau particulier. Avec cet identifiant, vous pouvez appeler la fonction GetFileInformationByHandle dans la fonction de Ash réponse .

Cela nécessite également un kernel32.dll l'importation :

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern SafeFileHandle CreateFile(
            string lpFileName,
            [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
            [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
            IntPtr securityAttributes,
            [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile
        );

Je développerai cette réponse un peu plus tard. Mais, avec la réponse ci-dessus, cela devrait commencer à avoir du sens. Une de mes nouvelles ressources préférées est pinvoke qui m'a aidé avec les possibilités de signature de .Net C#.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X