The Road to Delphi

Delphi – Free Pascal – Oxygene

How get and parse a manifest of an external application using delphi

Leave a comment

A manifest is basically a XML file that contains settings that informs Windows how to handle a program when it is started.  The manifest can be embedded inside the program file (as a resource) or it can be located in a separate external XML file. In this article I will show how you can read a embedded windows application manifest from a exe using delphi and parse the information contained using XPath.

The manifest are full of rich information which you can use to determine for example the Requested Execution Levels  or the version of the comctl32.dll used by an application.

To read the manifest from a exe file you must use the LoadLibraryEx function with the LOAD_LIBRARY_AS_DATAFILE flag (or since windows vista you can use the LOAD_LIBRARY_AS_IMAGE_RESOURCE value instead) and the TResourceStream class.

Check this sample code which returns the manifest from a exe file as a string;

function  GetManifest(const FileName:string) : AnsiString;
var
  hModule  : THandle;
  Resource : TResourceStream;
begin
  Result:='';
  //load the file to read
  hModule:=LoadLibraryEx(PChar(FileName),0,LOAD_LIBRARY_AS_DATAFILE);
  try
     if hModule=0 then RaiseLastOSError;
     //check if exist the manifest inside of the file
     if FindResource(hModule, MakeIntResource(1), RT_MANIFEST)<>0 then
     begin
       //load the resource
       Resource:=TResourceStream.CreateFromID(hModule,1,RT_MANIFEST);
       try
         SetString(Result, PAnsiChar(Resource.Memory),Resource.Size);
       finally
         Resource.Free;
       end;
     end;
  finally
      FreeLibrary(hModule);
  end;
end;

Ok that was the easy part, now before to parse the xml check a sample manifest file generated by Delphi XE

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
    type="win32"
    name="CodeGear RAD Studio"
    version="15.0.3890.34076" 
    processorArchitecture="*"/>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        publicKeyToken="6595b64144ccf1df"
        language="*"
        processorArchitecture="*"/>
    </dependentAssembly>
  </dependency>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel
          level="asInvoker"
          uiAccess="false"/>
        </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

As you can see exist two XML namespaces (urn:schemas-microsoft-com:asm.v1 and urn:schemas-microsoft-com:asm.v3) inside of the xml file, before to read the xml string using XPath you must consider these two namespaces.

See this code which deal with the xml and the namespaces

//the namespaces used
const
 assembly_namespace_V1='urn:schemas-microsoft-com:asm.v1';
 assembly_namespace_V2='urn:schemas-microsoft-com:asm.v2';
 assembly_namespace_V3='urn:schemas-microsoft-com:asm.v3';
var
  XmlDoc : OleVariant;
  ns     : string;
  Node   : OleVariant;
begin
  if Trim(FManifest)='' then exit;
  //create a Xml Dom instance
  XmlDoc       := CreateOleObject('Msxml2.DOMDocument.6.0');
  XmlDoc.Async := False;
  try
    //load the Xml string
    XmlDoc.LoadXML(FManifest);
    XmlDoc.SetProperty('SelectionLanguage','XPath');

    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);

    //set the namespaces alias
    ns := Format('xmlns:a=%s xmlns:b=%s xmlns:c=%s',[QuotedStr(assembly_namespace_V1),QuotedStr(assembly_namespace_V2),QuotedStr(assembly_namespace_V3)]);
    XmlDoc.setProperty('SelectionNamespaces', ns);

    //get the version of the manifest
    Node:=XmlDoc.selectSingleNode('/a:assembly/@manifestVersion');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    FManifestVersion:=Node.text;

    //parsing then Assembly Identity
    Node:=XmlDoc.selectSingleNode('/a:assembly/a:assemblyIdentity');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FMainAssemblyIdentity.&type   :=Node.getAttribute('type');
      FMainAssemblyIdentity.name    :=Node.getAttribute('name');
      FMainAssemblyIdentity.language:=VarNullToStr(Node.getAttribute('language'));
      FMainAssemblyIdentity.version :=Node.getAttribute('version');
      FMainAssemblyIdentity.processorArchitecture:=VarNullToStr(Node.getAttribute('processorArchitecture'));
      FMainAssemblyIdentity.publicKeyToken       :=VarNullToStr(Node.getAttribute('publicKeyToken'));
    end;

    Node:=XmlDoc.selectSingleNode('/a:assembly/a:dependency/a:dependentAssembly/a:assemblyIdentity');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FDependentAssembly.&type   :=Node.getAttribute('type');
      FDependentAssembly.name    :=Node.getAttribute('name');
      FDependentAssembly.language:=VarNullToStr(Node.getAttribute('language'));
      FDependentAssembly.version :=Node.getAttribute('version');
      FDependentAssembly.processorArchitecture:=VarNullToStr(Node.getAttribute('processorArchitecture'));
      FDependentAssembly.publicKeyToken       :=VarNullToStr(Node.getAttribute('publicKeyToken'));
    end;

    //Now the tricky part. The requestedExecutionLevel can be located in one of these namespaces
    //urn:schemas-microsoft-com:asm.v2 or urn:schemas-microsoft-com:asm.v3
    Node:=XmlDoc.selectSingleNode('/a:assembly/b:trustInfo/b:security/b:requestedPrivileges/b:requestedExecutionLevel');
    //if not found the requestedExecutionLevel then
    if VarIsNull(Node) or VarIsClear(Node) then
    //try with the next namespace
      Node:=XmlDoc.selectSingleNode('/a:assembly/c:trustInfo/c:security/c:requestedPrivileges/c:requestedExecutionLevel');
    //contains data?
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FRequestedExecutionLevel.level   :=Node.getAttribute('level');
      FRequestedExecutionLevel.uiAccess:=VarNullToStr(Node.getAttribute('uiAccess'));
    end;

  finally
    XmlDoc:=Unassigned;
  end;
