从MSI多实例InstallShield包

时间:2015-08-24 19:51:32

标签: windows-installer installshield

根据Variable ODBC DSN name in InstallShield MSI installation中提出的想法,我试图找到在使用InstallShield 2015创建的多实例MSI安装程序项目中定义注册表项的最佳方法,以便在他们自己的实例取消后清理它们-installed。为了测试这个想法,我遵循了以下步骤:

  1. 创建一个名为Dummy1的新的基本MSI项目。
  2. 在“应用程序文件”的“项目助手”步骤中,将Readme.txt作为文件添加到[ProgramFilesFolder] \ My Company Name \ My Product Name。
  3. 在“安装设计器”,“文件和文件夹”中,右键单击并将其设置为“密钥文件”。
  4. 在“组件”视图中,添加注册表项HKLM \ SOFTWARE \ Dummy,Name [InstanceId],Value“Installed”。
  5. 使用版本向导创建包含所有默认值的版本。​​
  6. 在“产品配置1”上,选择“多个实例”选项卡并添加 实例1。
  7. 在Project Assistant,Application Interview中,将property设置为“Yes”以允许指定安装位置。
  8. 构建setup.exe并从Windows资源管理器运行两次,安装到 公司\ 1先到公司\ 2.
  9. 验证HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Dummy包含“0”和“1” 值。
  10. 从控制面板中卸载一个Dummy1实例。
  11. 注意到其中一个README.TXT文件已被删除,但包含它的空目录(在我的情况下为“1”)和注册表项仍然存在。
  12. 我希望在步骤11中删除已创建的目录和其中一个注册表项。我错过了什么吗?

    编辑:当我开启大量日志记录时,我可以看到如下消息:

      

    MSI(E0:F8)[14:30:24:540]:允许卸载共享   组件:{3AACE297-A264-4223-A0AF-C5A20D37551F}。其他客户   存在,但安装到不同的位置

    至少在IS 2015中似乎没有类似的识别安装到“共享”组件中包含的不同路径的注册表项,并且与正在删除的文件相同的组件中的注册表项似乎不会被卷起在删除组件。

    我可以在日志文件中看到删除最终组件的安装:

    MSI (s) (E0:08) [14:36:09:538]: Executing op: ActionStart(Name=RemoveRegistryValues,Description=Removing system registry values,Template=Key: [1], Name: [2])
    MSI (s) (E0:08) [14:36:09:538]: Executing op: ProgressTotal(Total=1,Type=1,ByteEquivalent=13200)
    MSI (s) (E0:08) [14:36:09:538]: Executing op: RegOpenKey(Root=-2147483646,Key=SOFTWARE\Infor\0,,BinaryType=0,,)
    MSI (s) (E0:08) [14:36:09:538]: Executing op: RegRemoveValue(Name=Test,Value=One,)
    

    并没有像我在日志中看到的卸载重复组件那样提及“允许卸载共享组件”。并且,在卸载重复组件的日志中,所有这4行都不存在,我不知道为什么。

1 个答案:

答案 0 :(得分:0)

有一个优雅的解决方案需要花费很多精力才能成为不熟悉Windows Installer或InstallShield的人。 InstallShield的Releases页面上Release对象的Events选项卡有一个Precompression事件,在此期间可以在压缩到Setup.exe之前修改MSI文件。我已将此命令输入预压缩事件:

"<ISProjectFolder>\UpdateMSI.exe" "<ISReleasePath>\<ISProductConfigName>\<ISReleaseName>\DiskImages\Disk1\Dummy.msi"

其中Dummy.msi是InstallShield在压缩之前生成的msi(或者如果释放配置为不压缩结果)。 我创建了一个名为UpdateMSI的VB.NET程序,并将输出放在C:\InstallShield 2015 Projects\中。该程序的作用本质上是提取InstallShield生成并嵌入到MSI文件中的所有实例转换,并更新它们以更新组件的GUID。现在我只有一个组件可以更新注册表,所以我只更新一个组件,为每个实例提供一个单独的GUID(最多9个实例加上默认实例)。

结果是一个安装程序干净地安装和卸载这些组件,方法是为它们保留单独的GUID ,而不必在InstallShield项目中复制它们。

UpdateMSI的代码如下所示。

Imports System.Runtime.InteropServices

