Browse Source

Add IsMsiProductInstalled and also StrToVersion. See #391. Todo: doc/whatsnew/credit.

Martijn Laan 4 years ago
parent
commit
59acd63e47
3 changed files with 109 additions and 7 deletions
  1. 89 0
      Projects/Msi.pas
  2. 6 4
      Projects/ScriptFunc.pas
  3. 14 3
      Projects/ScriptFunc_R.pas

+ 89 - 0
Projects/Msi.pas

@@ -0,0 +1,89 @@
+unit Msi;
+
+{
+  Inno Setup
+  Copyright (C) 1997-2020 Jordan Russell
+  Portions by Martijn Laan
+  For conditions of distribution and use, see LICENSE.TXT.
+
+  MSI functions
+}
+
+interface
+
+function IsMsiProductInstalled(const UpgradeCode: String; const PackedMinVersion: Int64; var ErrorCode: Cardinal): Boolean;
+
+implementation
+
+uses
+  Windows, SysUtils, CmnFunc2, PathFunc, VerInfo;
+
+var
+  MsiLoaded: Boolean;
+  MsiLibrary: HMODULE;
+  MsiEnumRelatedProductsFunc: function(lpUpgradeCode: PChar; dwReserved, iProductIndex: DWORD; lpProductBuf: PChar): UINT; stdcall;
+  MsiGetProductInfoFunc: function(szProduct, szAttribute, lpValueBuf: PChar; var pcchValueBuf: DWORD): UINT; stdcall;
+  MsiLibraryLastError, MsiEnumRelatedProductsFuncLastError, MsiGetProductInfoFuncLastError: Cardinal;
+
+function IsMsiProductInstalled(const UpgradeCode: String; const PackedMinVersion: Int64; var ErrorCode: Cardinal): Boolean;
+var
+  ProductCode: array[0..38] of Char;
+  VersionStringSize: DWORD;
+  VersionString: String;
+  VersionNumbers: TFileVersionNumbers;
+  PackedVersion: Int64;
+begin
+  Result := False;
+
+  if not MsiLoaded then begin
+    MsiLibrary := SafeLoadLibrary(AddBackslash(GetSystemDir) + 'msi.dll', SEM_NOOPENFILEERRORBOX);
+    if MsiLibrary <> 0 then begin
+      MsiEnumRelatedProductsFunc := GetProcAddress(MsiLibrary, 'MsiEnumRelatedProductsW');
+      MsiEnumRelatedProductsFuncLastError := GetLastError;
+      MsiGetProductInfoFunc := GetProcAddress(MsiLibrary, 'MsiGetProductInfoW');
+      MsiGetProductInfoFuncLastError := GetLastError;
+    end else
+      MsiLibraryLastError := GetLastError;
+    MsiLoaded := True;
+  end;
+
+  if MsiLibrary = 0 then
+    ErrorCode := MsiLibraryLastError
+  else if not Assigned(MsiEnumRelatedProductsFunc) then
+    ErrorCode := MsiEnumRelatedProductsFuncLastError
+  else if not Assigned(MsiGetProductInfoFunc) then
+    ErrorCode := MsiGetProductInfoFuncLastError
+  else
+    ErrorCode := ERROR_SUCCESS;
+  if ErrorCode <> ERROR_SUCCESS then
+    Exit;
+
+  ErrorCode := MsiEnumRelatedProductsFunc(PChar(UpgradeCode), 0, 0, ProductCode);
+  if ErrorCode <> ERROR_SUCCESS then begin
+    if ErrorCode = ERROR_NO_MORE_ITEMS then
+      ErrorCode := ERROR_SUCCESS;  { Not installed so should just return False without an error }
+    Exit;
+  end;
+
+  VersionStringSize := 16;
+  SetLength(VersionString, VersionStringSize);
+  ErrorCode := MsiGetProductInfoFunc(ProductCode, 'VersionString', PChar(VersionString), VersionStringSize);
+  if ErrorCode = ERROR_MORE_DATA then begin
+    SetLength(VersionString, VersionStringSize+1);
+    ErrorCode := MsiGetProductInfoFunc(ProductCode, 'VersionString', PChar(VersionString), VersionStringSize);
+  end;
+  if ErrorCode <> ERROR_SUCCESS then
+    Exit;
+  SetLength(VersionString, VersionStringSize);
+
+  if not StrToVersionNumbers(VersionString, VersionNumbers) then begin
+    ErrorCode := ERROR_BAD_FORMAT;
+    Exit;
+  end;
+
+  PackedVersion := (Int64(VersionNumbers.MS) shl 32) or VersionNumbers.LS;
+  Result := PackedVersion >= PackedMinVersion;
+  ErrorCode := ERROR_SUCCESS;
+end;
+
+end.

+ 6 - 4
Projects/ScriptFunc.pas

@@ -296,7 +296,7 @@ const
   );
 
   { VerInfo }
