Add RemoveRegistryKey
Rob Mensching committed
Apr 11, 2021 at 12:23 UTC
ae7e9817bb10d635e031e51496f2e529595a9cfe
11 files changed
+366
-6
src/ca/RemoveRegistryKeysEx.cpp
new
+114
@@ -0,0 +1,114 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+#include "precomp.h"
4
+
5
+LPCWSTR vcsRemoveRegistryKeyExQuery =
6
+ L"SELECT `Wix4RemoveRegistryKeyEx`, `Component_`, `Root`, `Key`, `InstallMode`, `Condition` FROM `Wix4RemoveRegistryKeyEx`";
7
+enum eRemoveRegistryKeyExQuery { rrxqId = 1, rrxqComponent, rrxqRoot, rrxqKey, rrxqMode, rrxqCondition };
8
+
9
+extern "C" UINT WINAPI WixRemoveRegistryKeysEx(
10
+ __in MSIHANDLE hInstall
11
+)
12
+{
13
+ //AssertSz(FALSE, "debug WixRemoveRegistryKeyEx");
14
+
15
+ HRESULT hr = S_OK;
16
+ PMSIHANDLE hView;
17
+ PMSIHANDLE hRec;
18
+ LPWSTR sczId = NULL;
19
+ LPWSTR sczComponent = NULL;
20
+ LPWSTR sczCondition = NULL;
21
+ LPWSTR sczKey = NULL;
22
+ int iRoot = 0;
23
+ int iMode = 0;
24
+ MSIHANDLE hTable = NULL;
25
+ MSIHANDLE hColumns = NULL;
26
+
27
+ hr = WcaInitialize(hInstall, __FUNCTION__);
28
+ ExitOnFailure(hr, "Failed to initialize " __FUNCTION__);
29
+
30
+ // anything to do?
31
+ if (S_OK != WcaTableExists(L"Wix4RemoveRegistryKeyEx"))
32
+ {
33
+ WcaLog(LOGMSG_STANDARD, "Wix4RemoveRegistryKeyEx table doesn't exist, so there are no registry keys to remove.");
34
+ ExitFunction();
35
+ }
36
+
37
+ hr = WcaOpenExecuteView(vcsRemoveRegistryKeyExQuery, &hView);
38
+ ExitOnFailure(hr, "Failed to open view on Wix4RemoveRegistryKeyEx table");
39
+
40
+ while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
41
+ {
42
+ hr = WcaGetRecordString(hRec, rrxqId, &sczId);
43
+ ExitOnFailure(hr, "Failed to get Wix4RemoveRegistryKeyEx identity.");
44
+
45
+ hr = WcaGetRecordString(hRec, rrxqCondition, &sczCondition);
46
+ ExitOnFailure(hr, "Failed to get Wix4RemoveRegistryKeyEx condition.");
47
+
48
+ if (sczCondition && *sczCondition)
49
+ {
50
+ MSICONDITION condition = ::MsiEvaluateConditionW(hInstall, sczCondition);
51
+ if (MSICONDITION_TRUE == condition)
52
+ {
53
+ WcaLog(LOGMSG_STANDARD, "True condition for row %S: %S; processing.", sczId, sczCondition);
54
+ }
55
+ else
56
+ {
57
+ WcaLog(LOGMSG_STANDARD, "False or invalid condition for row %S: %S; skipping.", sczId, sczCondition);
58
+ continue;
59
+ }
60
+ }
61
+
62
+ hr = WcaGetRecordString(hRec, rrxqComponent, &sczComponent);
63
+ ExitOnFailure(hr, "Failed to get Wix4RemoveRegistryKeyEx component.");
64
+
65
+ hr = WcaGetRecordInteger(hRec, rrxqRoot, &iRoot);
66
+ ExitOnFailure(hr, "Failed to get Wix4RemoveRegistryKeyEx root.");
67
+
68
+ hr = WcaGetRecordString(hRec, rrxqKey, &sczKey);
69
+ ExitOnFailure(hr, "Failed to get Wix4RemoveRegistryKeyEx key.");
70
+
71
+ hr = WcaGetRecordInteger(hRec, rrxqMode, &iMode);
72
+ ExitOnFailure(hr, "Failed to get Wix4RemoveRegistryKeyEx mode.");
73
+
74
+ switch (iMode)
75
+ {
76
+ case 1: // remove on install
77
+ WcaLog(LOGMSG_STANDARD, "Adding RemoveRegistry row: %ls/%d/%ls/-/%ls", sczId, iRoot, sczKey, sczComponent);
78
+ hr = WcaAddTempRecord(&hTable, &hColumns, L"RemoveRegistry", NULL, 0, 5, sczId, iRoot, sczKey, L"-", sczComponent);
79
+ ExitOnFailure(hr, "Failed to add RemoveRegistry row for remove-on-install Wix4RemoveRegistryKeyEx row: %ls:", sczId);
80
+ break;
81
+ case 2: // remove on uninstall
82
+ WcaLog(LOGMSG_STANDARD, "Adding Registry row: %ls/%d/%ls/-/null/%ls", sczId, iRoot, sczKey, sczComponent);
83
+ hr = WcaAddTempRecord(&hTable, &hColumns, L"Registry", NULL, 0, 6, sczId, iRoot, sczKey, L"-", NULL, sczComponent);
84
+ ExitOnFailure(hr, "Failed to add Registry row for remove-on-uninstall Wix4RemoveRegistryKeyEx row: %ls:", sczId);
85
+ break;
86
+ }
87
+ }
88
+
89
+ // reaching the end of the list is actually a good thing, not an error
90
+ if (E_NOMOREITEMS == hr)
91
+ {
92
+ hr = S_OK;
93
+ }
94
+ ExitOnFailure(hr, "Failure occured while processing Wix4RemoveRegistryKeyEx table.");
95
+
96
+LExit:
97
+ if (hColumns)
98
+ {
99
+ ::MsiCloseHandle(hColumns);
100
+ }
101
+
102
+ if (hTable)
103
+ {
104
+ ::MsiCloseHandle(hTable);
105
+ }
106
+
107
+ ReleaseStr(sczKey);
108
+ ReleaseStr(sczComponent);
109
+ ReleaseStr(sczCondition);
110
+ ReleaseStr(sczId);
111
+
112
+ DWORD er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
113
+ return WcaFinalize(er);
114
+}
src/ca/utilca.def
+2
@@ -35,6 +35,8 @@ EXPORTS
35
WixSilentExec64
36
; RemoveFoldersEx.cpp
37
WixRemoveFoldersEx
38
+; RemoveRegistryKeysEx.cpp
39
+ WixRemoveRegistryKeysEx
40
;scaexec.cpp
41
RegisterPerfCounterData
42
UnregisterPerfCounterData
src/ca/utilca.vcxproj
+1
@@ -59,6 +59,7 @@
59
<ClCompile Include="OsInfo.cpp" />
60
<ClCompile Include="qtexecca.cpp" />
61
<ClCompile Include="RemoveFoldersEx.cpp" />
62
+ <ClCompile Include="RemoveRegistryKeysEx.cpp" />
63
<ClCompile Include="RestartManager.cpp" />
64
<ClCompile Include="scaexec.cpp" />
65
<ClCompile Include="scamanifest.cpp" />
src/test/WixToolsetTest.Util/TestData/RemoveRegistryKeyEx/Module.wxs
new
+13
@@ -0,0 +1,13 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Module Language="1033" Version="1.0.0.0" Id="InternetShortcutModule" Guid="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <SummaryInformation Manufacturer="Example Corporation" />
4
+
5
+ <ComponentGroupRef Id="ModuleComponents" />
6
+ </Module>
7
+
8
+ <Fragment>
9
+ <StandardDirectory Id="ProgramFilesFolder">
10
+ <Directory Id="INSTALLFOLDER" Name="MergeModule" />
11
+ </StandardDirectory>
12
+ </Fragment>
13
+</Wix>
src/test/WixToolsetTest.Util/TestData/RemoveRegistryKeyEx/ModuleComponents.wxs
new
+10
@@ -0,0 +1,10 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
2
+ <Fragment>
3
+ <ComponentGroup Id="ModuleComponents" Directory="INSTALLFOLDER">
4
+ <Component>
5
+ <File Source="ModuleComponents.wxs" />
6
+ <util:RemoveRegistryKey Root="HKLM" Key="SOFTWARE\Example" On="install" />
7
+ </Component>
8
+ </ComponentGroup>
9
+ </Fragment>
10
+</Wix>
src/test/WixToolsetTest.Util/UtilExtensionFixture.cs
+15
@@ -129,6 +129,21 @@ namespace WixToolsetTest.Util
129
}, results.OrderBy(s => s).ToArray());
130
}
131
132
+ [Fact]
133
+ public void CanBuildRemoveRegistryKeyExInMergeModule()
134
+ {
135
+ var folder = TestData.Get(@"TestData", "RemoveRegistryKeyEx");
136
+ var build = new Builder(folder, typeof(UtilExtensionFactory), new[] { folder }, "test.msm");
137
+
138
+ var results = build.BuildAndQuery(BuildX64, "Binary", "CustomAction", "RemoveRegistry", "Wix4RemoveRegistryKeyEx");
139
+ WixAssert.CompareLineByLine(new[]
140
+ {
141
+ "Binary:Wix4UtilCA_X64.047730A5_30FE_4A62_A520_DA9381B8226A\t[Binary data]",
142
+ "CustomAction:Wix4RemoveRegistryKeysEx_X64.047730A5_30FE_4A62_A520_DA9381B8226A\t65\tWix4UtilCA_X64.047730A5_30FE_4A62_A520_DA9381B8226A\tWixRemoveRegistryKeysEx\t",
143
+ "Wix4RemoveRegistryKeyEx:rrxfcDhR4HhE3v3rYiQcNtQjyahQNg.047730A5_30FE_4A62_A520_DA9381B8226A\tfilh4juyUVjoUcWWtcQmd5L07FoON4.047730A5_30FE_4A62_A520_DA9381B8226A\t2\tSOFTWARE\\Example\t1\t",
144
+ }, results.OrderBy(s => s).ToArray());
145
+ }
146
+
147
[Fact]
148
public void CanBuildRemoveFolderExInMergeModule()
149
{
src/wixext/Symbols/UtilSymbolDefinitions.cs
+4
@@ -23,6 +23,7 @@ namespace WixToolset.Util
23
WixFormatFiles,
24
WixInternetShortcut,
25
WixRemoveFolderEx,
26
+ WixRemoveRegistryKeyEx,
27
WixRestartResource,
28
WixTouchFile,
29
WixWindowsFeatureSearch,
@@ -93,6 +94,9 @@ namespace WixToolset.Util
94
case UtilSymbolDefinitionType.WixRemoveFolderEx:
95
return UtilSymbolDefinitions.WixRemoveFolderEx;
96
97
+ case UtilSymbolDefinitionType.WixRemoveRegistryKeyEx:
98
+ return UtilSymbolDefinitions.WixRemoveRegistryKeyEx;
99
+
100
case UtilSymbolDefinitionType.WixRestartResource:
101
return UtilSymbolDefinitions.WixRestartResource;
102
src/wixext/Symbols/WixRemoveRegistryKeyExSymbol.cs
new
+86
@@ -0,0 +1,86 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Util
4
+{
5
+ using WixToolset.Data;
6
+ using WixToolset.Util.Symbols;
7
+
8
+ public static partial class UtilSymbolDefinitions
9
+ {
10
+ public static readonly IntermediateSymbolDefinition WixRemoveRegistryKeyEx = new IntermediateSymbolDefinition(
11
+ UtilSymbolDefinitionType.WixRemoveRegistryKeyEx.ToString(),
12
+ new[]
13
+ {
14
+ new IntermediateFieldDefinition(nameof(WixRemoveRegistryKeyExSymbolFields.ComponentRef), IntermediateFieldType.String),
15
+ new IntermediateFieldDefinition(nameof(WixRemoveRegistryKeyExSymbolFields.Root), IntermediateFieldType.Number),
16
+ new IntermediateFieldDefinition(nameof(WixRemoveRegistryKeyExSymbolFields.Key), IntermediateFieldType.String),
17
+ new IntermediateFieldDefinition(nameof(WixRemoveRegistryKeyExSymbolFields.InstallMode), IntermediateFieldType.Number),
18
+ new IntermediateFieldDefinition(nameof(WixRemoveRegistryKeyExSymbolFields.Condition), IntermediateFieldType.String),
19
+ },
20
+ typeof(WixRemoveRegistryKeyExSymbol));
21
+ }
22
+}
23
+
24
+namespace WixToolset.Util.Symbols
25
+{
26
+ using WixToolset.Data;
27
+ using WixToolset.Data.Symbols;
28
+
29
+ public enum WixRemoveRegistryKeyExSymbolFields
30
+ {
31
+ ComponentRef,
32
+ Root,
33
+ Key,
34
+ InstallMode,
35
+ Condition,
36
+ }
37
+
38
+ public enum WixRemoveRegistryKeyExInstallMode
39
+ {
40
+ Install = 1,
41
+ Uninstall = 2,
42
+ }
43
+
44
+ public class WixRemoveRegistryKeyExSymbol : IntermediateSymbol
45
+ {
46
+ public WixRemoveRegistryKeyExSymbol() : base(UtilSymbolDefinitions.WixRemoveRegistryKeyEx, null, null)
47
+ {
48
+ }
49
+
50
+ public WixRemoveRegistryKeyExSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(UtilSymbolDefinitions.WixRemoveRegistryKeyEx, sourceLineNumber, id)
51
+ {
52
+ }
53
+
54
+ public IntermediateField this[WixRemoveRegistryKeyExSymbolFields index] => this.Fields[(int)index];
55
+
56
+ public string ComponentRef
57
+ {
58
+ get => this.Fields[(int)WixRemoveRegistryKeyExSymbolFields.ComponentRef].AsString();
59
+ set => this.Set((int)WixRemoveRegistryKeyExSymbolFields.ComponentRef, value);
60
+ }
61
+
62
+ public RegistryRootType Root
63
+ {
64
+ get => (RegistryRootType)this.Fields[(int)WixRemoveRegistryKeyExSymbolFields.Root].AsNumber();
65
+ set => this.Set((int)WixRemoveRegistryKeyExSymbolFields.Root, (int)value);
66
+ }
67
+
68
+ public string Key
69
+ {
70
+ get => (string)this.Fields[(int)WixRemoveRegistryKeyExSymbolFields.Key];
71
+ set => this.Set((int)WixRemoveRegistryKeyExSymbolFields.Key, value);
72
+ }
73
+
74
+ public WixRemoveRegistryKeyExInstallMode InstallMode
75
+ {
76
+ get => (WixRemoveRegistryKeyExInstallMode)this.Fields[(int)WixRemoveRegistryKeyExSymbolFields.InstallMode].AsNumber();
77
+ set => this.Set((int)WixRemoveRegistryKeyExSymbolFields.InstallMode, (int)value);
78
+ }
79
+
80
+ public string Condition
81
+ {
82
+ get => this.Fields[(int)WixRemoveRegistryKeyExSymbolFields.Condition].AsString();
83
+ set => this.Set((int)WixRemoveRegistryKeyExSymbolFields.Condition, value);
84
+ }
85
+ }
86
+}
src/wixext/UtilCompiler.cs
+97
-6
@@ -35,12 +35,6 @@ namespace WixToolset.Util
35
internal const int UserDontCreateUser = 0x00000200;
36
internal const int UserNonVital = 0x00000400;
37
38
- internal enum WixRegistrySearchFormat
39
- {
40
- Raw,
41
- Compatible,
42
- }
43
-
38
private static readonly Regex FindPropertyBrackets = new Regex(@"\[(?!\\|\])|(?<!\[\\\]|\[\\|\\\[)\]", RegexOptions.ExplicitCapture | RegexOptions.Compiled);
39
40
public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/util";
@@ -135,6 +129,9 @@ namespace WixToolset.Util
129
case "RemoveFolderEx":
130
this.ParseRemoveFolderExElement(intermediate, section, element, componentId);
131
break;
132
+ case "RemoveRegistryKey":
133
+ this.ParseRemoveRegistryKeyExElement(intermediate, section, element, componentId);
134
+ break;
135
case "RestartResource":
136
this.ParseRestartResourceElement(intermediate, section, element, componentId);
137
break;
@@ -2887,6 +2884,100 @@ namespace WixToolset.Util
2884
}
2885
}
2886
2887
+ /// <summary>
2888
+ /// Parses a RemoveRegistryKeyEx element.
2889
+ /// </summary>
2890
+ /// <param name="node">Element to parse.</param>
2891
+ /// <param name="componentId">Identifier of parent component.</param>
2892
+ private void ParseRemoveRegistryKeyExElement(Intermediate intermediate, IntermediateSection section, XElement element, string componentId)
2893
+ {
2894
+ var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
2895
+ Identifier id = null;
2896
+ var mode = WixRemoveRegistryKeyExInstallMode.Uninstall;
2897
+ string condition = null;
2898
+ RegistryRootType? root = null;
2899
+ string key = null;
2900
+
2901
+ foreach (var attrib in element.Attributes())
2902
+ {
2903
+ if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
2904
+ {
2905
+ switch (attrib.Name.LocalName)
2906
+ {
2907
+ case "Condition":
2908
+ condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
2909
+ break;
2910
+ case "Id":
2911
+ id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib);
2912
+ break;
2913
+ case "On":
2914
+ var actionValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
2915
+ switch (actionValue)
2916
+ {
2917
+ case "":
2918
+ break;
2919
+ case "install":
2920
+ mode = WixRemoveRegistryKeyExInstallMode.Install;
2921
+ break;
2922
+ case "uninstall":
2923
+ mode = WixRemoveRegistryKeyExInstallMode.Uninstall;
2924
+ break;
2925
+ default:
2926
+ this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "On", actionValue, "install", "uninstall"));
2927
+ break;
2928
+ }
2929
+ break;
2930
+ case "Root":
2931
+ root = this.ParseHelper.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, false);
2932
+ break;
2933
+ case "Key":
2934
+ key = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
2935
+ break;
2936
+ default:
2937
+ this.ParseHelper.UnexpectedAttribute(element, attrib);
2938
+ break;
2939
+ }
2940
+ }
2941
+ else
2942
+ {
2943
+ this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib);
2944
+ }
2945
+ }
2946
+
2947
+ if (!root.HasValue)
2948
+ {
2949
+ this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Root"));
2950
+ }
2951
+
2952
+ if (key == null)
2953
+ {
2954
+ this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Key"));
2955
+ }
2956
+
2957
+ if (id == null)
2958
+ {
2959
+ id = this.ParseHelper.CreateIdentifier("rrx", componentId, condition, root.ToString(), key, mode.ToString());
2960
+ }
2961
+
2962
+ this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element);
2963
+
2964
+ if (!this.Messaging.EncounteredError)
2965
+ {
2966
+ this.ParseHelper.EnsureTable(section, sourceLineNumbers, "Registry");
2967
+ this.ParseHelper.EnsureTable(section, sourceLineNumbers, "RemoveRegistry");
2968
+ this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4RemoveRegistryKeysEx", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64);
2969
+
2970
+ section.AddSymbol(new WixRemoveRegistryKeyExSymbol(sourceLineNumbers, id)
2971
+ {
2972
+ ComponentRef = componentId,
2973
+ Root = root.Value,
2974
+ Key = key,
2975
+ InstallMode = mode,
2976
+ Condition = condition
2977
+ });
2978
+ }
2979
+ }
2980
+
2981
/// <summary>
2982
/// Parses a RestartResource element.
2983
/// </summary>
src/wixext/UtilTableDefinitions.cs
+16
@@ -38,6 +38,21 @@ namespace WixToolset.Util
38
symbolIdIsPrimaryKey: true
39
);
40
41
+ public static readonly TableDefinition Wix4RemoveRegistryKeyEx = new TableDefinition(
42
+ "Wix4RemoveRegistryKeyEx",
43
+ UtilSymbolDefinitions.WixRemoveRegistryKeyEx,
44
+ new[]
45
+ {
46
+ new ColumnDefinition("Wix4RemoveRegistryKeyEx", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, description: "Identifier for the Wix4RemoveRegistryKeyEx row in the package.", modularizeType: ColumnModularizeType.Column),
47
+ new ColumnDefinition("Component_", ColumnType.String, 72, primaryKey: false, nullable: false, ColumnCategory.Identifier, keyTable: "Component", keyColumn: 1, description: "Foreign key into the Component table used to determine install state", modularizeType: ColumnModularizeType.Column),
48
+ new ColumnDefinition("Root", ColumnType.Number, 2, primaryKey: false, nullable: false, ColumnCategory.Unknown, minValue: -1, maxValue: 3, description: "The predefined root key for the registry value, one of rrkEnum."),
49
+ new ColumnDefinition("Key", ColumnType.Localized, 255, primaryKey: false, nullable: false, ColumnCategory.RegPath, description: "The key for the registry value.", modularizeType: ColumnModularizeType.Property),
50
+ new ColumnDefinition("InstallMode", ColumnType.Number, 2, primaryKey: false, nullable: false, ColumnCategory.Unknown, minValue: 1, maxValue: 3, description: "1 == Remove only when the associated component is being installed (msiInstallStateLocal or msiInstallStateSource), 2 == Remove only when the associated component is being removed (msiInstallStateAbsent), 3 = Remove in either of the above cases."),
51
+ new ColumnDefinition("Condition", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Condition, description: "Optional expression to control whether the registry key is removed.", modularizeType: ColumnModularizeType.Condition, forceLocalizable: true),
52
+ },
53
+ symbolIdIsPrimaryKey: true
54
+ );
55
+
56
public static readonly TableDefinition Wix4RestartResource = new TableDefinition(
57
"Wix4RestartResource",
58
UtilSymbolDefinitions.WixRestartResource,
@@ -281,6 +296,7 @@ namespace WixToolset.Util
296
{
297
Wix4CloseApplication,
298
Wix4RemoveFolderEx,
299
+ Wix4RemoveRegistryKeyEx,
300
Wix4RestartResource,
301
Wix4FileShare,
302
Wix4FileSharePermissions,
src/wixlib/UtilExtension_Platform.wxi
+8
@@ -44,6 +44,14 @@
44
</InstallExecuteSequence>
45
</Fragment>
46
47
+ <Fragment>
48
+ <CustomAction Id="$(var.Prefix)RemoveRegistryKeysEx$(var.Suffix)" DllEntry="WixRemoveRegistryKeysEx" Execute="immediate" Return="ignore" BinaryRef="$(var.Prefix)UtilCA$(var.Suffix)" />
49
+
50
+ <InstallExecuteSequence>
51
+ <Custom Action="$(var.Prefix)RemoveRegistryKeysEx$(var.Suffix)" Before="RemoveRegistryValues" />
52
+ </InstallExecuteSequence>
53
+ </Fragment>
54
+
55
<Fragment>
56
<CustomAction Id="$(var.Prefix)BroadcastSettingChange$(var.Suffix)" DllEntry="WixBroadcastSettingChange" Execute="immediate" Return="ignore" SuppressModularization="yes" BinaryRef="$(var.Prefix)UtilCA$(var.Suffix)" />
57