根据Variable ODBC DSN name in InstallShield MSI installation中提出的想法,我试图找到在使用InstallShield 2015创建的多实例MSI安装程序项目中定义注册表项的最佳方法,以便在他们自己的实例取消后清理它们-installed。为了测试这个想法,我遵循了以下步骤:
我希望在步骤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行都不存在,我不知道为什么。
答案 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