Using GUIDs to identify a file

I had an interesting read over at the “Old new thing” website. Discussing if it’s possible to open a file by its id rather then using its full qualified path and filename. You can find the original article here: Raymond Chen

 

 

Background

On NTFS every single file on a volume has a object identifier which is a 16-byte buffer. If a file doesn’t have an object identifier you can ask for one to be created for this you need to make use of FSCTL_CREATE_OR_GET_OBJECT_ID.

Screenshot retrieving the object identifier

filebyguid screenshot

OpenFileByID

 

 

 

 

 

As you may noticed these are IOCTL Codes used to send a control code directly to a specified device driver, causing the corresponding device to perform the corresponding operation.

DeviceIoControl Function

BOOL WINAPI DeviceIoControl(
  __in         HANDLE hDevice,
  __in         DWORD dwIoControlCode,
  __in_opt     LPVOID lpInBuffer,
  __in         DWORD nInBufferSize,
  __out_opt    LPVOID lpOutBuffer,
  __in         DWORD nOutBufferSize,
  __out_opt    LPDWORD lpBytesReturned,
  __inout_opt  LPOVERLAPPED lpOverlapped
);

 

IOCTL Codes to be used with the object identifier
IOCTL CodeDescription
FSCTL_CREATE_OR_GET_OBJECT_IDRetrieve the existing object identifier associated with a file, or create one if there isn't one already.
FSCTL_SET_OBJECT_IDSpecifies the GUID you want to use as the object identifier. The call fails if the file already has an object identifier.
FSCTL_GET_OBJECT_IDRetrieves the object identifier, if any.

Original code which we need to translate to a Delphi equivalent

#define UNICODE
#define _UNICODE
#include
#include
#include
#include
#include 

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HANDLE h = CreateFile(argv[1], 0,
                 FILE_SHARE_READ | FILE_SHARE_WRITE |
                 FILE_SHARE_DELETE, NULL,
                 OPEN_EXISTING, 0, NULL);
 if (h != INVALID_HANDLE_VALUE) {
  FILE_OBJECTID_BUFFER buf;
  DWORD cbOut;
  if (DeviceIoControl(h, FSCTL_CREATE_OR_GET_OBJECT_ID,
                 NULL, 0, &buf, sizeof(buf),
                 &cbOut, NULL)) {
    GUID guid;
    CopyMemory(&guid, &buf.ObjectId, sizeof(GUID));
    WCHAR szGuid[39];
    StringFromGUID2(guid, szGuid, 39);
    _tprintf(_T("GUID is %ws\n"), szGuid);
  }
  CloseHandle(h);
 }
 return 0;
}

This program takes as parameter an directory or filename and prints out the object identifier.

2nd Part using the received GUID to open the file

#define UNICODE
#define _UNICODE
#include
#include
#include
#include 

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HANDLE hRoot = CreateFile(_T("C:\\"), 0,
                 FILE_SHARE_READ | FILE_SHARE_WRITE |
                 FILE_SHARE_DELETE, NULL,
                 OPEN_EXISTING,
                 FILE_FLAG_BACKUP_SEMANTICS, NULL);
 if (hRoot != INVALID_HANDLE_VALUE) {
  FILE_ID_DESCRIPTOR desc;
  desc.dwSize = sizeof(desc);
  desc.Type = ObjectIdType;
  if (SUCCEEDED(CLSIDFromString(argv[1], &desc.ObjectId))) {
   HANDLE h = OpenFileById(hRoot, &desc, GENERIC_READ,
                 FILE_SHARE_READ | FILE_SHARE_WRITE |
                 FILE_SHARE_DELETE, NULL, 0);
   if (h != INVALID_HANDLE_VALUE) {
    BYTE b;
    DWORD cb;
    if (ReadFile(h, &b, 1, &cb, NULL)) {
     _tprintf(_T("First byte of file is 0x%02x\n"), b);
    }
    CloseHandle(h);
   }
  }
  CloseHandle(hRoot);
 }
 return 0;
}

Well, making things work you first need to open something on the volume the file resides on. This is just needed to let the OpenFileById function knows on what volume we are working. In the example above Mr Raymond Chen is working on the C: drive which means that the file search will take place on the C: drive.

 

The Delphi equivalent

As you can see from the two sample programs above we need to convert first a few types into Delphi bevor we can start writing our demo there are:

 

FILE_ID_TYPE

Discriminator for the union in the FILE_ID_DESCRIPTOR structure.

type
  FILE_ID_TYPE = (
      FileIdType,
      ObjectIdType,
      MaximumFileIdType
      );
  {$EXTERNALSYM FILE_ID_TYPE}

 

FILE_ID_DESCRIPTOR

Specifies the type of ID that is being used.