-  VerInfoTable: array [0..10] of AnsiString =
+  VerInfoTable: array [0..11] of AnsiString =
   (
     'function GetVersionNumbers(const Filename: String; var VersionMS, VersionLS: Cardinal): Boolean;',
     'function GetVersionComponents(const Filename: String; var Major, Minor, Revision, Build: Word): Boolean;',
@@ -308,7 +308,8 @@ const
     'function SamePackedVersion(const Version1, Version2: Int64): Boolean;',
     'procedure UnpackVersionNumbers(const Version: Int64; var VersionMS, VersionLS: Cardinal);',
     'procedure UnpackVersionComponents(const Version: Int64; var Major, Minor, Revision, Build: Word);',
-    'function VersionToStr(const Version: Int64): String;'
+    'function VersionToStr(const Version: Int64): String;',
+    'function StrToVersion(const VersionString: String; var Version: Int64): Boolean;'
   );
 
   { Windows }
@@ -345,7 +346,7 @@ const
   );
 
   { Other }
-  OtherTable: array [0..31] of AnsiString =
+  OtherTable: array [0..32] of AnsiString =
   (
     'procedure BringToFrontAndRestore;',
     'function WizardDirValue: String;',
@@ -378,7 +379,8 @@ const
     'function EnableFsRedirection(const Enable: Boolean): Boolean;',
     'function GetUninstallProgressForm: TUninstallProgressForm;',
     'function CreateCallback(Method: AnyMethod): Longword;',
-    'function IsDotNetInstalled(const MinVersion: TDotNetVersion; const MinServicePack: Cardinal): Boolean;'
+    'function IsDotNetInstalled(const MinVersion: TDotNetVersion; const MinServicePack: Cardinal): Boolean;',
+    'function IsMsiProductInstalled(const UpgradeCode: String; const PackedMinVersion: Int64): Boolean;'
   );
 
 implementation

+ 14 - 3
Projects/ScriptFunc_R.pas

@@ -27,7 +27,7 @@ uses
   Struct, ScriptDlg, Main, PathFunc, CmnFunc, CmnFunc2, FileClass, RedirFunc,
   Install, InstFunc, InstFnc2, Msgs, MsgIDs, NewDisk, BrowseFunc, Wizard, VerInfo,
   SetupTypes, Int64Em, MD5, SHA1, Logging, SetupForm, RegDLL, Helper,
-  SpawnClient, UninstProgressForm, ASMInline, DotNet;
+  SpawnClient, UninstProgressForm, ASMInline, DotNet, Msi;
 
 var
   ScaleBaseUnitsInitialized: Boolean;
@@ -1559,6 +1559,12 @@ begin
     VersionNumbers.LS := UInt64(Stack.GetInt64(PStart-1)) and $FFFFFFFF;
     Stack.SetString(PStart, Format('%u.%u.%u.%u', [VersionNumbers.MS shr 16,
       VersionNumbers.MS and $FFFF, VersionNumbers.LS shr 16, VersionNumbers.LS and $FFFF]));
+  end else if Proc.Name = 'STRTOVERSION' then begin
+    if StrToVersionNumbers(Stack.GetString(PStart-1), VersionNumbers) then begin
+      Stack.SetInt64(PStart-2, (Int64(VersionNumbers.MS) shl 32) or VersionNumbers.LS);
+      Stack.SetBool(PStart, True);
+    end else
+      Stack.SetBool(PStart, False);
   end else
     Result := False;
 end;
@@ -1878,6 +1884,7 @@ var
   S: String;
   AnsiS: AnsiString;
   Arr: TPSVariantIFC;
+  ErrorCode: Cardinal;
 begin
   PStart := Stack.Count-1;
   Result := True;
@@ -2009,9 +2016,13 @@ begin
   end else if Proc.Name = 'GETUNINSTALLPROGRESSFORM' then begin
     Stack.SetClass(PStart, GetUninstallProgressForm);
   end else if Proc.Name = 'CREATECALLBACK' then begin
-   Stack.SetInt(PStart, CreateCallback(Stack.Items[PStart-1]));
+    Stack.SetInt(PStart, CreateCallback(Stack.Items[PStart-1]));
   end else if Proc.Name = 'ISDOTNETINSTALLED' then begin
-   Stack.SetBool(PStart, IsDotNetInstalled(InstallDefaultRegView, TDotNetVersion(Stack.GetInt(PStart-1)), Stack.GetInt(PStart-2)));
+    Stack.SetBool(PStart, IsDotNetInstalled(InstallDefaultRegView, TDotNetVersion(Stack.GetInt(PStart-1)), Stack.GetInt(PStart-2)));
+  end else if Proc.Name = 'ISMSIPRODUCTINSTALLED' then begin
+    Stack.SetBool(PStart, IsMsiProductInstalled(Stack.GetString(PStart-1), Stack.GetInt64(PStart-2), ErrorCode));
+    if ErrorCode <> 0 then
+      raise Exception.Create(Win32ErrorString(ErrorCode));
   end else
     Result := False;
 end;