This article is about how to retrieve the owner of a file. If you are experienced with some of the WinAPI security function this can be pretty easy. There are some problems that needs to be addressed though. The first one is the size of the security items like the SID name of the owner. Secondly, there is the possibility that a SID cannot be resolved to a human readable name at all. And thirdly, we need to check all the result values.

WinAPI

uses
  SysUtils,
  //JwaWindows, //Use either JwaWindows or these JEDI headers:
  JwaWinBase,
  JwaWinType,
  JwaWinNT,
  JwaAclApi,
  JwaAccCtrl,
  JwaWinError,
  JwaSddl;

type
  TOwnerResult = (orNone, orName, orSID);

function GetFileOwner(const FileName: string;
  out Domain, Username: String): TOwnerResult;
var
  pSD: PSecurityDescriptor;
  dwOwnerNameSize, dwDomainNameSize: DWORD;
  pOwnerSID: PSID;
  pszOwnerName, pszDomainName: PChar;
  OwnerType: SID_NAME_USE;
  dwError : DWORD;
begin
  result := orNone;

  pSD := nil;
  dwError := GetNamedSecurityInfo(
     PChar(FileName),//__in       LPTSTR pObjectName,
     SE_FILE_OBJECT,//__in       SE_OBJECT_TYPE ObjectType,
     OWNER_SECURITY_INFORMATION,//__in       SECURITY_INFORMATION SecurityInfo,
     @pOwnerSID,//__out_opt  PSID *ppsidOwner,
     nil,//__out_opt  PSID *ppsidGroup,
     nil,//__out_opt  PACL *ppDacl,
     nil,//__out_opt  PACL *ppSacl,
     pSD//__out_opt  PSECURITY_DESCRIPTOR *ppSecurityDescriptor
   );
  if (dwError <> NOERROR) then
  begin
    SetLastError(dwError);
    RaiseLastOSError;
  end;

  //First get necessary memory size for Owner and Domain
  dwOwnerNameSize  := 0;
  dwDomainNameSize := 0;
  if not LookupAccountSID(nil, pOwnerSID, nil,
    dwOwnerNameSize, nil, dwDomainNameSize, OwnerType) then
  begin
    //Check for SIDs that are unknown on this local system
    if (GetLastError() = ERROR_NONE_MAPPED) then
    begin
      Win32Check(ConvertSidToStringSid(pOwnerSID, pszOwnerName));
      Domain := '';
      Username := pszOwnerName;
      LocalFree(HLOCAL(pszOwnerName));
      result := orSID;
      exit;
    end
    else
    //Any other error exits the function
    if (GetLastError() <> ERROR_INSUFFICIENT_BUFFER) then
      RaiseLastOsError;
  end;

  //Allocate memory for owner and domain
  //Take into account the size of a char type (like WideCHAR)
  GetMem(pszOwnerName, dwOwnerNameSize*sizeof(CHAR));
  try
    GetMem(pszDomainName, dwDomainNameSize*sizeof(CHAR));
    try
      //Retrieve name and domain
      Win32Check(LookupAccountSID(nil, pOwnerSID, pszOwnerName,
        dwOwnerNameSize, pszDomainName, dwDomainNameSize, OwnerType));

      result := orName;

      Domain   := pszDomainName;
      Username := pszOwnerName;
    finally
      FreeMem(pszDomainName);
    end;
  finally
    FreeMem(pszOwnerName);
  end;
end;

The function behaves the following:

  • If an WinAPI error occurs the exception EOSError is thrown. You can retrieve the error value from its property Errorcode.
  • If the user name could be retrieved the result value is orName and both parameter Domain and UserName contain a value. Otherwise the result is orSID and only UserName contains the User Security Identifier (e.g. S-1-5-21-564352346-2735467565-567745764-1001).

JWSCL

Of course, JWSCL wraps these function calls already. Thus the source size decreases rapidly. See another example here.
In JWSCL it is quite different to implement the function. However the behaviour is equal:

function JwsclGetFileOwner(const FileName: string;
  out Domain, Username: String): TOwnerResult;
var
  F : TJwSecureFileObject;
  Owner : TJwSecurityId;
begin
  F := TJwSecureFileObject.Create(FileName);
  try
    Owner := F.Owner; //Owner is cached/freed by TJwSecureFileObject
    try
      Domain := Owner.GetAccountDomainName();
      Username := Owner.GetAccountName();
      Result := orName;
    except
      on E : EJwsclWinCallFailedException do
      begin
        if E.LastError = ERROR_NONE_MAPPED then
        begin
          Domain := '';
          Username := Owner.StringSID;
          Result := orSID;
        end
        else
          raise;
      end;
    end;
  finally
    F.Free;
  end;
end;

In a next version of JWSCL (currently trunk) the exception handling will be a little bit different:

function JwsclGetFileOwner2(const FileName: string;
  out Domain, Username: String): TOwnerResult;
var
  F : TJwSecureFileObject;
  Owner : TJwSecurityId;
begin
  F := TJwSecureFileObject.Create(FileName);
  try
    Owner := F.Owner; //Owner is cached/freed by TJwSecureFileObject
    try
      Domain := Owner.GetAccountDomainName();
      Username := Owner.GetAccountName();
      Result := orName;
    except
      on E : EJwsclSidNotMappedException do //not mapped error is handled differently
      begin
        Domain := '';
        Username := Owner.StringSID;
        Result := orSID;
      end;
    end;
  finally
    F.Free;
  end;
end;

If you use JWSCL (Version >= 0.9.2a) and enable the compiler switch JWSCL_SIDCACHE in file Include\Jwscl.inc, JWSCL will add a SID cache (for all TJwSecurityID instances globally). In this way LookupAccountSid will be called only once for every unknown SID and thus SIDs that were already translated are retrieved from cache instead.