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.