type
  _File_ID_DESCRIPTOR = record
    dwSize: DWORD;
    cType: FILE_ID_TYPE;
    case integer of
      0:
        (FileId: LARGE_INTEGER);
      1:
        (ObjectID: TGUID);
  end;
   FILE_ID_DESCRIPTOR = _File_ID_DESCRIPTOR;
   {$EXTERNALSYM FILE_ID_DESCRIPTOR}
   LPFILE_ID_DESCRIPTOR = ^_File_ID_DESCRIPTOR;
   {$EXTERNALSYM LPFILE_ID_DESCRIPTOR}
   TFILE_ID_DESCRIPTOR = _File_ID_DESCRIPTOR;
   {$EXTERNALSYM TFILE_ID_DESCRIPTOR}

 

FILE_NAME_INFO

Receives the file name. Used for any handles. Use only when calling GetFileInformationByHandleEx.

type
  _FILE_NAME_INFO = record
	FileNameLength : DWORD;
	FileName : array[0..Max_Path] of WCHAR;
	end;
	 FILE_NAME_INFO = _FILE_NAME_INFO;
	 {$EXTERNALSYM FILE_NAME_INFO}
	 PFILE_NAME_INFO = ^_FILE_NAME_INFO;
	 {$EXTERNALSYM PFILE_NAME_INFO}
     TFILE_NAME_INFO = _FILE_NAME_INFO;
     {$EXTERNALSYM TFILE_NAME_INFO}

 

FILE_INFO_BY_HANDLE_CLASS

Identifies the type of file information that GetFileInformationByHandleExshould retrieve or SetFileInformationByHandle should set.

type
_FILE_INFO_BY_HANDLE_CLASS = (
   FileBasicInfo = 0,
   FileStandardInfo = 1,
   FileNameInfo = 2,
   FileRenameInfo = 3,
   FileDispositionInfo = 4,
   FileAllocationInfo = 5,
   FileEndOfFileInfo = 6,
   FileStreamInfo = 7,
   FileCompressionInfo = 8,
   FileAttributeTagInfo = 9,
   FileIdBothDirectoryInfo = 10,
   FileIdBothDirectoryRestartInfo = 11,
   FileIoPriorityHintInfo = 12,
   FileRemoteProtocolInfo = 13,
   MaximumFileInfoByHandlesClass = 14
  );

  FILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS;
  {$EXTERNALSYM FILE_INFO_BY_HANDLE_CLASS}
  PFILE_INFO_BY_HANDLE_CLASS = ^_FILE_INFO_BY_HANDLE_CLASS;
  {$EXTERNALSYM PFILE_INFO_BY_HANDLE_CLASS}
  TFILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS;
  {$EXTERNALSYM TFILE_INFO_BY_HANDLE_CLASS}

 

Full source of the Delphi demo program

unit uMain;

interface

uses
  Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ActiveX,

  JwaWindows, ExtCtrls;

type
  FILE_ID_TYPE = (
      FileIdType,
      ObjectIdType,
      MaximumFileIdType
      );
  {$EXTERNALSYM FILE_ID_TYPE}

type
  _File_ID_DESCRIPTOR = record
    dwSize: DWORD;
    cType: FILE_ID_TYPE;
    case integer of
      0:
        (FileId: LARGE_INTEGER);
      1:
        (ObjectID: TGUID);
  end;
   FILE_ID_DESCRIPTOR = _File_ID_DESCRIPTOR;
   {$EXTERNALSYM FILE_ID_DESCRIPTOR}
   LPFILE_ID_DESCRIPTOR = ^_File_ID_DESCRIPTOR;
   {$EXTERNALSYM LPFILE_ID_DESCRIPTOR}
   TFILE_ID_DESCRIPTOR = _File_ID_DESCRIPTOR;
   {$EXTERNALSYM TFILE_ID_DESCRIPTOR}

type
  _FILE_NAME_INFO = record
	FileNameLength : DWORD;
	FileName : array[0..Max_Path] of WCHAR;
	end;
	 FILE_NAME_INFO = _FILE_NAME_INFO;
	 {$EXTERNALSYM FILE_NAME_INFO}
	 PFILE_NAME_INFO = ^_FILE_NAME_INFO;
	 {$EXTERNALSYM PFILE_NAME_INFO}
     TFILE_NAME_INFO = _FILE_NAME_INFO;
     {$EXTERNALSYM TFILE_NAME_INFO}

type
_FILE_INFO_BY_HANDLE_CLASS = (
   FileBasicInfo = 0,
   FileStandardInfo = 1,
   FileNameInfo = 2,
   FileRenameInfo = 3,
   FileDispositionInfo = 4,
   FileAllocationInfo = 5,
   FileEndOfFileInfo = 6,
   FileStreamInfo = 7,
   FileCompressionInfo = 8,
   FileAttributeTagInfo = 9,
   FileIdBothDirectoryInfo = 10,
   FileIdBothDirectoryRestartInfo = 11,
   FileIoPriorityHintInfo = 12,
   FileRemoteProtocolInfo = 13,
   MaximumFileInfoByHandlesClass = 14
  );

  FILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS;
  {$EXTERNALSYM FILE_INFO_BY_HANDLE_CLASS}
  PFILE_INFO_BY_HANDLE_CLASS = ^_FILE_INFO_BY_HANDLE_CLASS;
  {$EXTERNALSYM PFILE_INFO_BY_HANDLE_CLASS}
  TFILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS;
  {$EXTERNALSYM TFILE_INFO_BY_HANDLE_CLASS}