end;

Finally check this class to read the content of an Manifest embedded in exe file.

{$APPTYPE CONSOLE}

uses
  ActiveX,
  Classes,
  Windows,
  Variants,
  ComObj,
  StrUtils,
  SysUtils;

type
  TAssemblyIdentity=record
    &type : string;
    name	: string;
    language: string;
    processorArchitecture	: string;
    version	: string;
    publicKeyToken: string;
  end;

  TRequestedExecutionLevel=record
    level    : string;
    uiAccess : string;
  end;

  TManifiestReader=class
  private
    FFileName: string;
    FManifest: AnsiString;
    FMainAssemblyIdentity: TAssemblyIdentity;
    FHasManifest: Boolean;
    FDependentAssembly: TAssemblyIdentity;
    FManifestVersion: string;
    FRequestedExecutionLevel: TRequestedExecutionLevel;
    procedure GetManifest;
    procedure LoadManifestData;
    function  VarNullToStr(Value:OleVariant):string;
  public
    property FileName : string read FFileName;
    property Manifest : AnsiString read FManifest;
    property ManifestVersion : string read FManifestVersion;
    property MainAssemblyIdentity : TAssemblyIdentity read FMainAssemblyIdentity;
    property DependentAssembly : TAssemblyIdentity read FDependentAssembly;
    property HasManifest : Boolean read FHasManifest;
    property RequestedExecutionLevel : TRequestedExecutionLevel read FRequestedExecutionLevel;
    constructor Create(const AFileName:string);
  end;

{ TReadManifiest }

constructor TManifiestReader.Create(const AFileName: string);
begin
  FFileName:=AFileName;
  FHasManifest:=False;
  GetManifest;
  LoadManifestData;
end;

procedure TManifiestReader.GetManifest;
var
  hModule  : THandle;
  Resource : TResourceStream;
begin
  FManifest:='';
  hModule:=LoadLibraryEx(PChar(FileName),0,LOAD_LIBRARY_AS_DATAFILE);
  try
     if hModule=0 then RaiseLastOSError;
     if FindResource(hModule, MakeIntResource(1), RT_MANIFEST)<>0 then
     begin
       Resource:=TResourceStream.CreateFromID(hModule,1,RT_MANIFEST);
       try
         SetString(FManifest, PAnsiChar(Resource.Memory),Resource.Size);
         FHasManifest:=True;
       finally
         Resource.Free;
       end;
     end;
  finally
      FreeLibrary(hModule);
  end;
end;

procedure TManifiestReader.LoadManifestData;
const
 assembly_namespace_V1='urn:schemas-microsoft-com:asm.v1';
 assembly_namespace_V2='urn:schemas-microsoft-com:asm.v2';
 assembly_namespace_V3='urn:schemas-microsoft-com:asm.v3';
var
  XmlDoc : OleVariant;
  ns     : string;
  Node   : OleVariant;
