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.