type
  TMainForm = class(TForm)
    FileOpenDialog1: TFileOpenDialog;
    EdtFileName: TEdit;
    EdtFileGuid: TEdit;
    LblFilename: TLabel;
    LblFileGuid: TLabel;
    BtnOpnFile: TButton;
    Button1: TButton;
    btnCreateGUID: TButton;
    edtFileInfo: TEdit;
    lblFileInfo: TLabel;
    Info: TImage;
    procedure BtnOpnFileClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnCreateGUIDClick(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    function FilenameToGUIDStr(const AFilename: String): Boolean;
    function PrintGUID(AGUID: TGUID): string;
    function OpenFilenameFromGUID(const ARoot: string;
      ACLSIDStr: string): Boolean;
  end;

var
  MainForm: TMainForm;

function OpenFileByID(hFile: THandle; lpFileID: LPFILE_ID_DESCRIPTOR;
  dwDesiredAccess: DWORD; dwShareMode: DWORD;
  lpSecurityAttributes: PSecurityAttributes; dwFlags: DWORD): THandle;
  stdcall; external kernel32 name 'OpenFileById';
  {$EXTERNALSYM OpenFileByID}

function GetFileInformationByHandleEx(hFile: THandle;
  FileInformationClass: FILE_INFO_BY_HANDLE_CLASS;
  lpFileInformation: LPVOID;
  dwBufferSize: DWORD): BOOL; stdcall; external kernel32 name 'GetFileInformationByHandleEx';
  {$EXTERNALSYM GetFileInformationByHandleEx}

implementation

{$R *.dfm}

procedure TMainForm.BtnOpnFileClick(Sender: TObject);
begin
  if not(FileOpenDialog1.Execute) then
    Exit;

  FilenameToGUIDStr(FileOpenDialog1.FileName);
end;

function TMainForm.PrintGUID(AGUID: TGUID): string;
begin
  Result := GUIDToString(AGUID);
end;

procedure TMainForm.Button1Click(Sender: TObject);
begin
  OpenFilenameFromGUID('C:\', EdtFileGuid.Text);
end;

function TMainForm.FilenameToGUIDStr(const AFilename: String): Boolean;
var
  h: THandle;
  buf: FILE_OBJECTID_BUFFER;
  lpOutBuffer: PDWORD;
  GUID: TGUID;
begin
  Result := False;
  try
    h := CreateFile(PChar(AFilename), 0,
      FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil,
      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

    if not(h = INVALID_HANDLE_VALUE) then

    if (DeviceIoControl(h, FSCTL_CREATE_OR_GET_OBJECT_ID
        { FSCTL_GET_OBJECT_ID }
        { FSCTL_SET_OBJECT_ID } , nil, 0, @buf, sizeof(buf), @lpOutBuffer,
        nil)) then

    CopyMemory(@GUID, @buf.ObjectID, sizeof(GUID));
    EdtFileGuid.Text := PrintGUID(GUID);
    EdtFileName.Text := AFilename;

  except
    RaiseLastOSError;
  end;

   CloseHandle(h);
   Result := True;
end;

function TMainForm.OpenFilenameFromGUID(const ARoot: string;
  ACLSIDStr: string): Boolean;
var
  hRoot: THandle;
  h: THandle;
  desc: File_ID_DESCRIPTOR;
  pFilenameInfoClass : PFILE_NAME_INFO;
  dwSize: DWORD;
begin
  Result := False;
  hRoot := CreateFile(PChar(ARoot), 0,
    FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil,
    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);

  if (hRoot <> INVALID_HANDLE_VALUE) then
  begin
    desc.dwSize := sizeof(desc);
    desc.cType := ObjectIdType;

    if (SUCCEEDED(CLSIDFromString(PChar(ACLSIDStr), desc.ObjectID))) then
      try
        h := OpenFileByID(hRoot, @desc, GENERIC_READ,
          FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, 0);

        if (h <> INVALID_HANDLE_VALUE) then
        begin
          dwSize := sizeof(TFILE_NAME_INFO) + sizeof(WCHAR) * $8000;
          pFilenameInfoClass := AllocMem(dwSize);

          if Assigned(pFilenameInfoClass) then
          begin
            if not GetFileInformationByHandleEx(h, FileNameInfo,
              pFilenameInfoClass, dwSize) then
              RaiseLastOSError;
          end;
          CloseHandle(h);
          Result := True;
          edtFileInfo.Text := pFilenameInfoClass.FileName;

          // do whatever you need with your file here...
        end;
      except
        CloseHandle(hRoot);
        RaiseLastOSError;
      end;
    CloseHandle(hRoot);
    FreeMem(pFilenameInfoClass, sizeof(pFilenameInfoClass));
  end;
end;

end.

 

References

Raymond Chen original author of this article.

Microsoft OpenFileByID

  • http://www.remkoweijnen.nl Remko

    Hey, cool you worked it out and published it!
    Good job, keep it up!!!

    • http://private-storm.de stOrM!

      Thank you Remko for stopping by and leaving a comment.