begin
  if Trim(FManifest)='' then exit;
  XmlDoc       := CreateOleObject('Msxml2.DOMDocument.6.0');
  XmlDoc.Async := False;
  try
    XmlDoc.LoadXML(FManifest);
    XmlDoc.SetProperty('SelectionLanguage','XPath');

    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);

    //set the namespaces alias
    ns := Format('xmlns:a=%s xmlns:b=%s xmlns:c=%s',[QuotedStr(assembly_namespace_V1),QuotedStr(assembly_namespace_V2),QuotedStr(assembly_namespace_V3)]);
    XmlDoc.setProperty('SelectionNamespaces', ns);

    //get the version of the manifest
    Node:=XmlDoc.selectSingleNode('/a:assembly/@manifestVersion');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    FManifestVersion:=Node.text;

    Node:=XmlDoc.selectSingleNode('/a:assembly/a:assemblyIdentity');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FMainAssemblyIdentity.&type   :=Node.getAttribute('type');
      FMainAssemblyIdentity.name    :=Node.getAttribute('name');
      FMainAssemblyIdentity.language:=VarNullToStr(Node.getAttribute('language'));
      FMainAssemblyIdentity.version :=Node.getAttribute('version');
      FMainAssemblyIdentity.processorArchitecture:=VarNullToStr(Node.getAttribute('processorArchitecture'));
      FMainAssemblyIdentity.publicKeyToken       :=VarNullToStr(Node.getAttribute('publicKeyToken'));
    end;

    Node:=XmlDoc.selectSingleNode('/a:assembly/a:dependency/a:dependentAssembly/a:assemblyIdentity');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FDependentAssembly.&type   :=Node.getAttribute('type');
      FDependentAssembly.name    :=Node.getAttribute('name');
      FDependentAssembly.language:=VarNullToStr(Node.getAttribute('language'));
      FDependentAssembly.version :=Node.getAttribute('version');
      FDependentAssembly.processorArchitecture:=VarNullToStr(Node.getAttribute('processorArchitecture'));
      FDependentAssembly.publicKeyToken       :=VarNullToStr(Node.getAttribute('publicKeyToken'));
    end;

    Node:=XmlDoc.selectSingleNode('/a:assembly/b:trustInfo/b:security/b:requestedPrivileges/b:requestedExecutionLevel');
    if VarIsNull(Node) or VarIsClear(Node) then
      Node:=XmlDoc.selectSingleNode('/a:assembly/c:trustInfo/c:security/c:requestedPrivileges/c:requestedExecutionLevel');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FRequestedExecutionLevel.level   :=Node.getAttribute('level');
      FRequestedExecutionLevel.uiAccess:=VarNullToStr(Node.getAttribute('uiAccess'));
    end;

  finally
    XmlDoc:=Unassigned;
  end;
end;

function TManifiestReader.VarNullToStr(Value: OleVariant): string;
begin
  if VarIsNull(Value) then
    Result:=''
  else
    Result:=VarToStr(Value);
end;

Var
  ManifestReader : TManifiestReader;
begin
 try
    CoInitialize(nil);
    try
      ManifestReader:=TManifiestReader.Create('MyApplication.exe');
      try
        //Writeln(ManifestReader.Manifest);

        Writeln('Manifest version '+ManifestReader.ManifestVersion);
        Writeln('Main Assembly Identity');
        Writeln('----------------------');
        Writeln('type     '+ManifestReader.MainAssemblyIdentity.&type);
        Writeln('name     '+ManifestReader.MainAssemblyIdentity.name);
        Writeln('language '+ManifestReader.MainAssemblyIdentity.language);
        Writeln('version  '+ManifestReader.MainAssemblyIdentity.version);
        Writeln('processorArchitecture '+ManifestReader.MainAssemblyIdentity.processorArchitecture);
        Writeln('publicKeyToken        '+ManifestReader.MainAssemblyIdentity.publicKeyToken);
        Writeln('');

        Writeln('Dependent Assembly Identity');
        Writeln('---------------------------');
        Writeln('type     '+ManifestReader.DependentAssembly.&type);
        Writeln('name     '+ManifestReader.DependentAssembly.name);
        Writeln('language '+ManifestReader.DependentAssembly.language);
        Writeln('version  '+ManifestReader.DependentAssembly.version);
        Writeln('processorArchitecture '+ManifestReader.DependentAssembly.processorArchitecture);
        Writeln('publicKeyToken        '+ManifestReader.DependentAssembly.publicKeyToken);
        Writeln('');

        Writeln('Requested Execution Level');
        Writeln('---------------------------');
        Writeln('level     '+ManifestReader.RequestedExecutionLevel.level);
        Writeln('uiAccess  '+ManifestReader.RequestedExecutionLevel.uiAccess);

      finally
        ManifestReader.Free;
      end;
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
  Readln;
end.

Additional resources

Author: Rodrigo

Just another Delphi guy.

Leave a comment