Module Main
   Private Declare Auto Function MsiOpenDatabase Lib "msi.dll" (ByVal szDatabasePath As String, ByVal szPersist As IntPtr, <Out> ByRef phDatabase As IntPtr) As UInt32
   Private Declare Auto Function MsiCloseHandle Lib "msi.dll" (ByVal hAny As Integer) As UInt32
   Private Declare Auto Function MsiDatabaseOpenView Lib "msi.dll" (ByVal hDatabase As IntPtr, ByVal szQuery As String, <Out> ByRef phView As IntPtr) As UInt32
   Private Declare Auto Function MsiViewExecute Lib "msi.dll" (ByVal hView As IntPtr, ByVal hRecord As IntPtr) As UInt32
   Private Declare Auto Function MsiViewFetch Lib "msi.dll" (ByVal hView As IntPtr, <Out> ByRef phRecord As IntPtr) As UInt32
   Private Declare Auto Function MsiRecordGetString Lib "msi.dll" (ByVal hRecord As IntPtr, iField As UInt32, ByVal szValueBuf As System.Text.StringBuilder, ByRef pcchValueBuf As UInt32) As UInt32
   Private Declare Auto Function MsiViewModify Lib "msi.dll" (ByVal hView As IntPtr, eModifyMode As IntPtr, hRecord As IntPtr) As UInt32
   Private Declare Auto Function MsiRecordSetString Lib "msi.dll" (ByVal hRecord As IntPtr, iField As UInt32, szValue As String) As UInt32
   Private Declare Auto Function MsiDatabaseCommit Lib "msi.dll" (ByVal hDatabase As IntPtr) As UInt32
   Private Declare Auto Function MsiViewClose Lib "msi.dll" (ByVal hView As IntPtr) As UInt32
   Private Declare Auto Function MsiDatabaseApplyTransform Lib "msi.dll" (ByVal hDatabase As IntPtr, ByVal szTransformFile As String, ByVal iErrorConditions As Integer) As UInt32
   Private Declare Auto Function MsiDatabaseGenerateTransform Lib "msi.dll" (ByVal hDatabase As IntPtr, ByVal hDatabaseReference As IntPtr, ByVal szTransformFile As String, ByVal iReserved As Integer, ByVal iReserved As Integer) As UInt32
   Private Declare Auto Function MsiCreateTransformSummaryInfo Lib "msi.dll" (ByVal hDatabase As IntPtr, ByVal hDatabaseReference As IntPtr, ByVal szTransformFile As String, ByVal iErrorConditions As Integer, ByVal iValidation As Integer) As UInt32
   Private Declare Auto Function MsiRecordSetStream Lib "msi.dll" (ByVal hRecord As IntPtr, ByVal iField As UInt32, ByVal szFilePath As String) As UInt32

   Private Const MSIDBOPEN_READONLY As Integer = 0
   Private Const MSIDBOPEN_TRANSACT As Integer = 1
   Private Const MSIDBOPEN_DIRECT As Integer = 2
   Private Const ERROR_SUCCESS As Integer = 0
   Private Const ERROR_NO_MORE_ITEMS As Integer = 259
   Private Const MSIMODIFY_UPDATE As Integer = 2

   Private Enum MSITRANSFORM_VALIDATE
      None = 0
      Language = 1
      Product = 2
      MajorVersion = 8
      MinorVersion = &H10
      UpdateVersion = &H20
      NewLessBaseVersion = &H40
      NewLessEqualBaseVersion = &H80
      NewEqualBaseVersion = &H100
      NewGreaterEqualBaseVersion = &H200
      NewGreaterBaseVersion = &H400
      UpgradeCode = &H800
   End Enum

   Const RegComponentGuid As String = "{3AACE297-A264-4223-A0AF-C5A20D37551F}"
   Dim RegComponentInstGuids As String() = { _
      "{12456A37-51F3-4F53-A19E-34DF8CB1B063}", _
      "{0F00D476-E1F2-4B5D-85C4-D380A33BB78E}", _
      "{EFC3C9D6-9C2B-43E9-84A3-837C86BE5DD8}", _
      "{1398A80D-681D-4726-9972-8DEC8B030B4A}", _
      "{FF610463-5E35-4059-A3C4-EB51A7CABA1D}", _
      "{C4A43C73-5791-4B17-906C-93240AAB2F03}", _
      "{12E37949-704F-4A92-A7D2-5B7B94554505}", _
      "{E9175D7C-BC4E-4AC1-A79D-6B314EAB5D45}", _
      "{0A1E0D5C-44CC-4199-9C4E-FE23A1136B60}"}

   Function Main(args As String()) As Integer
      If args.Count <> 1 Then
         Console.Error.WriteLine(String.Format("{0} <MSI File>", System.Reflection.Assembly.GetExecutingAssembly().GetName().Name))
         Console.ReadLine()
         Return 1
      End If
      Main = UpdateTransforms(args(0))
   End Function

   Public Sub CheckResult(result As Integer, Optional ByVal failureName As String = "MSI call")
      If result <> ERROR_SUCCESS Then
         Throw New ApplicationException(String.Format("{0} failed with code {1}", failureName, result))
      End If
   End Sub

   Private Function UpdateTransforms(msiFile As String)
      Dim hDB As IntPtr = IntPtr.Zero
      Dim hDBOriginal As IntPtr = IntPtr.Zero
      Dim hView As IntPtr = IntPtr.Zero
      Dim mainResult As Integer = 0
      Dim hRec As IntPtr = IntPtr.Zero
      Dim instanceNames As IEnumerable(Of String) = Nothing

      Try
         instanceNames = GetEmbeddedTransforms(msiFile)
         Console.WriteLine("Found {0} instances embedded in {1}", instanceNames.Count, msiFile)
         If instanceNames.Count > RegComponentInstGuids.Length Then
            Console.Error.WriteLine("More instances were found than pre-defined GUIDs")
            Console.ReadLine()
            Return 4
         End If

         ' For each embedded instance, modify the MST for that instance to also modify component GUIDs
         Dim componentIndex As Integer = 0
         For Each instanceName In instanceNames
            Dim msiCopy As String = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "InstanceIdTmp.msi")
            System.IO.File.Copy(msiFile, msiCopy)
            CheckResult(MsiOpenDatabase(msiCopy, MSIDBOPEN_TRANSACT, hDB), "MsiOpenDatabase")
            CheckResult(MsiDatabaseApplyTransform(hDB, String.Format(":{0}", instanceName), 0), "MsiDatabaseApplyTransform")
            CheckResult(MsiDatabaseOpenView(hDB, String.Format("UPDATE `Component` SET `Component`.`ComponentId`='{0}' WHERE `Component`.`ComponentId`='{1}'", RegComponentInstGuids(componentIndex), RegComponentGuid), hView), "MsiDatabaseOpenView")
            CheckResult(MsiViewExecute(hView, IntPtr.Zero), "MsiViewExecute")
            CheckResult(MsiViewClose(hView), "MsiViewClose")
            CheckResult(MsiDatabaseCommit(hDB), "MsiDatabaseCommit")
            CheckResult(MsiOpenDatabase(msiFile, MSIDBOPEN_TRANSACT, hDBOriginal), "MsiOpenDatabase")
            Dim mstFile As String = System.IO.Path.Combine(System.IO.Path.GetTempPath(), instanceName)
            CheckResult(MsiDatabaseGenerateTransform(hDB, hDBOriginal, mstFile, 0, 0), "MsiDatabaseGenerateTransform")
            CheckResult(MsiCreateTransformSummaryInfo(hDB, hDBOriginal, mstFile, 0, MSITRANSFORM_VALIDATE.UpgradeCode), "MsiCreateTransformSummaryInfo")
            CheckResult(MsiCloseHandle(hView), "MsiCloseHandle")
            hView = IntPtr.Zero
            CheckResult(MsiDatabaseOpenView(hDBOriginal, String.Format("SELECT Data FROM `_Storages` WHERE Name='{0}'", instanceName), hView), "MsiDatabaseOpenView")
            Console.WriteLine("Updating component {0} in instance {1} to use {2}", RegComponentGuid, componentIndex, RegComponentInstGuids(componentIndex))
            CheckResult(MsiViewExecute(hView, IntPtr.Zero), "MsiViewExecute")
            CheckResult(MsiViewFetch(hView, hRec), "MsiViewFetch")
            CheckResult(MsiRecordSetStream(hRec, 1, mstFile), "MsiRecordSetStream")
            CheckResult(MsiViewModify(hView, 2, hRec), "MsiViewModify")
            CheckResult(MsiCloseHandle(hRec), "MsiCloseHandle")
            hRec = IntPtr.Zero
            CheckResult(MsiViewClose(hView), "MsiViewClose")
            CheckResult(MsiDatabaseCommit(hDBOriginal), "MsiDatabaseCommit")
            If System.IO.File.Exists(mstFile) Then System.IO.File.Delete(mstFile)
            CheckResult(MsiCloseHandle(hView), "MsiCloseHandle")
            hView = IntPtr.Zero
            CheckResult(MsiCloseHandle(hDBOriginal), "MsiCloseHandle")
            hDBOriginal = IntPtr.Zero
            CheckResult(MsiCloseHandle(hDB), "MsiCloseHandle")
            hDB = IntPtr.Zero
            System.IO.File.Delete(msiCopy)
            componentIndex += 1
         Next
      Catch ex As System.Exception
         Console.Error.WriteLine(ex.ToString())
         Console.ReadLine()
         mainResult = 2
      Finally
         Try
            If hRec <> IntPtr.Zero Then CheckResult(MsiCloseHandle(hRec), "MsiCloseHandle")
            If hDBOriginal <> IntPtr.Zero Then CheckResult(MsiCloseHandle(hDBOriginal), "MsiCloseHandle")
            If hView <> IntPtr.Zero Then CheckResult(MsiCloseHandle(hView), "MsiCloseHandle")
         Catch ex As Exception
            Console.Error.WriteLine(ex.ToString())
            Console.ReadLine()
            mainResult = 3
         Finally
            If hDB <> IntPtr.Zero Then
               CheckResult(MsiCloseHandle(hDB), "MsiCloseHandle")
            End If
            Dim msiCopy As String = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "InstanceIdTmp.msi")
            If System.IO.File.Exists(msiCopy) Then System.IO.File.Delete(msiCopy)
            If instanceNames IsNot Nothing Then
               For Each instanceName In instanceNames
                  If System.IO.File.Exists(instanceName) Then System.IO.File.Delete(instanceName)
               Next
            End If
         End Try
      End Try
      Return mainResult
   End Function

   Private Function GetEmbeddedTransforms(msiFile As String) As IEnumerable(Of String)
      Dim hDB As IntPtr = IntPtr.Zero
      Dim hView As IntPtr = IntPtr.Zero
      Dim hRec As IntPtr = IntPtr.Zero
      Dim instanceNames As New LinkedList(Of String)
      Try
         CheckResult(MsiOpenDatabase(msiFile, MSIDBOPEN_READONLY, hDB), "MsiOpenDatabase")
         CheckResult(MsiDatabaseOpenView(hDB, "SELECT Name FROM `_Storages`", hView), "MsiDatabaseOpenView")
         CheckResult(MsiViewExecute(hView, IntPtr.Zero), "MsiViewExecute")
         Do
            Dim fetchResult = MsiViewFetch(hView, hRec)
            If fetchResult = ERROR_NO_MORE_ITEMS Then Exit Do
            CheckResult(fetchResult)
            Dim instMstName As New System.Text.StringBuilder(256)
            CheckResult(MsiRecordGetString(hRec, 1, instMstName, instMstName.Capacity - 1), "MsiRecordGetString")
            If instMstName.ToString() Like "InstanceId#*.mst" Then
               instanceNames.AddLast(instMstName.ToString())
            End If
            CheckResult(MsiCloseHandle(hRec), "MsiCloseHandle")
            hRec = IntPtr.Zero
         Loop
         CheckResult(MsiViewClose(hView), "MsiViewClose")
      Catch ex As System.Exception
         Console.Error.WriteLine(ex.ToString())
         Console.ReadLine()
      Finally
         If hRec <> IntPtr.Zero Then CheckResult(MsiCloseHandle(hRec), "MsiCloseHandle")
         If hView <> IntPtr.Zero Then CheckResult(MsiCloseHandle(hView), "MsiCloseHandle")
         If hDB <> IntPtr.Zero Then CheckResult(MsiCloseHandle(hDB), "MsiCloseHandle")
      End Try
      Return instanceNames
   End Function
End Module