从MSI多实例InstallShield软件包中卸载注册表项的最佳方法 [英] Best way to uninstall registry entries from MSI multi-instance InstallShield package

查看:125
本文介绍了从MSI多实例InstallShield软件包中卸载注册表项的最佳方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于中的变量ODBC DSN名称中提出的想法我正在尝试安装InstallShield MSI安装,我试图在用InstallShield 2015创建的多实例MSI安装程序项目中找到定义注册表项的最佳方法,以便在卸载自己的实例后将它们清除.为了测试这个想法,我按照以下步骤操作:

  1. 创建一个名为Dummy1的新的Basic MSI项目.
  2. 在应用程序文件"步骤的项目助手"中,将Readme.txt作为文件添加到[ProgramFilesFolder] \我的公司名称" \我的产品名称"中.
  3. 在Installation Designer的文件和文件夹"中,右键单击并将其设置为密钥文件".
  4. 在组件"视图中,添加注册表项HKLM \ SOFTWARE \ Dummy,名称[InstanceId],值已安装".
  5. 使用发布向导创建具有所有默认值的发布.
  6. 在产品配置"上,选择多个实例"选项卡并添加 实例1.
  7. 在Project Assistant的应用程序面试"中,将属性设置为是"以允许指定安装位置.
  8. 构建setup.exe并从Windows资源管理器中运行两次,然后安装到 公司\ 1,然后是公司\ 2.
  9. 验证HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Dummy是否包含"0"和"1" 值.
  10. 从控制面板中卸载一个Dummy1实例.
  11. 观察到其中一个README.TXT文件已被删除,但是包含该文件的空目录(在我的情况下为"1")和注册表项都仍然存在.

在第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,)

并且它没有提到允许共享组件的卸载",正如我在日志中看到的,用于卸载重复的组件.而且,在用于卸载重复组件的日志中,所有这四行都完全不存在,我也不知道为什么.

解决方案

有一个优雅的解决方案,需要花很多精力才能成为对Windows Installer或InstallShield不太熟悉的人. InstallShield的发行"页面上发行"对象的事件"选项卡上有一个Precompression事件,在此期间,可以在MSI文件压缩到Setup.exe中之前对其进行修改.我已经在Precompression事件中输入了以下命令:

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

其中Dummy.msi是InstallShield在压缩之前生成的msi(或者如果发行版配置为不压缩结果). 我创建了一个名为UpdateMSI的VB.NET程序,并将输出放在C:\InstallShield 2015 Projects\中.该程序要做的实际上是提取InstallShield生成并嵌入到MSI文件中的所有实例转换,并对其进行更新以更新组件的GUID.现在,我只有一个组件来更新注册表,因此我只更新了一个组件,以便为每个实例(最多9个实例和默认实例)具有一个单独的GUID.

结果是一个安装程序,通过干净干净地安装和卸载这些组件,方法是为它们保留单独的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
 

Stemming from an idea presented in Variable ODBC DSN name in InstallShield MSI installation, I am attempting to find the best way to define registry entries in a multi-instance MSI installer project created with InstallShield 2015 such that they clean up after their own instance is un-installed. To test the idea I have followed these steps:

  1. Create a new Basic MSI project called Dummy1.
  2. In Project assistant on Application Files step, add Readme.txt as a file to deliver to [ProgramFilesFolder]\My Company Name\My Product Name.
  3. In Installation Designer, Files and Folders, right-click and set it as Key File.
  4. In Components view, add registry entry HKLM\SOFTWARE\Dummy, Name [InstanceId], Value "Installed".
  5. Use Release Wizard to create a Release with all the default values.
  6. On Product Configuration 1, select Multiple Instances tab and add Instance 1.
  7. In Project Assistant, Application Interview, set property to "Yes" to allow specifying install location.
  8. Build setup.exe and run it twice from Windows Explorer, installing to Company\1 first and then Company\2.
  9. Verify HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Dummy contains "0" and "1" values.
  10. Un-install one instance of Dummy1 from control panel.
  11. Observe that one of the README.TXT files has been removed, but the empty directory containing it ("1" in my case), and the registry entries are all still there.

I would have expected in step 11 that the directory that was created and one of the registry entries would have been removed. Did I miss something?

Edit: When I turn on lots of logging I can see a message like this:

MSI (s) (E0:F8) [14:30:24:540]: Allowing uninstallation of shared component: {3AACE297-A264-4223-A0AF-C5A20D37551F}. Other clients exist, but installed to a different location

It appears at least in IS 2015 there's no similar recognition of registry entries installing to different paths included in a "shared" component, and the registry entries in the same component as a file that's being removed do not seem to be rolled up in the removal of the component.

I can see in the log file for the removal of the final component installation:

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,)

And it does not mention "Allowing uninstallation of shared component" as I saw in the log for the uninstallation of the duplicated component. And, in the log for the uninstallation of the duplicated component, all 4 of these lines are simply absent and I don't know why.

解决方案

There is an elegant solution that took quite a bit of effort to work out being someone not very familiar with Windows Installer or InstallShield. The Events tab of the Release object on InstallShield's Releases page has a Precompression event, during which time it's possible to modify the MSI file before it gets compressed into Setup.exe. I have entered this command into the Precompression event:

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

Where Dummy.msi is the msi generated by InstallShield before it gets compressed (or if the release is configured not to compress the result). I created a VB.NET program called UpdateMSI and put the output in C:\InstallShield 2015 Projects\. What this program does is essentially extract all the instance transforms generated and embedded into the MSI file by InstallShield, and updates them to also update the GUIDs for the component(s). Right now I have only one component which updates the registry so I am updating just the one component to have a separate GUID for each instance (up to 9 instances plus the default instance).

The result is a setup program that cleanly installs and un-installs these components by retaining separate GUIDs for them without having to duplicate them in the InstallShield project.

The code for UpdateMSI looks like this.

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

这篇关于从MSI多实例InstallShield软件包中卸载注册表项的最佳方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