main
cs 3,565 lines 167 KB
Raw
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.Converters
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Text;
11 using System.Text.RegularExpressions;
12 using System.Xml;
13 using System.Xml.Linq;
14 using System.Xml.XPath;
15 using WixToolset.Data;
16 using WixToolset.Data.WindowsInstaller;
17 using WixToolset.Extensibility.Services;
18
19 /// <summary>
20 /// How to convert CustomTable elements.
21 /// </summary>
22 public enum CustomTableTarget
23 {
24 /// <summary>
25 /// Ambiguous elements will be left alone.
26 /// </summary>
27 Unknown,
28
29 /// <summary>
30 /// Use CustomTable, CustomTableRef, and Unreal.
31 /// </summary>
32 Msi,
33
34 /// <summary>
35 /// Use BundleCustomData and BundleCustomDataRef.
36 /// </summary>
37 Bundle,
38 }
39
40 /// <summary>
41 /// WiX source code converter.
42 /// </summary>
43 public sealed class WixConverter
44 {
45 private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
46 private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
47
48 private const char XDocumentNewLine = '\n'; // XDocument normalizes "\r\n" to just "\n".
49 private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
50 private static readonly XNamespace Wix3Namespace = "http://schemas.microsoft.com/wix/2006/wi";
51 private static readonly XNamespace WixBalNamespace = "http://wixtoolset.org/schemas/v4/wxs/bal";
52 private static readonly XNamespace WixDependencyNamespace = "http://wixtoolset.org/schemas/v4/wxs/dependency";
53 private static readonly XNamespace WixDirectXNamespace = "http://wixtoolset.org/schemas/v4/wxs/directx";
54 private static readonly XNamespace WixFirewallNamespace = "http://wixtoolset.org/schemas/v4/wxs/firewall";
55 private static readonly XNamespace WixIisNamespace = "http://wixtoolset.org/schemas/v4/wxs/iis";
56 private static readonly XNamespace WixUiNamespace = "http://wixtoolset.org/schemas/v4/wxs/ui";
57 private static readonly XNamespace WixUtilNamespace = "http://wixtoolset.org/schemas/v4/wxs/util";
58 private static readonly XNamespace WixVSNamespace = "http://wixtoolset.org/schemas/v4/wxs/vs";
59 private static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl";
60 private static readonly XNamespace Wxl3Namespace = "http://schemas.microsoft.com/wix/2006/localization";
61
62 private static readonly XName AdminExecuteSequenceElementName = WixNamespace + "AdminExecuteSequence";
63 private static readonly XName AdminUISequenceSequenceElementName = WixNamespace + "AdminUISequence";
64 private static readonly XName AdvertiseExecuteSequenceElementName = WixNamespace + "AdvertiseExecuteSequence";
65 private static readonly XName InstallExecuteSequenceElementName = WixNamespace + "InstallExecuteSequence";
66 private static readonly XName InstallUISequenceSequenceElementName = WixNamespace + "InstallUISequence";
67 private static readonly XName BootstrapperApplicationElementName = WixNamespace + "BootstrapperApplication";
68 private static readonly XName BootstrapperApplicationDllElementName = WixNamespace + "BootstrapperApplicationDll";
69 private static readonly XName BootstrapperApplicationRefElementName = WixNamespace + "BootstrapperApplicationRef";
70 private static readonly XName ApprovedExeForElevationElementName = WixNamespace + "ApprovedExeForElevation";
71 private static readonly XName BundleAttributeElementName = WixNamespace + "BundleAttribute";
72 private static readonly XName BundleAttributeDefinitionElementName = WixNamespace + "BundleAttributeDefinition";
73 private static readonly XName BundleCustomDataElementName = WixNamespace + "BundleCustomData";
74 private static readonly XName BundleCustomDataRefElementName = WixNamespace + "BundleCustomDataRef";
75 private static readonly XName BundleElementElementName = WixNamespace + "BundleElement";
76 private static readonly XName CustomElementName = WixNamespace + "Custom";
77 private static readonly XName CustomTableElementName = WixNamespace + "CustomTable";
78 private static readonly XName CustomTableRefElementName = WixNamespace + "CustomTableRef";
79 private static readonly XName CatalogElementName = WixNamespace + "Catalog";
80 private static readonly XName CertificateElementName = WixIisNamespace + "Certificate";
81 private static readonly XName ColumnElementName = WixNamespace + "Column";
82 private static readonly XName ComponentElementName = WixNamespace + "Component";
83 private static readonly XName ControlElementName = WixNamespace + "Control";
84 private static readonly XName ConditionElementName = WixNamespace + "Condition";
85 private static readonly XName CreateFolderElementName = WixNamespace + "CreateFolder";
86 private static readonly XName DataElementName = WixNamespace + "Data";
87 private static readonly XName OldProvidesElementName = WixDependencyNamespace + "Provides";
88 private static readonly XName OldRequiresElementName = WixDependencyNamespace + "Requires";
89 private static readonly XName OldRequiresRefElementName = WixDependencyNamespace + "RequiresRef";
90 private static readonly XName DirectoryElementName = WixNamespace + "Directory";
91 private static readonly XName DirectoryRefElementName = WixNamespace + "DirectoryRef";
92 private static readonly XName EmbeddedChainerElementName = WixNamespace + "EmbeddedChainer";
93 private static readonly XName ErrorElementName = WixNamespace + "Error";
94 private static readonly XName FeatureElementName = WixNamespace + "Feature";
95 private static readonly XName FileElementName = WixNamespace + "File";
96 private static readonly XName FragmentElementName = WixNamespace + "Fragment";
97 private static readonly XName FirewallRemoteAddressElementName = WixFirewallNamespace + "RemoteAddress";
98 private static readonly XName LaunchElementName = WixNamespace + "Launch";
99 private static readonly XName LevelElementName = WixNamespace + "Level";
100 private static readonly XName ExePackageElementName = WixNamespace + "ExePackage";
101 private static readonly XName ExePackagePayloadElementName = WixNamespace + "ExePackagePayload";
102 private static readonly XName ModuleElementName = WixNamespace + "Module";
103 private static readonly XName MsiPackageElementName = WixNamespace + "MsiPackage";
104 private static readonly XName MspPackageElementName = WixNamespace + "MspPackage";
105 private static readonly XName MsuPackageElementName = WixNamespace + "MsuPackage";
106 private static readonly XName MsuPackagePayloadElementName = WixNamespace + "MsuPackagePayload";
107 private static readonly XName PackageElementName = WixNamespace + "Package";
108 private static readonly XName PayloadElementName = WixNamespace + "Payload";
109 private static readonly XName PermissionExElementName = WixNamespace + "PermissionEx";
110 private static readonly XName ProductElementName = WixNamespace + "Product";
111 private static readonly XName ProgressTextElementName = WixNamespace + "ProgressText";
112 private static readonly XName PropertyRefElementName = WixNamespace + "PropertyRef";
113 private static readonly XName PublishElementName = WixNamespace + "Publish";
114 private static readonly XName ProvidesElementName = WixNamespace + "Provides";
115 private static readonly XName RequiresElementName = WixNamespace + "Requires";
116 private static readonly XName RequiresRefElementName = WixNamespace + "RequiresRef";
117 private static readonly XName MultiStringValueElementName = WixNamespace + "MultiStringValue";
118 private static readonly XName RelatedBundleElementName = WixNamespace + "RelatedBundle";
119 private static readonly XName RemotePayloadElementName = WixNamespace + "RemotePayload";
120 private static readonly XName RegistryKeyElementName = WixNamespace + "RegistryKey";
121 private static readonly XName RegistrySearchElementName = WixNamespace + "RegistrySearch";
122 private static readonly XName RequiredPrivilegeElementName = WixNamespace + "RequiredPrivilege";
123 private static readonly XName RowElementName = WixNamespace + "Row";
124 private static readonly XName ServiceArgumentElementName = WixNamespace + "ServiceArgument";
125 private static readonly XName SetDirectoryElementName = WixNamespace + "SetDirectory";
126 private static readonly XName SetPropertyElementName = WixNamespace + "SetProperty";
127 private static readonly XName ShortcutPropertyElementName = WixNamespace + "ShortcutProperty";
128 private static readonly XName SoftwareTagElementName = WixNamespace + "SoftwareTag";
129 private static readonly XName SoftwareTagRefElementName = WixNamespace + "SoftwareTagRef";
130 private static readonly XName StandardDirectoryElementName = WixNamespace + "StandardDirectory";
131 private static readonly XName TagElementName = XNamespace.None + "Tag";
132 private static readonly XName TagRefElementName = XNamespace.None + "TagRef";
133 private static readonly XName TextElementName = WixNamespace + "Text";
134 private static readonly XName UITextElementName = WixNamespace + "UIText";
135 private static readonly XName VariableElementName = WixNamespace + "Variable";
136 private static readonly XName VerbElementName = WixNamespace + "Verb";
137 private static readonly XName BalConditionElementName = WixBalNamespace + "Condition";
138 private static readonly XName BalPrereqLicenseUrlAttributeName = WixBalNamespace + "PrereqLicenseUrl";
139 private static readonly XName BalPrereqPackageAttributeName = WixBalNamespace + "PrereqPackage";
140 private static readonly XName BalUseUILanguagesName = WixBalNamespace + "UseUILanguages";
141 private static readonly XName BalStandardBootstrapperApplicationName = WixBalNamespace + "WixStandardBootstrapperApplication";
142 private static readonly XName BalManagedBootstrapperApplicationHostName = WixBalNamespace + "WixManagedBootstrapperApplicationHost";
143 private static readonly XName BalOldDotNetCoreBootstrapperApplicationName = WixBalNamespace + "WixDotNetCoreBootstrapperApplication";
144 private static readonly XName BalNewDotNetCoreBootstrapperApplicationName = WixBalNamespace + "WixDotNetCoreBootstrapperApplicationHost";
145 private static readonly XName UtilCloseApplicationElementName = WixUtilNamespace + "CloseApplication";
146 private static readonly XName UtilPermissionExElementName = WixUtilNamespace + "PermissionEx";
147 private static readonly XName UtilRegistrySearchName = WixUtilNamespace + "RegistrySearch";
148 private static readonly XName UtilXmlConfigElementName = WixUtilNamespace + "XmlConfig";
149 private static readonly XName CustomActionElementName = WixNamespace + "CustomAction";
150 private static readonly XName CustomActionRefElementName = WixNamespace + "CustomActionRef";
151 private static readonly XName UIRefElementName = WixNamespace + "UIRef";
152 private static readonly XName PropertyElementName = WixNamespace + "Property";
153 private static readonly XName Wix4ElementName = WixNamespace + "Wix";
154 private static readonly XName Wix3ElementName = Wix3Namespace + "Wix";
155 private static readonly XName WixElementWithoutNamespaceName = XNamespace.None + "Wix";
156 private static readonly XName WixVariableElementName = WixNamespace + "WixVariable";
157 private static readonly XName Include4ElementName = WixNamespace + "Include";
158 private static readonly XName Include3ElementName = Wix3Namespace + "Include";
159 private static readonly XName IncludeElementWithoutNamespaceName = XNamespace.None + "Include";
160 private static readonly XName SummaryInformationElementName = WixNamespace + "SummaryInformation";
161 private static readonly XName MediaTemplateElementName = WixNamespace + "MediaTemplate";
162
163 private static readonly XName DependencyCheckAttributeName = WixDependencyNamespace + "Check";
164 private static readonly XName DependencyEnforceAttributeName = WixDependencyNamespace + "Enforce";
165
166 private static readonly XName WixLocalization4ElementName = WxlNamespace + "WixLocalization";
167 private static readonly XName WixLocalizationStringElementName = WxlNamespace + "String";
168 private static readonly XName WixLocalizationUIElementName = WxlNamespace + "UI";
169 private static readonly XName WixLocalization3ElementName = Wxl3Namespace + "WixLocalization";
170 private static readonly XName WixLocalizationElementWithoutNamespaceName = XNamespace.None + "WixLocalization";
171
172 private static readonly Dictionary<string, XNamespace> OldToNewNamespaceMapping = new Dictionary<string, XNamespace>()
173 {
174 { "http://schemas.microsoft.com/wix/BalExtension", WixBalNamespace },
175 { "http://schemas.microsoft.com/wix/ComPlusExtension", "http://wixtoolset.org/schemas/v4/wxs/complus" },
176 { "http://schemas.microsoft.com/wix/DependencyExtension", WixDependencyNamespace },
177 { "http://schemas.microsoft.com/wix/DifxAppExtension", "http://wixtoolset.org/schemas/v4/wxs/difxapp" },
178 { "http://schemas.microsoft.com/wix/FirewallExtension", WixFirewallNamespace },
179 { "http://schemas.microsoft.com/wix/HttpExtension", "http://wixtoolset.org/schemas/v4/wxs/http" },
180 { "http://schemas.microsoft.com/wix/IIsExtension", WixIisNamespace },
181 { "http://schemas.microsoft.com/wix/MsmqExtension", "http://wixtoolset.org/schemas/v4/wxs/msmq" },
182 { "http://schemas.microsoft.com/wix/NetFxExtension", "http://wixtoolset.org/schemas/v4/wxs/netfx" },
183 { "http://schemas.microsoft.com/wix/PSExtension", "http://wixtoolset.org/schemas/v4/wxs/powershell" },
184 { "http://schemas.microsoft.com/wix/SqlExtension", "http://wixtoolset.org/schemas/v4/wxs/sql" },
185 { "http://schemas.microsoft.com/wix/TagExtension", XNamespace.None },
186 { "http://schemas.microsoft.com/wix/UtilExtension", WixUtilNamespace },
187 { "http://schemas.microsoft.com/wix/VSExtension", WixVSNamespace },
188 { "http://wixtoolset.org/schemas/thmutil/2010", "http://wixtoolset.org/schemas/v4/thmutil" },
189 { "http://schemas.microsoft.com/wix/2009/Lux", "http://wixtoolset.org/schemas/v4/lux" },
190 { "http://schemas.microsoft.com/wix/2006/wi", "http://wixtoolset.org/schemas/v4/wxs" },
191 { "http://schemas.microsoft.com/wix/2006/localization", "http://wixtoolset.org/schemas/v4/wxl" },
192 { "http://schemas.microsoft.com/wix/2006/libraries", "http://wixtoolset.org/schemas/v4/wixlib" },
193 { "http://schemas.microsoft.com/wix/2006/objects", "http://wixtoolset.org/schemas/v4/wixobj" },
194 { "http://schemas.microsoft.com/wix/2006/outputs", "http://wixtoolset.org/schemas/v4/wixout" },
195 { "http://schemas.microsoft.com/wix/2007/pdbs", "http://wixtoolset.org/schemas/v4/wixpdb" },
196 { "http://schemas.microsoft.com/wix/2003/04/actions", "http://wixtoolset.org/schemas/v4/wi/actions" },
197 { "http://schemas.microsoft.com/wix/2006/tables", "http://wixtoolset.org/schemas/v4/wi/tables" },
198 { "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" },
199 };
200
201 private static readonly Dictionary<string, string> CustomActionIdsWithPlatformSuffix = new Dictionary<string, string>()
202 {
203 { "ConfigureComPlusUninstall", "Wix4ConfigureComPlusUninstall_<PlatformSuffix>" },
204 { "ConfigureComPlusInstall", "Wix4ConfigureComPlusInstall_<PlatformSuffix>" },
205 { "WixDependencyRequire", "Wix4DependencyRequire_<PlatformSuffix>" },
206 { "WixDependencyCheck", "Wix4DependencyCheck_<PlatformSuffix>" },
207 { "WixQueryDirectXCaps", "Wix4QueryDirectXCaps_<PlatformSuffix>" },
208 { "WixSchedFirewallExceptionsUninstall", "Wix5SchedFirewallExceptionsUninstall_<PlatformSuffix>" },
209 { "WixSchedFirewallExceptionsInstall", "Wix5SchedFirewallExceptionsInstall_<PlatformSuffix>" },
210 { "WixSchedHttpUrlReservationsUninstall", "Wix4SchedHttpUrlReservationsUninstall_<PlatformSuffix>" },
211 { "WixSchedHttpUrlReservationsInstall", "Wix4SchedHttpUrlReservationsInstall_<PlatformSuffix>" },
212 { "ConfigureIIs", "Wix4ConfigureIIs_<PlatformSuffix>" },
213 { "UninstallCertificates", "Wix4UninstallCertificates_<PlatformSuffix>" },
214 { "InstallCertificates", "Wix4_<PlatformSuffix>" },
215 { "MessageQueuingUninstall", "Wix4MessageQueuingUninstall_<PlatformSuffix>" },
216 { "MessageQueuingInstall", "Wix4_MessageQueuingInstall<PlatformSuffix>" },
217 { "NetFxScheduleNativeImage", "Wix4NetFxScheduleNativeImage_<PlatformSuffix>" },
218 { "NetFxExecuteNativeImageCommitUninstall", "Wix4NetFxExecuteNativeImageCommitUninstall_<PlatformSuffix>" },
219 { "NetFxExecuteNativeImageUninstall", "Wix4NetFxExecuteNativeImageUninstall_<PlatformSuffix>" },
220 { "NetFxExecuteNativeImageCommitInstall", "Wix4NetFxExecuteNativeImageCommitInstall_<PlatformSuffix>" },
221 { "NetFxExecuteNativeImageInstall", "Wix4NetFxExecuteNativeImageInstall_<PlatformSuffix>" },
222 { "UninstallSqlData", "Wix4UninstallSqlData_<PlatformSuffix>" },
223 { "InstallSqlData", "Wix4InstallSqlData_<PlatformSuffix>" },
224 { "WixCheckRebootRequired", "Wix4CheckRebootRequired_<PlatformSuffix>" },
225 { "WixCloseApplications", "Wix4CloseApplications_<PlatformSuffix>" },
226 { "WixRegisterRestartResources", "Wix4RegisterRestartResources_<PlatformSuffix>" },
227 { "ConfigureUsers", "Wix4ConfigureUsers_<PlatformSuffix>" },
228 { "ConfigureSmbInstall", "Wix4ConfigureSmbInstall_<PlatformSuffix>" },
229 { "ConfigureSmbUninstall", "Wix4ConfigureSmbUninstall_<PlatformSuffix>" },
230 { "InstallPerfCounterData", "Wix4InstallPerfCounterData_<PlatformSuffix>" },
231 { "UninstallPerfCounterData", "Wix4UninstallPerfCounterData_<PlatformSuffix>" },
232 { "ConfigurePerfmonInstall", "Wix4ConfigurePerfmonInstall_<PlatformSuffix>" },
233 { "ConfigurePerfmonUninstall", "Wix4ConfigurePerfmonUninstall_<PlatformSuffix>" },
234 { "ConfigurePerfmonManifestRegister", "Wix4ConfigurePerfmonManifestRegister_<PlatformSuffix>" },
235 { "ConfigurePerfmonManifestUnregister", "Wix4ConfigurePerfmonManifestUnregister_<PlatformSuffix>" },
236 { "ConfigureEventManifestRegister", "Wix4ConfigureEventManifestRegister_<PlatformSuffix>" },
237 { "ConfigureEventManifestUnregister", "Wix4ConfigureEventManifestUnregister_<PlatformSuffix>" },
238 { "SchedServiceConfig", "Wix4SchedServiceConfig_<PlatformSuffix>" },
239 { "SchedXmlFile", "Wix4SchedXmlFile_<PlatformSuffix>" },
240 { "SchedXmlConfig", "Wix4SchedXmlConfig_<PlatformSuffix>" },
241 { "WixSchedInternetShortcuts", "Wix4SchedInternetShortcuts_<PlatformSuffix>" },
242 { "WixRollbackInternetShortcuts", "Wix4RollbackInternetShortcuts_<PlatformSuffix>" },
243 { "WixCreateInternetShortcuts", "Wix4CreateInternetShortcuts_<PlatformSuffix>" },
244 { "WixQueryOsInfo", "Wix4QueryOsInfo_<PlatformSuffix>" },
245 { "WixQueryOsDirs", "Wix4QueryOsDirs_<PlatformSuffix>" },
246 { "WixQueryOsWellKnownSID", "Wix4QueryOsWellKnownSID_<PlatformSuffix>" },
247 { "WixQueryOsDriverInfo", "Wix4QueryOsDriverInfo_<PlatformSuffix>" },
248 { "WixQueryNativeMachine", "Wix4QueryNativeMachine_<PlatformSuffix>" },
249 { "WixFailWhenDeferred", "Wix4FailWhenDeferred_<PlatformSuffix>" },
250 { "WixWaitForEvent", "Wix4WaitForEvent_<PlatformSuffix>" },
251 { "WixWaitForEventDeferred", "Wix4WaitForEventDeferred_<PlatformSuffix>" },
252 { "WixExitEarlyWithSuccess", "Wix4ExitEarlyWithSuccess_<PlatformSuffix>" },
253 { "WixBroadcastSettingChange", "Wix4BroadcastSettingChange_<PlatformSuffix>" },
254 { "WixBroadcastEnvironmentChange", "Wix4BroadcastEnvironmentChange_<PlatformSuffix>" },
255 { "SchedSecureObjects", "Wix4SchedSecureObjects_<PlatformSuffix>" },
256 { "SchedSecureObjectsRollback", "Wix4SchedSecureObjectsRollback_<PlatformSuffix>" },
257 { "VSFindInstances", "Wix4VSFindInstances_<PlatformSuffix>" },
258 };
259
260 private readonly Dictionary<XName, Action<XElement>> ConvertElementMapping;
261 private readonly Regex DeprecatedPrefixRegex = new Regex(@"(?<=(^|[^\$])(\$\$)*)\$(?=\(loc\.[^.].*\))",
262 RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
263
264 /// <summary>
265 /// Instantiate a new Converter class.
266 /// </summary>
267 /// <param name="messaging"></param>
268 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
269 /// <param name="errorsAsWarnings">Test errors to display as warnings.</param>
270 /// <param name="ignoreErrors">Test errors to ignore.</param>
271 /// <param name="customTableTarget">How to convert CustomTable elements.</param>
272 public WixConverter(IMessaging messaging, int indentationAmount, IEnumerable<string> errorsAsWarnings = null, IEnumerable<string> ignoreErrors = null, CustomTableTarget customTableTarget = CustomTableTarget.Unknown)
273 {
274 this.ConvertElementMapping = new Dictionary<XName, Action<XElement>>
275 {
276 { WixConverter.AdminExecuteSequenceElementName, this.ConvertSequenceElement },
277 { WixConverter.AdminUISequenceSequenceElementName, this.ConvertSequenceElement },
278 { WixConverter.AdvertiseExecuteSequenceElementName, this.ConvertSequenceElement },
279 { WixConverter.InstallUISequenceSequenceElementName, this.ConvertSequenceElement },
280 { WixConverter.InstallExecuteSequenceElementName, this.ConvertSequenceElement },
281 { WixConverter.BalConditionElementName, this.ConvertBalConditionElement },
282 { WixConverter.BootstrapperApplicationElementName, this.ConvertBootstrapperApplicationElement },
283 { WixConverter.BootstrapperApplicationRefElementName, this.ConvertBootstrapperApplicationRefElement },
284 { WixConverter.ApprovedExeForElevationElementName, this.ConvertApprovedExeForElevationElement },
285 { WixConverter.CatalogElementName, this.ConvertCatalogElement },
286 { WixConverter.CertificateElementName, this.ConvertCertificateElement },
287 { WixConverter.ColumnElementName, this.ConvertColumnElement },
288 { WixConverter.ComponentElementName, this.ConvertComponentElement },
289 { WixConverter.ControlElementName, this.ConvertControlElement },
290 { WixConverter.CustomElementName, this.ConvertCustomElement },
291 { WixConverter.CustomActionElementName, this.ConvertCustomActionElement },
292 { WixConverter.CustomTableElementName, this.ConvertCustomTableElement },
293 { WixConverter.DataElementName, this.ConvertDataElement },
294 { WixConverter.DirectoryElementName, this.ConvertDirectoryElement },
295 { WixConverter.DirectoryRefElementName, this.ConvertDirectoryRefElement },
296 { WixConverter.FeatureElementName, this.ConvertFeatureElement },
297 { WixConverter.FileElementName, this.ConvertFileElement },
298 { WixConverter.ConditionElementName, this.ConvertLaunchConditionElement },
299 { WixConverter.FirewallRemoteAddressElementName, this.ConvertFirewallRemoteAddressElement },
300 { WixConverter.EmbeddedChainerElementName, this.ConvertEmbeddedChainerElement },
301 { WixConverter.ErrorElementName, this.ConvertErrorElement },
302 { WixConverter.ExePackageElementName, this.ConvertExePackageElement },
303 { WixConverter.ModuleElementName, this.ConvertModuleElement },
304 { WixConverter.MsiPackageElementName, this.ConvertWindowsInstallerPackageElement },
305 { WixConverter.MspPackageElementName, this.ConvertWindowsInstallerPackageElement },
306 { WixConverter.MsuPackageElementName, this.ConvertMsuPackageElement },
307 { WixConverter.OldProvidesElementName, this.ConvertProvidesElement },
308 { WixConverter.OldRequiresElementName, this.ConvertRequiresElement },
309 { WixConverter.OldRequiresRefElementName, this.ConvertRequiresRefElement },
310 { WixConverter.PayloadElementName, this.ConvertSuppressSignatureVerification },
311 { WixConverter.PermissionExElementName, this.ConvertPermissionExElement },
312 { WixConverter.ProductElementName, this.ConvertProductElement },
313 { WixConverter.ProgressTextElementName, this.ConvertProgressTextElement },
314 { WixConverter.PropertyRefElementName, this.ConvertPropertyRefElement },
315 { WixConverter.PublishElementName, this.ConvertPublishElement },
316 { WixConverter.MultiStringValueElementName, this.ConvertMultiStringValueElement },
317 { WixConverter.RegistryKeyElementName, this.ConvertRegistryKeyElement },
318 { WixConverter.RegistrySearchElementName, this.ConvertRegistrySearchElement },
319 { WixConverter.RelatedBundleElementName, this.ConvertRelatedBundleElement },
320 { WixConverter.RemotePayloadElementName, this.ConvertRemotePayloadElement },
321 { WixConverter.RequiredPrivilegeElementName, this.ConvertRequiredPrivilegeElement },
322 { WixConverter.CustomActionRefElementName, this.ConvertCustomActionRefElement },
323 { WixConverter.ServiceArgumentElementName, this.ConvertServiceArgumentElement },
324 { WixConverter.SetDirectoryElementName, this.ConvertSetDirectoryElement },
325 { WixConverter.SetPropertyElementName, this.ConvertSetPropertyElement },
326 { WixConverter.ShortcutPropertyElementName, this.ConvertShortcutPropertyElement },
327 { WixConverter.TagElementName, this.ConvertTagElement },
328 { WixConverter.TagRefElementName, this.ConvertTagRefElement },
329 { WixConverter.TextElementName, this.ConvertTextElement },
330 { WixConverter.UITextElementName, this.ConvertUITextElement },
331 { WixConverter.VariableElementName, this.ConvertVariableElement },
332 { WixConverter.UtilCloseApplicationElementName, this.ConvertUtilCloseApplicationElementName },
333 { WixConverter.UtilPermissionExElementName, this.ConvertUtilPermissionExElement },
334 { WixConverter.UtilRegistrySearchName, this.ConvertUtilRegistrySearchElement },
335 { WixConverter.UtilXmlConfigElementName, this.ConvertUtilXmlConfigElement },
336 { WixConverter.PropertyElementName, this.ConvertPropertyElement },
337 { WixConverter.WixElementWithoutNamespaceName, this.ConvertElementWithoutNamespace },
338 { WixConverter.IncludeElementWithoutNamespaceName, this.ConvertElementWithoutNamespace },
339 { WixConverter.VerbElementName, this.ConvertVerbElement },
340 { WixConverter.UIRefElementName, this.ConvertUIRefElement },
341 { WixConverter.WixLocalizationElementWithoutNamespaceName, this.ConvertWixLocalizationElementWithoutNamespace },
342 { WixConverter.WixLocalizationStringElementName, this.ConvertWixLocalizationStringElement},
343 { WixConverter.WixLocalizationUIElementName, this.ConvertWixLocalizationUIElement},
344 };
345
346 this.Messaging = messaging;
347
348 this.IndentationAmount = indentationAmount;
349
350 this.ErrorsAsWarnings = new HashSet<ConverterTestType>(this.YieldConverterTypes(errorsAsWarnings));
351
352 this.IgnoreErrors = new HashSet<ConverterTestType>(this.YieldConverterTypes(ignoreErrors));
353
354 this.CustomTableSetting = customTableTarget;
355 }
356
357 private CustomTableTarget CustomTableSetting { get; }
358
359 private List<Message> ConversionMessages
360 {
361 get { return this.State.ConversionMessages; }
362 }
363
364 private ConversionState State { get; set; }
365
366 private HashSet<ConverterTestType> ErrorsAsWarnings { get; set; }
367
368 private HashSet<ConverterTestType> IgnoreErrors { get; set; }
369
370 private IMessaging Messaging { get; }
371
372 private int IndentationAmount { get; set; }
373
374 private ConvertOperation Operation
375 {
376 get { return this.State.Operation; }
377 }
378
379 private string SourceFile
380 {
381 get { return this.State.SourceFile; }
382 }
383
384 private int SourceVersion
385 {
386 get { return this.State.SourceVersion; }
387 set { this.State.SourceVersion = value; }
388 }
389
390 private XElement XRoot
391 {
392 get { return this.State.XDocument.Root; }
393 }
394
395 /// <summary>
396 /// Convert a file.
397 /// </summary>
398 /// <param name="sourceFile">The file to convert.</param>
399 /// <param name="saveConvertedFile">Option to save the converted Messages that are found.</param>
400 /// <returns>The number of conversions found.</returns>
401 public int ConvertFile(string sourceFile, bool saveConvertedFile)
402 {
403 var savedDocument = false;
404
405 if (this.TryOpenSourceFile(ConvertOperation.Convert, sourceFile))
406 {
407 this.DoIt(this.State.XDocument);
408
409 // Fix Messages if requested and necessary.
410 if (saveConvertedFile && 0 < this.ConversionMessages.Count)
411 {
412 savedDocument = this.SaveDocument(this.State.XDocument);
413 }
414 }
415
416 return this.ReportMessages(this.State.XDocument, savedDocument);
417 }
418
419 /// <summary>
420 /// Convert a document.
421 /// </summary>
422 /// <param name="document">The document to convert.</param>
423 /// <param name="sourceFile">The file that the document was loaded from.</param>
424 /// <returns>The number of conversions found.</returns>
425 public int ConvertDocument(XDocument document, string sourceFile = "InMemoryXml")
426 {
427 this.State = new ConversionState(ConvertOperation.Convert, sourceFile);
428 this.State.Initialize(document);
429 this.DoIt(document);
430
431 return this.ReportMessages(document, false);
432 }
433
434 /// <summary>
435 /// Format a file.
436 /// </summary>
437 /// <param name="sourceFile">The file to format.</param>
438 /// <param name="saveConvertedFile">Option to save the format Messages that are found.</param>
439 /// <returns>The number of Messages found.</returns>
440 public int FormatFile(string sourceFile, bool saveConvertedFile)
441 {
442 var savedDocument = false;
443
444 if (this.TryOpenSourceFile(ConvertOperation.Format, sourceFile))
445 {
446 this.FormatDocument(this.State.XDocument, sourceFile);
447
448 // Fix Messages if requested and necessary.
449 if (saveConvertedFile && 0 < this.ConversionMessages.Count)
450 {
451 savedDocument = this.SaveDocument(this.State.XDocument);
452 }
453 }
454
455 return this.ReportMessages(this.State.XDocument, savedDocument);
456 }
457
458 /// <summary>
459 /// Format a document.
460 /// </summary>
461 /// <param name="document">The document to format.</param>
462 /// <param name="sourceFile">The file that the document was loaded from.</param>
463 /// <returns>The number of Messages found.</returns>
464 public int FormatDocument(XDocument document, string sourceFile = "InMemoryXml")
465 {
466 this.State = new ConversionState(ConvertOperation.Format, sourceFile);
467 this.State.Initialize(document);
468 this.DoIt(document);
469
470 return this.ReportMessages(document, false);
471 }
472
473 private void DoIt(XDocument document)
474 {
475 // Remove the declaration.
476 if (null != document.Declaration
477 && this.OnInformation(ConverterTestType.DeclarationPresent, document, "This file contains an XML declaration on the first line."))
478 {
479 document.Declaration = null;
480 TrimLeadingText(document);
481 }
482
483 // Start converting the nodes at the top.
484 this.ConvertNodes(document.Nodes(), 0);
485 this.ConvertMbaPrereqVariables();
486 this.RemoveUnusedNamespaces(document.Root);
487 this.MoveNamespacesToRoot(document.Root);
488 }
489
490 private bool TryOpenSourceFile(ConvertOperation operation, string sourceFile)
491 {
492 this.State = new ConversionState(operation, sourceFile);
493
494 try
495 {
496 this.State.Initialize();
497 return true;
498 }
499 catch (XmlException e)
500 {
501 this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message);
502 return false;
503 }
504 }
505
506 private bool SaveDocument(XDocument document)
507 {
508 var ignoreDeclarationError = this.IgnoreErrors.Contains(ConverterTestType.DeclarationPresent);
509
510 try
511 {
512 using (var writer = XmlWriter.Create(this.SourceFile, new XmlWriterSettings { OmitXmlDeclaration = !ignoreDeclarationError }))
513 {
514 document.Save(writer);
515 }
516
517 return true;
518 }
519 catch (UnauthorizedAccessException)
520 {
521 this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file.");
522 }
523
524 return false;
525 }
526
527 private void ConvertNodes(IEnumerable<XNode> nodes, int level)
528 {
529 // Note we operate on a copy of the node list since we may
530 // remove some whitespace nodes during this processing.
531 foreach (var node in nodes.ToList())
532 {
533 if (node is XText text)
534 {
535 if (null != text.Value)
536 {
537 if (this.TryFixDeprecatedLocalizationPrefixes(node, text.Value, out var newValue, ConverterTestType.DeprecatedLocalizationVariablePrefixInTextValue))
538 {
539 text.Value = newValue;
540 }
541 }
542 if (!String.IsNullOrWhiteSpace(text.Value))
543 {
544 text.Value = text.Value.Trim();
545 }
546 else if (node.NextNode is XCData)
547 {
548 this.EnsurePrecedingWhitespaceRemoved(text, node, ConverterTestType.WhitespacePrecedingNodeWrong);
549 }
550 else if (node.NextNode is XElement)
551 {
552 this.EnsurePrecedingWhitespaceCorrect(text, node, level, ConverterTestType.WhitespacePrecedingNodeWrong);
553 }
554 else if (node.NextNode is null) // this is the space before the close element
555 {
556 if (node.PreviousNode is null || node.PreviousNode is XCData)
557 {
558 this.EnsurePrecedingWhitespaceRemoved(text, node.Parent, ConverterTestType.WhitespacePrecedingEndElementWrong);
559 }
560 else if (level == 0) // root element's close tag
561 {
562 this.EnsurePrecedingWhitespaceCorrect(text, node, 0, ConverterTestType.WhitespacePrecedingEndElementWrong);
563 }
564 else
565 {
566 this.EnsurePrecedingWhitespaceCorrect(text, node, level - 1, ConverterTestType.WhitespacePrecedingEndElementWrong);
567 }
568 }
569 }
570 else if (node is XElement element)
571 {
572 this.ConvertElement(element);
573 var before = element.Nodes().ToList();
574 this.ConvertNodes(before, level + 1);
575
576 // If any nodes were added during the processing of the children,
577 // ensure those added children get processed as well.
578 var added = element.Nodes().Except(before).ToList();
579
580 if (added.Any())
581 {
582 this.ConvertNodes(added, level + 1);
583 }
584 }
585 }
586 }
587
588 private bool TryFixDeprecatedLocalizationPrefixes(XNode node, string value, out string newValue, ConverterTestType testType)
589 {
590 newValue = this.DeprecatedPrefixRegex.Replace(value, "!");
591
592 if (Object.ReferenceEquals(newValue, value))
593 {
594 return false;
595 }
596
597 var message = testType == ConverterTestType.DeprecatedLocalizationVariablePrefixInTextValue ? "The prefix on the localization variable in the inner text is incorrect." : "The prefix on the localization variable in the attribute value is incorrect.";
598
599 return this.OnInformation(testType, node, message);
600 }
601
602 private void EnsurePrecedingWhitespaceCorrect(XText whitespace, XNode node, int level, ConverterTestType testType)
603 {
604 if (!WixConverter.LeadingWhitespaceValid(this.IndentationAmount, level, whitespace.Value))
605 {
606 var message = testType == ConverterTestType.WhitespacePrecedingEndElementWrong ? "The whitespace preceding this end element is incorrect." : "The whitespace preceding this comment is incorrect.";
607
608 if (this.OnInformation(testType, node, message))
609 {
610 WixConverter.FixupWhitespace(this.IndentationAmount, level, whitespace);
611 }
612 }
613 }
614
615 private void EnsurePrecedingWhitespaceRemoved(XText whitespace, XNode node, ConverterTestType testType)
616 {
617 if (!String.IsNullOrEmpty(whitespace.Value) && whitespace.NodeType != XmlNodeType.CDATA)
618 {
619 var message = testType == ConverterTestType.WhitespacePrecedingEndElementWrong ? "The whitespace preceding this end element is incorrect." : "The whitespace preceding this comment is incorrect.";
620
621 if (this.OnInformation(testType, node, message))
622 {
623 whitespace.Remove();
624 }
625 }
626 }
627
628 private void ConvertElement(XElement element)
629 {
630 var deprecatedToUpdatedNamespaces = new Dictionary<XNamespace, XNamespace>();
631
632 foreach (var attribute in element.Attributes())
633 {
634 if (attribute.IsNamespaceDeclaration)
635 {
636 // Gather any deprecated namespaces, then update this element tree based on those deprecations.
637 var declaration = attribute;
638
639 if (element.Name == Wix3ElementName || element.Name == Include3ElementName || element.Name == WixLocalization3ElementName)
640 {
641 this.SourceVersion = 3;
642 }
643 else if (element.Name == Wix4ElementName || element.Name == Include4ElementName || element.Name == WixLocalization4ElementName)
644 {
645 this.SourceVersion = 4;
646 }
647
648 if (WixConverter.OldToNewNamespaceMapping.TryGetValue(declaration.Value, out var ns))
649 {
650 if (this.OnInformation(ConverterTestType.XmlnsValueWrong, declaration, "The namespace '{0}' is out of date. It must be '{1}'.", declaration.Value, ns.NamespaceName))
651 {
652 deprecatedToUpdatedNamespaces.Add(declaration.Value, ns);
653 }
654 }
655 }
656 else
657 {
658 if (null != attribute.Value)
659 {
660 if (this.TryFixDeprecatedLocalizationPrefixes(element, attribute.Value, out var newValue, ConverterTestType.DeprecatedLocalizationVariablePrefixInAttributeValue))
661 {
662 attribute.Value = newValue;
663 }
664 }
665 }
666 }
667
668 if (deprecatedToUpdatedNamespaces.Any())
669 {
670 WixConverter.UpdateElementsWithDeprecatedNamespaces(element.DescendantsAndSelf(), deprecatedToUpdatedNamespaces);
671 }
672
673 // Apply any specialized conversion actions.
674 if (this.ConvertElementMapping.TryGetValue(element.Name, out var convert))
675 {
676 convert(element);
677 }
678 }
679
680 private void ConvertBalConditionElement(XElement element)
681 {
682 this.ConvertInnerTextToAttribute(element, "Condition");
683 }
684
685 private void ConvertBootstrapperApplicationElement(XElement element)
686 {
687 var xUseUILanguages = element.Attribute(BalUseUILanguagesName);
688 if (xUseUILanguages != null &&
689 this.OnInformation(ConverterTestType.BalUseUILanguagesDeprecated, element, "bal:UseUILanguages is deprecated, 'true' is now the standard behavior."))
690 {
691 xUseUILanguages.Remove();
692 }
693
694 var xBADll = element.Elements(BootstrapperApplicationDllElementName).FirstOrDefault();
695 if (xBADll == null)
696 {
697 xBADll = this.CreateBootstrapperApplicationDllElement(element);
698
699 if (xBADll != null)
700 {
701 element.Add(Environment.NewLine);
702 element.Add(xBADll);
703 element.Add(Environment.NewLine);
704 }
705 }
706 }
707
708 private XElement CreateBootstrapperApplicationDllElement(XElement element)
709 {
710 XElement xBADll = null;
711 var xSource = element.Attribute("SourceFile");
712 var xDpiAwareness = element.Attribute("DpiAwareness");
713
714 if (xSource != null)
715 {
716 if (xBADll != null || CreateBADllElement(element, out xBADll))
717 {
718 MoveAttribute(element, "SourceFile", xBADll);
719 MoveAttribute(element, "Name", xBADll);
720 }
721 }
722 else if (xDpiAwareness != null || this.SourceVersion < 4) // older code might be relying on old behavior of first Payload element being the BA dll.
723 {
724 var xFirstChild = element.Elements().FirstOrDefault();
725 if (xFirstChild?.Name == PayloadElementName)
726 {
727 if (xBADll != null || CreateBADllElement(element, out xBADll))
728 {
729 var attributes = xFirstChild.Attributes().ToList();
730 xFirstChild.Remove();
731
732 foreach (var attribute in attributes)
733 {
734 xBADll.Add(attribute);
735 }
736 }
737 }
738 else
739 {
740 this.OnError(ConverterTestType.BootstrapperApplicationDllRequired, element, "The new BootstrapperApplicationDll element is required but could not be added automatically since the bootstrapper application dll was not directly specified. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-bundles");
741 }
742 }
743
744 if (xDpiAwareness != null)
745 {
746 if (xBADll != null || CreateBADllElement(element, out xBADll))
747 {
748 MoveAttribute(element, "DpiAwareness", xBADll);
749 }
750 }
751 else if (this.SourceVersion < 4 && xBADll != null &&
752 this.OnInformation(ConverterTestType.AssignBootstrapperApplicationDpiAwareness, element, "The BootstrapperApplicationDll DpiAwareness attribute is being set to 'unaware' to ensure it remains the same as the v3 default"))
753 {
754 xBADll.Add(new XAttribute("DpiAwareness", "unaware"));
755 }
756
757 return xBADll;
758
759 bool CreateBADllElement(XObject node, out XElement xCreatedBADll)
760 {
761 var create = this.OnInformation(ConverterTestType.BootstrapperApplicationDll, node, "The bootstrapper application dll is now specified in the BootstrapperApplicationDll element.");
762 xCreatedBADll = create ? new XElement(BootstrapperApplicationDllElementName) : null;
763 return create;
764 }
765 }
766
767 private void ConvertBootstrapperApplicationRefElement(XElement element)
768 {
769 var xUseUILanguages = element.Attribute(BalUseUILanguagesName);
770 if (xUseUILanguages != null &&
771 this.OnInformation(ConverterTestType.BalUseUILanguagesDeprecated, element, "bal:UseUILanguages is deprecated, 'true' is now the standard behavior."))
772 {
773 xUseUILanguages.Remove();
774 }
775
776 var xId = element.Attribute("Id");
777 if (xId != null)
778 {
779 XName balBAName = null;
780 XName oldBalBAName = null;
781 string theme = null;
782
783 switch (xId.Value)
784 {
785 case "WixStandardBootstrapperApplication.RtfLicense":
786 balBAName = BalStandardBootstrapperApplicationName;
787 theme = "rtfLicense";
788 break;
789 case "WixStandardBootstrapperApplication.RtfLargeLicense":
790 balBAName = BalStandardBootstrapperApplicationName;
791 theme = "rtfLargeLicense";
792 break;
793 case "WixStandardBootstrapperApplication.HyperlinkLicense":
794 balBAName = BalStandardBootstrapperApplicationName;
795 theme = "hyperlinkLicense";
796 break;
797 case "WixStandardBootstrapperApplication.HyperlinkLargeLicense":
798 balBAName = BalStandardBootstrapperApplicationName;
799 theme = "hyperlinkLargeLicense";
800 break;
801 case "WixStandardBootstrapperApplication.HyperlinkSidebarLicense":
802 balBAName = BalStandardBootstrapperApplicationName;
803 theme = "hyperlinkSidebarLicense";
804 break;
805 case "WixStandardBootstrapperApplication.Foundation":
806 balBAName = BalStandardBootstrapperApplicationName;
807 theme = "none";
808 break;
809 case "ManagedBootstrapperApplicationHost":
810 case "ManagedBootstrapperApplicationHost.RtfLicense":
811 balBAName = BalManagedBootstrapperApplicationHostName;
812 theme = "standard";
813 break;
814 case "ManagedBootstrapperApplicationHost.Minimal":
815 case "ManagedBootstrapperApplicationHost.RtfLicense.Minimal":
816 case "ManagedBootstrapperApplicationHost.Foundation":
817 balBAName = BalManagedBootstrapperApplicationHostName;
818 theme = "none";
819 break;
820 case "DotNetCoreBootstrapperApplicationHost":
821 case "DotNetCoreBootstrapperApplicationHost.RtfLicense":
822 balBAName = BalNewDotNetCoreBootstrapperApplicationName;
823 oldBalBAName = BalOldDotNetCoreBootstrapperApplicationName;
824 theme = "standard";
825 break;
826 case "DotNetCoreBootstrapperApplicationHost.Minimal":
827 case "DotNetCoreBootstrapperApplicationHost.RtfLicense.Minimal":
828 case "DotNetCoreBootstrapperApplicationHost.Foundation":
829 balBAName = BalNewDotNetCoreBootstrapperApplicationName;
830 oldBalBAName = BalOldDotNetCoreBootstrapperApplicationName;
831 theme = "none";
832 break;
833 }
834
835 if (balBAName != null && theme != null &&
836 this.OnInformation(ConverterTestType.BalBootstrapperApplicationRefToElement, element, "Built-in bootstrapper applications must be referenced through their custom element"))
837 {
838 element.Name = BootstrapperApplicationElementName;
839 xId.Remove();
840 this.ConvertBalBootstrapperApplicationRef(element, theme, balBAName, oldBalBAName);
841 }
842 }
843 }
844
845 private void ConvertApprovedExeForElevationElement(XElement element)
846 {
847 this.RenameWin64ToBitness(element);
848 }
849
850 private void ConvertBalBootstrapperApplicationRef(XElement element, string theme, XName balBAElementName, XName oldBalBAElementName = null)
851 {
852 var xBalBa = element.Element(oldBalBAElementName ?? balBAElementName);
853 if (xBalBa == null)
854 {
855 xBalBa = new XElement(balBAElementName);
856 element.Add(Environment.NewLine);
857 element.Add(xBalBa);
858 element.Add(Environment.NewLine);
859 }
860 else if (oldBalBAElementName != null)
861 {
862 xBalBa.Name = BalNewDotNetCoreBootstrapperApplicationName;
863 }
864
865 if (theme != "standard")
866 {
867 xBalBa.Add(new XAttribute("Theme", theme));
868 }
869 }
870
871 private void ConvertCatalogElement(XElement element)
872 {
873 if (this.OnInformation(ConverterTestType.SuppressSignatureVerificationObsolete, element, "The Catalog element is obsolete. The element will be removed."))
874 {
875 element.Remove();
876 }
877 }
878
879
880 private void ConvertCertificateElement(XElement xCertificate)
881 {
882 var xBinaryKey = xCertificate.Attribute("BinaryKey");
883 if (xBinaryKey != null && this.OnInformation(ConverterTestType.CertificateBinaryKeyIsNowBinaryRef, xCertificate, "The Certificate BinaryKey element has been renamed to BinaryRef."))
884 {
885 xCertificate.SetAttributeValue("BinaryRef", xBinaryKey.Value);
886 xBinaryKey.Remove();
887 }
888 }
889
890 private void ConvertColumnElement(XElement element)
891 {
892 var category = element.Attribute("Category");
893 if (category != null)
894 {
895 var camelCaseValue = LowercaseFirstChar(category.Value);
896 if (category.Value != camelCaseValue &&
897 this.OnInformation(ConverterTestType.ColumnCategoryCamelCase, element, "The CustomTable Category attribute contains an incorrectly cased '{0}' value. Lowercase the first character instead.", category.Name))
898 {
899 category.Value = camelCaseValue;
900 }
901 }
902
903 var modularization = element.Attribute("Modularize");
904 if (modularization != null)
905 {
906 var camelCaseValue = LowercaseFirstChar(modularization.Value);
907 if (modularization.Value != camelCaseValue &&
908 this.OnInformation(ConverterTestType.ColumnModularizeCamelCase, element, "The CustomTable Modularize attribute contains an incorrectly cased '{0}' value. Lowercase the first character instead.", modularization.Name))
909 {
910 modularization.Value = camelCaseValue;
911 }
912 }
913 }
914
915 private void ConvertCustomElement(XElement element)
916 {
917 var actionId = element.Attribute("Action")?.Value;
918
919 if (actionId != null
920 && CustomActionIdsWithPlatformSuffix.TryGetValue(actionId, out var replacementId))
921 {
922 this.OnError(ConverterTestType.CustomActionIdsIncludePlatformSuffix, element,
923 $"Custom action ids have changed in WiX v4 extensions to support platform-specific custom actions. The platform is applied as a suffix: _X86, _X64, _A64 (Arm64). When manually rescheduling custom action '{actionId}', you must use the new custom action id '{replacementId}'. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages");
924 }
925 }
926
927 private void ConvertCustomTableElement(XElement element)
928 {
929 var bootstrapperApplicationData = element.Attribute("BootstrapperApplicationData");
930 if (bootstrapperApplicationData?.Value == "no")
931 {
932 if (this.OnInformation(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'Unreal' attribute instead.", bootstrapperApplicationData.Name))
933 {
934 bootstrapperApplicationData.Remove();
935 }
936 }
937 else
938 {
939 if (element.Elements(ColumnElementName).Any() || bootstrapperApplicationData != null)
940 {
941 // Table definition
942 if (bootstrapperApplicationData != null)
943 {
944 switch (this.CustomTableSetting)
945 {
946 case CustomTableTarget.Bundle:
947 if (this.OnInformation(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'BundleCustomData' element for Bundles.", bootstrapperApplicationData.Name))
948 {
949 element.Name = WixConverter.BundleCustomDataElementName;
950 bootstrapperApplicationData.Remove();
951 this.ConvertCustomTableElementToBundle(element);
952 }
953 break;
954 case CustomTableTarget.Msi:
955 if (this.OnInformation(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'Unreal' attribute instead.", bootstrapperApplicationData.Name))
956 {
957 element.Add(new XAttribute("Unreal", bootstrapperApplicationData.Value));
958 bootstrapperApplicationData.Remove();
959 }
960 break;
961 default:
962 this.OnError(ConverterTestType.CustomTableNotAlwaysConvertable, element, "The CustomTable element contains deprecated '{0}' attribute so can't be converted. Use the 'Unreal' attribute for MSI. Use the 'BundleCustomData' element for Bundles. Use the --custom-table argument to force conversion to 'msi' or 'bundle'. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-bundles", bootstrapperApplicationData.Name);
963 break;
964 }
965 }
966 }
967 else
968 {
969 // Table ref
970 switch (this.CustomTableSetting)
971 {
972 case CustomTableTarget.Bundle:
973 if (this.OnInformation(ConverterTestType.CustomTableRef, element, "CustomTable elements that don't contain the table definition are now BundleCustomDataRef for Bundles."))
974 {
975 element.Name = WixConverter.BundleCustomDataRefElementName;
976 this.ConvertCustomTableElementToBundle(element);
977 }
978 break;
979 case CustomTableTarget.Msi:
980 if (this.OnInformation(ConverterTestType.CustomTableRef, element, "CustomTable elements that don't contain the table definition are now CustomTableRef for MSI."))
981 {
982 element.Name = WixConverter.CustomTableRefElementName;
983 }
984 break;
985 default:
986 this.OnError(ConverterTestType.CustomTableNotAlwaysConvertable, element, "The CustomTable element contains no 'Column' elements so can't be converted. Use the 'CustomTableRef' element for MSI. Use the 'BundleCustomDataRef' element for Bundles. Use the --custom-table argument to force conversion to 'msi' or 'bundle'. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-bundles");
987 break;
988 }
989 }
990 }
991 }
992
993 private void ConvertCustomTableElementToBundle(XElement element)
994 {
995 foreach (var xColumn in element.Elements(ColumnElementName))
996 {
997 xColumn.Name = WixConverter.BundleAttributeDefinitionElementName;
998
999 foreach (var xAttribute in xColumn.Attributes().ToList())
1000 {
1001 if (xAttribute.Name.LocalName != "Id" &&
1002 (xAttribute.Name.Namespace == WixConverter.Wix3Namespace ||
1003 xAttribute.Name.Namespace == WixConverter.WixNamespace ||
1004 String.IsNullOrEmpty(xAttribute.Name.Namespace.NamespaceName)))
1005 {
1006 xAttribute.Remove();
1007 }
1008 }
1009 }
1010
1011 foreach (var xRow in element.Elements(RowElementName))
1012 {
1013 xRow.Name = WixConverter.BundleElementElementName;
1014
1015 foreach (var xData in xRow.Elements(DataElementName))
1016 {
1017 xData.Name = WixConverter.BundleAttributeElementName;
1018
1019 var xColumn = xData.Attribute("Column");
1020 if (xColumn != null)
1021 {
1022 xData.Add(new XAttribute("Id", xColumn.Value));
1023 xColumn.Remove();
1024 }
1025
1026 this.ConvertInnerTextToAttribute(xData, "Value");
1027 }
1028 }
1029 }
1030
1031 private void ConvertControlElement(XElement element)
1032 {
1033 using (var lab = new ConversionLab(element))
1034 {
1035 var xConditions = element.Elements(ConditionElementName).ToList();
1036 var collector = new InnerContentCollector();
1037 var conditions = new List<KeyValuePair<string, string>>();
1038
1039 foreach (var xCondition in xConditions)
1040 {
1041 var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value);
1042
1043 if (!String.IsNullOrEmpty(action) &&
1044 collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1045 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action))
1046 {
1047 conditions.Add(new KeyValuePair<string, string>(action, value));
1048 }
1049 }
1050
1051 foreach (var actionCondition in conditions.GroupBy(c => c.Key))
1052 {
1053 var conditionValues = actionCondition.Select(c => c.Value).ToList();
1054
1055 var finalCondition = (conditionValues.Count == 1) ? conditionValues.Single() : String.Join(" OR ", conditionValues.Select(c => $"({c})"));
1056
1057 element.Add(new XAttribute(actionCondition.Key + "Condition", finalCondition));
1058 }
1059
1060 foreach (var xCondition in xConditions)
1061 {
1062 xCondition.Remove();
1063 }
1064
1065 lab.RemoveOrphanTextNodes();
1066 lab.AddCommentsAsSiblings(collector.Comments);
1067 }
1068 }
1069
1070 private void ConvertComponentElement(XElement element)
1071 {
1072 var guid = element.Attribute("Guid");
1073 if (guid != null && guid.Value == "*")
1074 {
1075 if (this.OnInformation(ConverterTestType.AutoGuidUnnecessary, element, "Using '*' for the Component Guid attribute is unnecessary. Remove the attribute to remove the redundancy."))
1076 {
1077 guid.Remove();
1078 }
1079 }
1080
1081 var xCondition = element.Element(ConditionElementName);
1082 if (xCondition != null)
1083 {
1084 var collector = new InnerContentCollector();
1085
1086 if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1087 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
1088 {
1089 using (var lab = new ConversionLab(element))
1090 {
1091 xCondition.Remove();
1092 element.Add(new XAttribute("Condition", value));
1093 lab.RemoveOrphanTextNodes();
1094 lab.AddCommentsAsSiblings(collector.Comments);
1095 }
1096 }
1097 }
1098
1099 this.RenameWin64ToBitness(element);
1100 }
1101
1102 private void ConvertDirectoryElement(XElement element)
1103 {
1104 if (null == element.Attribute("Name"))
1105 {
1106 var attribute = element.Attribute("ShortName");
1107 if (null != attribute)
1108 {
1109 var shortName = attribute.Value;
1110 if (this.OnInformation(ConverterTestType.AssignDirectoryNameFromShortName, element, "The directory ShortName attribute is being renamed to Name since Name wasn't specified for value '{0}'", shortName))
1111 {
1112 element.Add(new XAttribute("Name", shortName));
1113 attribute.Remove();
1114 }
1115 }
1116 }
1117
1118 var id = element.Attribute("Id")?.Value;
1119
1120 if (id == "TARGETDIR")
1121 {
1122 if (this.OnInformation(ConverterTestType.TargetDirDeprecated, element, "The TARGETDIR directory should no longer be explicitly defined. Remove the Directory element with Id attribute 'TARGETDIR'."))
1123 {
1124 AddTargetDirDirectoryAttributeToComponents(element);
1125
1126 RemoveElementKeepChildren(element);
1127 }
1128 }
1129 else if (id != null &&
1130 WindowsInstallerStandard.IsStandardDirectory(id) &&
1131 this.OnInformation(ConverterTestType.DefiningStandardDirectoryDeprecated, element, "Standard directories such as '{0}' should no longer be defined using the Directory element. Use the StandardDirectory element instead.", id))
1132 {
1133 RenameElementToStandardDirectory(element);
1134 }
1135 }
1136
1137 private void ConvertDirectoryRefElement(XElement element)
1138 {
1139 var id = element.Attribute("Id")?.Value;
1140
1141 if (id != null && WindowsInstallerStandard.IsStandardDirectory(id))
1142 {
1143 if (!element.HasElements)
1144 {
1145 this.OnError(ConverterTestType.EmptyStandardDirectoryRefNotConvertable, element, "Referencing '{0}' directory directly is no longer supported. The DirectoryRef will not be removed but you will probably need to reference a more specific directory. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages", id);
1146 }
1147 else if (id == "TARGETDIR")
1148 {
1149 if (this.OnInformation(ConverterTestType.StandardDirectoryRefDeprecated, element, "The {0} directory should no longer be explicitly referenced. Remove the DirectoryRef element with Id attribute '{0}'.", id))
1150 {
1151 AddTargetDirDirectoryAttributeToComponents(element);
1152
1153 RemoveElementKeepChildren(element);
1154
1155 this.OnError(ConverterTestType.TargetDirRefRemoved, element, "A reference to the TARGETDIR Directory was removed. This can cause unintended side effects. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages");
1156 }
1157 }
1158 else if (this.OnInformation(ConverterTestType.StandardDirectoryRefDeprecated, element, "The standard directory '{0}' should no longer be directly referenced. Use the StandardDirectory element instead.", id))
1159 {
1160 RenameElementToStandardDirectory(element);
1161 }
1162 }
1163 }
1164
1165 private void ConvertFeatureElement(XElement element)
1166 {
1167 var xAbsent = element.Attribute("Absent");
1168 if (xAbsent != null &&
1169 this.OnInformation(ConverterTestType.FeatureAbsentAttributeReplaced, element, "The Feature element's Absent attribute has been replaced with the AllowAbsent attribute. Use the 'AllowAbsent' attribute instead."))
1170 {
1171 if (xAbsent.Value == "disallow")
1172 {
1173 element.Add(new XAttribute("AllowAbsent", "no"));
1174 }
1175 xAbsent.Remove();
1176 }
1177
1178 var xAllowAdvertise = element.Attribute("AllowAdvertise");
1179 if (xAllowAdvertise != null)
1180 {
1181 if ((xAllowAdvertise.Value == "system" || xAllowAdvertise.Value == "allow") &&
1182 this.OnInformation(ConverterTestType.FeatureAllowAdvertiseValueDeprecated, element, "The AllowAdvertise attribute's '{0}' value is deprecated. Set the value to 'yes' instead.", xAllowAdvertise.Value))
1183 {
1184 xAllowAdvertise.Value = "yes";
1185 }
1186 else if (xAllowAdvertise.Value == "disallow" &&
1187 this.OnInformation(ConverterTestType.FeatureAllowAdvertiseValueDeprecated, element, "The AllowAdvertise attribute's '{0}' value is deprecated. Remove the value instead.", xAllowAdvertise.Value))
1188 {
1189 xAllowAdvertise.Remove();
1190 }
1191 }
1192
1193 var xCondition = element.Element(ConditionElementName);
1194 if (xCondition != null)
1195 {
1196 var level = xCondition.Attribute("Level")?.Value;
1197 var collector = new InnerContentCollector();
1198
1199 if (!String.IsNullOrEmpty(level) &&
1200 collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1201 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName))
1202 {
1203 using (var lab = new ConversionLab(xCondition))
1204 {
1205 lab.ReplaceTargetElement(new XElement(LevelElementName,
1206 new XAttribute("Value", level),
1207 new XAttribute("Condition", value)));
1208 lab.AddCommentsAsSiblings(collector.Comments);
1209 }
1210 }
1211 }
1212 }
1213
1214 private void ConvertFileElement(XElement element)
1215 {
1216 if (this.SourceVersion < 4 && null == element.Attribute("Id"))
1217 {
1218 var attribute = element.Attribute("Name");
1219
1220 if (null == attribute)
1221 {
1222 attribute = element.Attribute("Source");
1223 }
1224
1225 if (null != attribute)
1226 {
1227 var name = Path.GetFileName(attribute.Value);
1228
1229 if (this.OnInformation(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the v3 default", name))
1230 {
1231 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
1232 element.RemoveAttributes();
1233 element.Add(new XAttribute("Id", GetIdentifierFromName(name)));
1234 element.Add(attributes);
1235 }
1236 }
1237 }
1238 }
1239
1240 private void ConvertLaunchConditionElement(XElement element)
1241 {
1242 var message = element.Attribute("Message")?.Value;
1243 var collector = new InnerContentCollector();
1244
1245 if (!String.IsNullOrEmpty(message) &&
1246 collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(element, out string value) &&
1247 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", element.Name.LocalName))
1248 {
1249 if (String.IsNullOrWhiteSpace(value))
1250 {
1251 value = String.Empty;
1252 }
1253
1254 using (var lab = new ConversionLab(element))
1255 {
1256 lab.ReplaceTargetElement(new XElement(LaunchElementName,
1257 new XAttribute("Condition", value),
1258 new XAttribute("Message", message)));
1259 lab.AddCommentsAsSiblings(collector.Comments);
1260 }
1261 }
1262 }
1263
1264 private void ConvertFirewallRemoteAddressElement(XElement element)
1265 {
1266 this.ConvertInnerTextToAttribute(element, "Value");
1267 }
1268
1269 private void ConvertEmbeddedChainerElement(XElement element)
1270 {
1271 this.ConvertInnerTextToAttribute(element, "Condition");
1272 }
1273
1274 private void ConvertErrorElement(XElement element)
1275 {
1276 this.ConvertInnerTextToAttribute(element, "Message");
1277 }
1278
1279 private void ConvertExePackageElement(XElement element)
1280 {
1281 this.ConvertSuppressSignatureVerification(element);
1282
1283 this.UpdatePackageCacheAttribute(element);
1284
1285 foreach (var attributeName in new[] { "InstallCommand", "RepairCommand", "UninstallCommand" })
1286 {
1287 var newName = attributeName.Replace("Command", "Arguments");
1288 var attribute = element.Attribute(attributeName);
1289
1290 if (attribute != null &&
1291 this.OnInformation(ConverterTestType.RenameExePackageCommandToArguments, element, "The {0} element {1} attribute has been renamed {2}.", element.Name.LocalName, attribute.Name.LocalName, newName))
1292 {
1293 element.Add(new XAttribute(newName, attribute.Value));
1294 attribute.Remove();
1295 }
1296 }
1297 }
1298
1299 private void ConvertPermissionExElement(XElement element)
1300 {
1301 var xCondition = element.Element(ConditionElementName);
1302 if (xCondition != null)
1303 {
1304 var collector = new InnerContentCollector();
1305 if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1306 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
1307 {
1308 using (var lab = new ConversionLab(xCondition))
1309 {
1310 lab.RemoveTargetElement();
1311 }
1312 using (var lab = new ConversionLab(element))
1313 {
1314 element.Add(new XAttribute("Condition", value));
1315 lab.RemoveOrphanTextNodes();
1316 lab.AddCommentsAsSiblings(collector.Comments);
1317 }
1318 }
1319 }
1320 }
1321
1322 private void ConvertProgressTextElement(XElement element)
1323 {
1324 this.ConvertInnerTextToAttribute(element, "Message");
1325 }
1326
1327 private void ConvertModuleElement(XElement element)
1328 {
1329 if (element.Attribute("Guid") == null // skip already-converted Module elements
1330 && this.OnInformation(ConverterTestType.ModuleAndPackageRenamed, element, "The Module and Package elements have been renamed and reorganized for simplicity."))
1331 {
1332 var xModule = element;
1333
1334 var xSummaryInformation = xModule.Element(PackageElementName);
1335 if (xSummaryInformation != null)
1336 {
1337 xSummaryInformation.Name = SummaryInformationElementName;
1338
1339 var xInstallerVersion = xSummaryInformation.Attribute("InstallerVersion");
1340 if (this.SourceVersion < 4 && xInstallerVersion == null)
1341 {
1342 this.OnInformation(ConverterTestType.InstallerVersionBehaviorChange, element, "Breaking change: The default value for Package/@InstallerVersion has been changed to '500' regardless of build platform. If you need a lower version, set it manually in the Module element.");
1343 }
1344
1345 RemoveAttribute(xSummaryInformation, "AdminImage");
1346 RemoveAttribute(xSummaryInformation, "Comments");
1347 MoveAttribute(xSummaryInformation, "Id", xModule, "Guid");
1348 MoveAttribute(xSummaryInformation, "InstallerVersion", xModule);
1349 RemoveAttribute(xSummaryInformation, "Languages");
1350 RemoveAttribute(xSummaryInformation, "Platform");
1351 RemoveAttribute(xSummaryInformation, "Platforms");
1352 RemoveAttribute(xSummaryInformation, "ReadOnly");
1353 MoveAttribute(xSummaryInformation, "SummaryCodepage", xSummaryInformation, "Codepage", defaultValue: "1252");
1354
1355 if (!xSummaryInformation.HasAttributes)
1356 {
1357 xSummaryInformation.Remove();
1358 }
1359 }
1360 }
1361 }
1362
1363 private void ConvertMsuPackageElement(XElement element)
1364 {
1365 this.ConvertSuppressSignatureVerification(element);
1366
1367 this.UpdatePackageCacheAttribute(element);
1368
1369 var kbAttribute = element.Attribute("KB");
1370
1371 if (null != kbAttribute
1372 && this.OnInformation(ConverterTestType.MsuPackageKBObsolete, element, "The MsuPackage element contains obsolete '{0}' attribute. Windows no longer supports silently removing MSUs so the attribute is unnecessary. The attribute will be removed.", kbAttribute.Name))
1373 {
1374 kbAttribute.Remove();
1375 }
1376
1377 var permanentAttribute = element.Attribute("Permanent");
1378
1379 if (null != permanentAttribute
1380 && this.OnInformation(ConverterTestType.MsuPackagePermanentObsolete, element, "The MsuPackage element contains obsolete '{0}' attribute. MSU packages are now always permanent because Windows no longer supports silently removing MSUs. The attribute will be removed.", permanentAttribute.Name))
1381 {
1382 permanentAttribute.Remove();
1383 }
1384 }
1385
1386 private void ConvertProductElement(XElement element)
1387 {
1388 var id = element.Attribute("Id");
1389 if (id != null && id.Value == "*")
1390 {
1391 if (this.OnInformation(ConverterTestType.AutoGuidUnnecessary, element, "Using '*' for the Product Id attribute is unnecessary. Remove the attribute to remove the redundancy."))
1392 {
1393 id.Remove();
1394 }
1395 }
1396
1397 var xMediaTemplate = element.Element(MediaTemplateElementName);
1398 if (xMediaTemplate?.HasAttributes == false
1399 && this.OnInformation(ConverterTestType.DefaultMediaTemplate, element, "A MediaTemplate with no attributes set is now provided by default. Remove the element."))
1400 {
1401 xMediaTemplate.Remove();
1402 }
1403
1404 if (this.OnInformation(ConverterTestType.ProductAndPackageRenamed, element, "The Product and Package elements have been renamed and reorganized for simplicity."))
1405 {
1406 var xPackage = element;
1407 xPackage.Name = PackageElementName;
1408
1409 var xSummaryInformation = xPackage.Element(PackageElementName);
1410 if (xSummaryInformation != null)
1411 {
1412 xSummaryInformation.Name = SummaryInformationElementName;
1413
1414 var xInstallerVersion = xSummaryInformation.Attribute("InstallerVersion");
1415 if (this.SourceVersion < 4 && xInstallerVersion == null)
1416 {
1417 this.OnInformation(ConverterTestType.InstallerVersionBehaviorChange, element, "Breaking change: The default value for Package/@InstallerVersion has been changed to '500' regardless of build platform. If you need a lower version, set it manually in the Package element.");
1418 }
1419
1420 if (xSummaryInformation.Attribute("Compressed") == null)
1421 {
1422 xPackage.SetAttributeValue("Compressed", "no");
1423 }
1424 else
1425 {
1426 MoveAttribute(xSummaryInformation, "Compressed", xPackage, defaultValue: "yes");
1427 }
1428
1429 RemoveAttribute(xSummaryInformation, "AdminImage");
1430 RemoveAttribute(xSummaryInformation, "Comments");
1431 RemoveAttribute(xSummaryInformation, "Id");
1432 MoveAttribute(xSummaryInformation, "InstallerVersion", xPackage, defaultValue: "500");
1433 MoveAttribute(xSummaryInformation, "InstallScope", xPackage, "Scope", defaultValue: "perMachine");
1434 RemoveAttribute(xSummaryInformation, "Languages");
1435 RemoveAttribute(xSummaryInformation, "Platform");
1436 RemoveAttribute(xSummaryInformation, "Platforms");
1437 RemoveAttribute(xSummaryInformation, "ReadOnly");
1438 MoveAttribute(xSummaryInformation, "ShortNames", xPackage);
1439 MoveAttribute(xSummaryInformation, "SummaryCodepage", xSummaryInformation, "Codepage", defaultValue: "1252");
1440 MoveAttribute(xPackage, "Id", xPackage, "ProductCode");
1441
1442 var xInstallPrivileges = xSummaryInformation.Attribute("InstallPrivileges");
1443 switch (xInstallPrivileges?.Value)
1444 {
1445 case "limited":
1446 xPackage.SetAttributeValue("Scope", "perUser");
1447 break;
1448 case "elevated":
1449 {
1450 var xAllUsers = xPackage.Elements(PropertyElementName).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS");
1451 if (xAllUsers?.Attribute("Value")?.Value == "1")
1452 {
1453 xAllUsers?.Remove();
1454 }
1455 }
1456 break;
1457 }
1458
1459 xInstallPrivileges?.Remove();
1460
1461 if (!xSummaryInformation.HasAttributes)
1462 {
1463 xSummaryInformation.Remove();
1464 }
1465 }
1466 }
1467 }
1468
1469 private static void MoveAttribute(XElement xSource, string attributeName, XElement xDestination, string destinationAttributeName = null, string defaultValue = null)
1470 {
1471 var xAttribute = xSource.Attribute(attributeName);
1472 if (xAttribute != null && (defaultValue == null || xAttribute.Value != defaultValue))
1473 {
1474 xDestination.SetAttributeValue(destinationAttributeName ?? attributeName, xAttribute.Value);
1475 }
1476
1477 xAttribute?.Remove();
1478 }
1479
1480 private static void RemoveAttribute(XElement xSummaryInformation, string attributeName)
1481 {
1482 var xAttribute = xSummaryInformation.Attribute(attributeName);
1483 xAttribute?.Remove();
1484 }
1485
1486 private void ConvertPropertyRefElement(XElement element)
1487 {
1488 var newElementName = String.Empty;
1489 var newNamespace = WixUtilNamespace;
1490 var newNamespaceName = "util";
1491 var replace = true;
1492
1493 var id = element.Attribute("Id");
1494 switch (id?.Value)
1495 {
1496 case "WIX_SUITE_BACKOFFICE":
1497 case "WIX_SUITE_BLADE":
1498 case "WIX_SUITE_COMMUNICATIONS":
1499 case "WIX_SUITE_COMPUTE_SERVER":
1500 case "WIX_SUITE_DATACENTER":
1501 case "WIX_SUITE_EMBEDDED_RESTRICTED":
1502 case "WIX_SUITE_EMBEDDEDNT":
1503 case "WIX_SUITE_ENTERPRISE":
1504 case "WIX_SUITE_MEDIACENTER":
1505 case "WIX_SUITE_PERSONAL":
1506 case "WIX_SUITE_SECURITY_APPLIANCE":
1507 case "WIX_SUITE_SERVERR2":
1508 case "WIX_SUITE_SINGLEUSERTS":
1509 case "WIX_SUITE_SMALLBUSINESS":
1510 case "WIX_SUITE_SMALLBUSINESS_RESTRICTED":
1511 case "WIX_SUITE_STARTER":
1512 case "WIX_SUITE_STORAGE_SERVER":
1513 case "WIX_SUITE_TABLETPC":
1514 case "WIX_SUITE_TERMINAL":
1515 case "WIX_SUITE_WH_SERVER":
1516 newElementName = "QueryWindowsSuiteInfo";
1517 break;
1518 case "WIX_DIR_ADMINTOOLS":
1519 case "WIX_DIR_ALTSTARTUP":
1520 case "WIX_DIR_CDBURN_AREA":
1521 case "WIX_DIR_COMMON_ADMINTOOLS":
1522 case "WIX_DIR_COMMON_ALTSTARTUP":
1523 case "WIX_DIR_COMMON_DOCUMENTS":
1524 case "WIX_DIR_COMMON_FAVORITES":
1525 case "WIX_DIR_COMMON_MUSIC":
1526 case "WIX_DIR_COMMON_PICTURES":
1527 case "WIX_DIR_COMMON_VIDEO":
1528 case "WIX_DIR_COOKIES":
1529 case "WIX_DIR_DESKTOP":
1530 case "WIX_DIR_HISTORY":
1531 case "WIX_DIR_INTERNET_CACHE":
1532 case "WIX_DIR_MYMUSIC":
1533 case "WIX_DIR_MYPICTURES":
1534 case "WIX_DIR_MYVIDEO":
1535 case "WIX_DIR_NETHOOD":
1536 case "WIX_DIR_PERSONAL":
1537 case "WIX_DIR_PRINTHOOD":
1538 case "WIX_DIR_PROFILE":
1539 case "WIX_DIR_RECENT":
1540 case "WIX_DIR_RESOURCES":
1541 newElementName = "QueryWindowsDirectories";
1542 break;
1543 case "WIX_DWM_COMPOSITION_ENABLED":
1544 case "WIX_WDDM_DRIVER_PRESENT":
1545 newElementName = "QueryWindowsDriverInfo";
1546 break;
1547 case "WIX_ACCOUNT_LOCALSYSTEM":
1548 case "WIX_ACCOUNT_LOCALSERVICE":
1549 case "WIX_ACCOUNT_NETWORKSERVICE":
1550 case "WIX_ACCOUNT_ADMINISTRATORS":
1551 case "WIX_ACCOUNT_USERS":
1552 case "WIX_ACCOUNT_GUESTS":
1553 case "WIX_ACCOUNT_PERFLOGUSERS":
1554 case "WIX_ACCOUNT_PERFLOGUSERS_NODOMAIN":
1555 newElementName = "QueryWindowsWellKnownSIDs";
1556 break;
1557 case "WIX_NATIVE_MACHINE":
1558 newElementName = "QueryNativeMachine";
1559 break;
1560 case "VS2017_ROOT_FOLDER":
1561 case "VS2017_IDE_FSHARP_PROJECTSYSTEM_INSTALLED":
1562 case "VS2017_IDE_VB_PROJECTSYSTEM_INSTALLED":
1563 case "VS2017_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED":
1564 case "VS2017_IDE_VSTS_TESTSYSTEM_INSTALLED":
1565 case "VS2017_IDE_VC_PROJECTSYSTEM_INSTALLED":
1566 case "VS2017_IDE_VWD_PROJECTSYSTEM_INSTALLED":
1567 case "VS2017_IDE_MODELING_PROJECTSYSTEM_INSTALLED":
1568 case "VS2019_ROOT_FOLDER":
1569 case "VS2019_IDE_FSHARP_PROJECTSYSTEM_INSTALLED":
1570 case "VS2019_IDE_VB_PROJECTSYSTEM_INSTALLED":
1571 case "VS2019_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED":
1572 case "VS2019_IDE_VSTS_TESTSYSTEM_INSTALLED":
1573 case "VS2019_IDE_VC_PROJECTSYSTEM_INSTALLED":
1574 case "VS2019_IDE_VWD_PROJECTSYSTEM_INSTALLED":
1575 case "VS2019_IDE_MODELING_PROJECTSYSTEM_INSTALLED":
1576 case "VS2022_ROOT_FOLDER":
1577 case "VS2022_IDE_FSHARP_PROJECTSYSTEM_INSTALLED":
1578 case "VS2022_IDE_VB_PROJECTSYSTEM_INSTALLED":
1579 case "VS2022_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED":
1580 case "VS2022_IDE_VSTS_TESTSYSTEM_INSTALLED":
1581 case "VS2022_IDE_VC_PROJECTSYSTEM_INSTALLED":
1582 case "VS2022_IDE_VWD_PROJECTSYSTEM_INSTALLED":
1583 case "VS2022_IDE_MODELING_PROJECTSYSTEM_INSTALLED":
1584 newElementName = "FindVisualStudio";
1585 newNamespace = WixVSNamespace;
1586 newNamespaceName = "vs";
1587 break;
1588 case "VS2017DEVENV":
1589 case "VS2017_EXTENSIONS_DIR":
1590 case "VS2017_ITEMTEMPLATES_DIR":
1591 case "VS2017_PROJECTTEMPLATES_DIR":
1592 case "VS2017_SCHEMAS_DIR":
1593 case "VS2017_IDE_DIR":
1594 case "VS2017_BOOTSTRAPPER_PACKAGE_FOLDER":
1595 case "VS2019DEVENV":
1596 case "VS2019_EXTENSIONS_DIR":
1597 case "VS2019_ITEMTEMPLATES_DIR":
1598 case "VS2019_PROJECTTEMPLATES_DIR":
1599 case "VS2019_SCHEMAS_DIR":
1600 case "VS2019_IDE_DIR":
1601 case "VS2019_BOOTSTRAPPER_PACKAGE_FOLDER":
1602 case "VS2022DEVENV":
1603 case "VS2022_EXTENSIONS_DIR":
1604 case "VS2022_ITEMTEMPLATES_DIR":
1605 case "VS2022_PROJECTTEMPLATES_DIR":
1606 case "VS2022_SCHEMAS_DIR":
1607 case "VS2022_IDE_DIR":
1608 case "VS2022_BOOTSTRAPPER_PACKAGE_FOLDER":
1609 // These PropertyRefs need to stay (in addition to the `FindVisualStudio`
1610 // addition) because they're constructed from deeper AppSearches.
1611 newElementName = "FindVisualStudio";
1612 newNamespace = WixVSNamespace;
1613 newNamespaceName = "vs";
1614 replace = false;
1615 break;
1616 case "WIX_DIRECTX_PIXELSHADERVERSION":
1617 case "WIX_DIRECTX_VERTEXSHADERVERSION":
1618 newElementName = "GetCapabilities";
1619 newNamespace = WixDirectXNamespace;
1620 newNamespaceName = "directx";
1621 break;
1622 }
1623
1624 if (!String.IsNullOrEmpty(newElementName)
1625 && this.OnInformation(ConverterTestType.ReferencesReplaced, element, "UI, custom action, and property reference {0} has been replaced with strongly-typed element.", id))
1626 {
1627 using (var lab = new ConversionLab(element))
1628 {
1629 this.XRoot.SetAttributeValue(XNamespace.Xmlns + newNamespaceName, newNamespace.NamespaceName);
1630 lab.InsertUniqueElementBeforeTargetElement(new XElement(newNamespace + newElementName));
1631
1632 if (replace)
1633 {
1634 lab.RemoveTargetElement();
1635 }
1636 }
1637 }
1638 }
1639
1640 private void ConvertUIRefElement(XElement element)
1641 {
1642 var id = element.Attribute("Id")?.Value;
1643
1644 if (id != null
1645 && (id == "WixUI_Advanced" || id == "WixUI_FeatureTree" || id == "WixUI_InstallDir" || id == "WixUI_Minimal" || id == "WixUI_Mondo")
1646 && this.OnInformation(ConverterTestType.ReferencesReplaced, element, "UI, custom action, and property reference {0} has been replaced with strongly-typed element.", id))
1647 {
1648 this.XRoot.SetAttributeValue(XNamespace.Xmlns + "ui", WixUiNamespace.NamespaceName);
1649
1650 element.AddBeforeSelf(new XElement(WixUiNamespace + "WixUI", new XAttribute("Id", id)));
1651
1652 element.Remove();
1653 }
1654 }
1655
1656 private void ConvertCustomActionRefElement(XElement element)
1657 {
1658 var newElementName = String.Empty;
1659
1660 var id = element.Attribute("Id");
1661 switch (id?.Value)
1662 {
1663 case "WixBroadcastSettingChange":
1664 case "WixBroadcastEnvironmentChange":
1665 case "WixCheckRebootRequired":
1666 case "WixExitEarlyWithSuccess":
1667 case "WixFailWhenDeferred":
1668 case "WixWaitForEvent":
1669 case "WixWaitForEventDeferred":
1670 newElementName = id?.Value.Substring(3); // strip leading Wix
1671 break;
1672 }
1673
1674 if (!String.IsNullOrEmpty(newElementName)
1675 && this.OnInformation(ConverterTestType.ReferencesReplaced, element, "UI, custom action and property reference {0} have been replaced with strongly-typed elements.", id))
1676 {
1677 element.AddAfterSelf(new XElement(WixUtilNamespace + newElementName));
1678 element.Remove();
1679 }
1680 }
1681
1682 private void ConvertPublishElement(XElement element)
1683 {
1684 var collector = new InnerContentCollector();
1685
1686 if (collector.CollectInnerTextAndCommentsForAttributeValue(element, out string value) &&
1687 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", element.Name.LocalName))
1688 {
1689 using (var lab = new ConversionLab(element))
1690 {
1691 if ("1" == value)
1692 {
1693 this.OnInformation(ConverterTestType.PublishConditionOneUnnecessary, element, "Adding Condition='1' on {0} elements is no longer necessary. Remove the Condition attribute.", element.Name.LocalName);
1694 }
1695 else
1696 {
1697 element.Add(new XAttribute("Condition", value));
1698 }
1699
1700 lab.RemoveOrphanTextNodes();
1701 lab.AddCommentsAsSiblings(collector.Comments);
1702 }
1703 }
1704
1705 var eventName = element.Attribute("Event")?.Value;
1706 var eventValue = element.Attribute("Value")?.Value;
1707
1708 if (eventName?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true)
1709 {
1710 if (eventValue?.StartsWith("WixUIPrintEula", StringComparison.OrdinalIgnoreCase) == true)
1711 {
1712 if (this.OnInformation(ConverterTestType.WixUIPrintEulaCustomAction, element, "The WixUIPrintEula custom action has been replaced with the MSI native MsiPrint control event in WiX v5 and no longer needs to be authored in a custom dialog set."))
1713 {
1714 element.Remove();
1715 }
1716 }
1717 else if (eventValue?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true
1718 && this.OnInformation(ConverterTestType.CustomActionIdsIncludePlatformSuffix, element, "Custom action ids have changed in WiX v4 extensions to support platform-specific custom actions. For more information, see https://wixtoolset.org/docs/fourthree/faqs/#converting-custom-wixui-dialog-sets."))
1719 {
1720 element.Attribute("Value").Value = eventValue + "_$(sys.BUILDARCHSHORT)";
1721 }
1722 }
1723 }
1724
1725 private void ConvertMultiStringValueElement(XElement element)
1726 {
1727 this.ConvertInnerTextToAttribute(element, "Value");
1728 }
1729
1730 private void ConvertRegistryKeyElement(XElement element)
1731 {
1732 var xAction = element.Attribute("Action");
1733
1734 if (xAction != null
1735 && this.OnInformation(ConverterTestType.RegistryKeyActionObsolete, element, "The RegistryKey element's Action attribute is obsolete. Action='create' will be converted to ForceCreateOnInstall='yes'. Action='createAndRemoveOnUninstall' will be converted to ForceCreateOnInstall='yes' and ForceDeleteOnUninstall='yes'."))
1736 {
1737 switch (xAction?.Value)
1738 {
1739 case "create":
1740 element.SetAttributeValue("ForceCreateOnInstall", "yes");
1741 break;
1742 case "createAndRemoveOnUninstall":
1743 element.SetAttributeValue("ForceCreateOnInstall", "yes");
1744 element.SetAttributeValue("ForceDeleteOnUninstall", "yes");
1745 break;
1746 }
1747
1748 xAction.Remove();
1749 }
1750 }
1751
1752 private void ConvertRelatedBundleElement(XElement element)
1753 {
1754 var xAction = element.Attribute("Action");
1755 var value = xAction?.Value;
1756 var lowercaseValue = value?.ToLowerInvariant();
1757
1758 if (value != lowercaseValue
1759 && this.OnInformation(ConverterTestType.RelatedBundleActionLowercase, element, "The RelatedBundle element's Action attribute value must now be all lowercase. The Action='{0}' will be converted to '{1}'", value, lowercaseValue))
1760 {
1761 xAction.Value = lowercaseValue;
1762 }
1763 }
1764
1765 private void ConvertRemotePayloadElement(XElement element)
1766 {
1767 var xParent = element.Parent;
1768
1769 if (xParent.Name == ExePackageElementName &&
1770 this.OnInformation(ConverterTestType.RemotePayloadRenamed, element, "The RemotePayload element has been renamed. Use the 'ExePackagePayload' instead."))
1771 {
1772 element.Name = ExePackagePayloadElementName;
1773 }
1774 else if (xParent.Name == MsuPackageElementName &&
1775 this.OnInformation(ConverterTestType.RemotePayloadRenamed, element, "The RemotePayload element has been renamed. Use the 'MsuPackagePayload' instead."))
1776 {
1777 element.Name = MsuPackagePayloadElementName;
1778 }
1779
1780 var xName = xParent.Attribute("Name");
1781 if (xName != null &&
1782 this.OnInformation(ConverterTestType.NameAttributeMovedToRemotePayload, xParent, "The Name attribute must be specified on the child XxxPackagePayload element when using a remote payload."))
1783 {
1784 element.SetAttributeValue("Name", xName.Value);
1785 xName.Remove();
1786 }
1787
1788 var xDownloadUrl = xParent.Attribute("DownloadUrl");
1789 if (xDownloadUrl != null &&
1790 this.OnInformation(ConverterTestType.DownloadUrlAttributeMovedToRemotePayload, xParent, "The DownloadUrl attribute must be specified on the child XxxPackagePayload element when using a remote payload."))
1791 {
1792 element.SetAttributeValue("DownloadUrl", xDownloadUrl.Value);
1793 xDownloadUrl.Remove();
1794 }
1795
1796 var xCompressed = xParent.Attribute("Compressed");
1797 if (xCompressed != null &&
1798 this.OnInformation(ConverterTestType.CompressedAttributeUnnecessaryForRemotePayload, xParent, "The Compressed attribute should not be specified when using a remote payload."))
1799 {
1800 xCompressed.Remove();
1801 }
1802
1803 this.OnInformation(ConverterTestType.BurnHashAlgorithmChanged, element, "The hash algorithm for bundles changed from SHA1 to SHA512.");
1804 }
1805
1806 private void ConvertRegistrySearchElement(XElement element)
1807 {
1808 this.RenameWin64ToBitness(element);
1809 }
1810
1811 private void ConvertRequiredPrivilegeElement(XElement element)
1812 {
1813 this.ConvertInnerTextToAttribute(element, "Name");
1814 }
1815
1816 private void ConvertDataElement(XElement element)
1817 {
1818 this.ConvertInnerTextToAttribute(element, "Value");
1819 }
1820
1821 private void ConvertSequenceElement(XElement element)
1822 {
1823 foreach (var child in element.Elements())
1824 {
1825 this.ConvertInnerTextToAttribute(child, "Condition");
1826 }
1827 }
1828
1829 private void ConvertServiceArgumentElement(XElement element)
1830 {
1831 this.ConvertInnerTextToAttribute(element, "Value");
1832 }
1833
1834 private void ConvertSetDirectoryElement(XElement element)
1835 {
1836 this.ConvertInnerTextToAttribute(element, "Condition");
1837 }
1838
1839 private void ConvertSetPropertyElement(XElement element)
1840 {
1841 this.ConvertInnerTextToAttribute(element, "Condition");
1842 }
1843
1844 private void ConvertShortcutPropertyElement(XElement element)
1845 {
1846 this.ConvertInnerTextToAttribute(element, "Value");
1847 }
1848
1849 private void ConvertProvidesElement(XElement element)
1850 {
1851 if (this.OnInformation(ConverterTestType.IntegratedDependencyNamespace, element, "The Provides element has been integrated into the WiX v4 namespace. Remove the namespace."))
1852 {
1853 element.Name = ProvidesElementName;
1854 }
1855
1856 if (element.Parent.Name == ComponentElementName &&
1857 this.OnInformation(ConverterTestType.IntegratedDependencyNamespace, element, "The Provides element has been integrated into the WiX v4 namespace. Add the 'Check' attribute from the WixDependency.wixext to match v3 runtime behavior."))
1858 {
1859 element.Add(new XAttribute(DependencyCheckAttributeName, "yes"));
1860 }
1861 }
1862
1863 private void ConvertRequiresElement(XElement element)
1864 {
1865 if (this.OnInformation(ConverterTestType.IntegratedDependencyNamespace, element, "The Requires element has been integrated into the WiX v4 namespace. Remove the namespace."))
1866 {
1867 element.Name = RequiresElementName;
1868 }
1869
1870 if (element.Parent.Name == ProvidesElementName &&
1871 element.Parent.Parent?.Name == ComponentElementName &&
1872 this.OnInformation(ConverterTestType.IntegratedDependencyNamespace, element, "The Requires element has been integrated into the WiX v4 namespace. Add the 'Enforce' attribute from the WixDependency.wixext to match v3 runtime behavior."))
1873 {
1874 element.Add(new XAttribute(DependencyEnforceAttributeName, "yes"));
1875 }
1876 }
1877
1878 private void ConvertRequiresRefElement(XElement element)
1879 {
1880 if (this.OnInformation(ConverterTestType.IntegratedDependencyNamespace, element, "The RequiresRef element has been integrated into the WiX v4 namespace. Remove the namespace."))
1881 {
1882 element.Name = RequiresRefElementName;
1883 }
1884
1885 if (element.Parent.Name == ProvidesElementName &&
1886 element.Parent.Parent?.Name == ComponentElementName &&
1887 this.OnInformation(ConverterTestType.IntegratedDependencyNamespace, element, "The RequiresRef element has been integrated into the WiX v4 namespace. Add the 'Enforce' attribute from the WixDependency.wixext to match v3 runtime behavior."))
1888 {
1889 element.Add(new XAttribute(DependencyEnforceAttributeName, "yes"));
1890 }
1891 }
1892
1893 private void ConvertSuppressSignatureVerification(XElement element)
1894 {
1895 var suppressSignatureVerification = element.Attribute("SuppressSignatureVerification");
1896
1897 if (null != suppressSignatureVerification
1898 && this.OnInformation(ConverterTestType.SuppressSignatureVerificationObsolete, element, "The chain package element contains obsolete '{0}' attribute. The attribute will be removed.", suppressSignatureVerification.Name))
1899 {
1900 suppressSignatureVerification.Remove();
1901 }
1902 }
1903
1904 private void ConvertTagElement(XElement element)
1905 {
1906 if (this.OnInformation(ConverterTestType.TagElementRenamed, element, "The Tag element has been renamed. Use the 'SoftwareTag' element instead."))
1907 {
1908 element.Name = SoftwareTagElementName;
1909 }
1910
1911 this.RemoveAttributeIfPresent(element, "Licensed", ConverterTestType.SoftwareTagLicensedObsolete, "The {0} element contains obsolete '{1}' attribute. The attribute will be removed.");
1912 this.RemoveAttributeIfPresent(element, "Type", ConverterTestType.SoftwareTagLicensedObsolete, "The {0} element contains obsolete '{1}' attribute. The attribute will be removed.");
1913 this.RenameWin64ToBitness(element);
1914 }
1915
1916 private void ConvertTagRefElement(XElement element)
1917 {
1918 if (this.OnInformation(ConverterTestType.TagRefElementRenamed, element, "The TagRef element has been renamed. Use the 'SoftwareTagRef' element instead."))
1919 {
1920 element.Name = SoftwareTagRefElementName;
1921 }
1922 }
1923
1924 private void ConvertTextElement(XElement element)
1925 {
1926 this.ConvertInnerTextToAttribute(element, "Value");
1927 }
1928
1929 private void ConvertUITextElement(XElement element)
1930 {
1931 this.ConvertInnerTextToAttribute(element, "Value");
1932 }
1933
1934 private void ConvertWindowsInstallerPackageElement(XElement element)
1935 {
1936 this.ConvertSuppressSignatureVerification(element);
1937
1938 this.UpdatePackageCacheAttribute(element);
1939
1940 if (null != element.Attribute("DisplayInternalUI"))
1941 {
1942 this.OnError(ConverterTestType.DisplayInternalUiNotConvertable, element, "The DisplayInternalUI functionality has fundamentally changed and requires BootstrapperApplication support. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-bundles");
1943 }
1944 }
1945
1946 private void ConvertVerbElement(XElement element)
1947 {
1948 if (null != element.Attribute("Target"))
1949 {
1950 this.OnError(ConverterTestType.VerbTargetNotConvertable, element, "The Verb/@Target attribute has been replaced with typed @TargetFile and @TargetProperty attributes. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages");
1951 }
1952 }
1953
1954 private void ConvertCustomActionElement(XElement xCustomAction)
1955 {
1956 var xBinaryKey = xCustomAction.Attribute("BinaryKey");
1957 if (xBinaryKey != null && this.OnInformation(ConverterTestType.CustomActionKeysAreNowRefs, xCustomAction, "The CustomAction attributes have been renamed from BinaryKey and FileKey to BinaryRef and FileRef."))
1958 {
1959 xCustomAction.SetAttributeValue("BinaryRef", xBinaryKey.Value);
1960 xBinaryKey.Remove();
1961 xBinaryKey = xCustomAction.Attribute("BinaryRef");
1962 }
1963
1964 var xFileKey = xCustomAction.Attribute("FileKey");
1965 if (xFileKey != null && this.OnInformation(ConverterTestType.CustomActionKeysAreNowRefs, xCustomAction, "The CustomAction attributes have been renamed from BinaryKey and FileKey to BinaryRef and FileRef."))
1966 {
1967 xCustomAction.SetAttributeValue("FileRef", xFileKey.Value);
1968 xFileKey.Remove();
1969 }
1970
1971 if (xBinaryKey?.Value == "WixCA" || xBinaryKey?.Value == "UtilCA")
1972 {
1973 if (this.OnInformation(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA custom action DLL Binary table id has been renamed. Use the id 'Wix4UtilCA_X86' instead."))
1974 {
1975 xBinaryKey.Value = "Wix4UtilCA_X86";
1976 }
1977 }
1978
1979 if (xBinaryKey?.Value == "WixCA_x64" || xBinaryKey?.Value == "UtilCA_x64")
1980 {
1981 if (this.OnInformation(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA_x64 custom action DLL Binary table id has been renamed. Use the id 'Wix4UtilCA_X64' instead."))
1982 {
1983 xBinaryKey.Value = "Wix4UtilCA_X64";
1984 }
1985 }
1986
1987 var xDllEntry = xCustomAction.Attribute("DllEntry");
1988
1989 if (xDllEntry?.Value == "CAQuietExec" || xDllEntry?.Value == "CAQuietExec64")
1990 {
1991 if (this.OnInformation(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The CAQuietExec and CAQuietExec64 custom action ids have been renamed. Use the ids 'WixQuietExec' and 'WixQuietExec64' instead."))
1992 {
1993 xDllEntry.Value = xDllEntry.Value.Replace("CAQuietExec", "WixQuietExec");
1994 }
1995 }
1996
1997 var xProperty = xCustomAction.Attribute("Property");
1998
1999 if (xProperty?.Value == "QtExecCmdLine" || xProperty?.Value == "QtExec64CmdLine")
2000 {
2001 if (this.OnInformation(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The QtExecCmdLine and QtExec64CmdLine property ids have been renamed. Use the ids 'WixQuietExecCmdLine' and 'WixQuietExec64CmdLine' instead."))
2002 {
2003 xProperty.Value = xProperty.Value.Replace("QtExec", "WixQuietExec");
2004 }
2005 }
2006
2007 var xScript = xCustomAction.Attribute("Script");
2008 var collector = new InnerContentCollector();
2009
2010 if (xScript != null && collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(xCustomAction, out string value))
2011 {
2012 if (this.OnInformation(ConverterTestType.InnerTextDeprecated, xCustomAction, "Using {0} element text is deprecated. Extract the text to a file and use the 'ScriptSourceFile' attribute to reference it.", xCustomAction.Name.LocalName))
2013 {
2014 var scriptFolder = Path.GetDirectoryName(this.SourceFile) ?? String.Empty;
2015 var id = xCustomAction.Attribute("Id")?.Value ?? Guid.NewGuid().ToString("N");
2016 var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt";
2017
2018 var scriptFile = Path.Combine(scriptFolder, id + ext);
2019 File.WriteAllText(scriptFile, value);
2020
2021 RemoveChildren(xCustomAction);
2022 xCustomAction.Add(new XAttribute("ScriptSourceFile", scriptFile));
2023
2024 if (collector.Comments.Any())
2025 {
2026 var remainingNodes = xCustomAction.NodesAfterSelf().ToList();
2027 var replacementNodes = remainingNodes.Where(e => XmlNodeType.Text != e.NodeType);
2028 foreach (var node in remainingNodes)
2029 {
2030 node.Remove();
2031 }
2032 foreach (var comment in collector.Comments)
2033 {
2034 xCustomAction.Add(comment);
2035 xCustomAction.Add("\n");
2036 }
2037 foreach (var node in replacementNodes)
2038 {
2039 xCustomAction.Add(node);
2040 }
2041 }
2042 }
2043 }
2044 }
2045
2046 private void ConvertVariableElement(XElement xVariable)
2047 {
2048 var xType = xVariable.Attribute("Type");
2049 var xValue = xVariable.Attribute("Value");
2050 if (this.SourceVersion < 4)
2051 {
2052 if (xType == null)
2053 {
2054 if (WasImplicitlyStringTyped(xValue?.Value) &&
2055 this.OnInformation(ConverterTestType.AssignVariableTypeFormatted, xVariable, "The \"string\" variable type now denotes a literal string. Use \"formatted\" to keep the previous behavior."))
2056 {
2057 xVariable.Add(new XAttribute("Type", "formatted"));
2058 }
2059 }
2060 else if (xType.Value == "string" &&
2061 this.OnInformation(ConverterTestType.AssignVariableTypeFormatted, xVariable, "The \"string\" variable type now denotes a literal string. Use \"formatted\" to keep the previous behavior."))
2062 {
2063 xType.Value = "formatted";
2064 }
2065 }
2066 }
2067
2068 private void ConvertPropertyElement(XElement xProperty)
2069 {
2070 var xId = xProperty.Attribute("Id");
2071
2072 if (xId.Value == "QtExecCmdTimeout")
2073 {
2074 this.OnInformation(ConverterTestType.QtExecCmdTimeoutAmbiguous, xProperty, "QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout.");
2075 }
2076
2077 this.ConvertInnerTextToAttribute(xProperty, "Value");
2078 }
2079
2080 private void ConvertUtilCloseApplicationElementName(XElement element)
2081 {
2082 this.ConvertInnerTextToAttribute(element, "Condition");
2083 }
2084
2085 private void ConvertUtilPermissionExElement(XElement element)
2086 {
2087 if (this.SourceVersion < 4 && null == element.Attribute("Inheritable"))
2088 {
2089 var inheritable = element.Parent.Name == CreateFolderElementName;
2090 if (!inheritable)
2091 {
2092 if (this.OnInformation(ConverterTestType.AssignPermissionExInheritable, element, "The PermissionEx Inheritable attribute is being set to 'no' to ensure it remains the same as the v3 default."))
2093 {
2094 element.Add(new XAttribute("Inheritable", "no"));
2095 }
2096 }
2097 }
2098 }
2099
2100 private void ConvertUtilRegistrySearchElement(XElement element)
2101 {
2102 this.RenameWin64ToBitness(element);
2103
2104 if (this.SourceVersion < 4)
2105 {
2106 var result = element.Attribute("Result")?.Value;
2107 if (result == null || result == "value")
2108 {
2109 this.OnError(ConverterTestType.UtilRegistryValueSearchBehaviorChange, element, "Breaking change: util:RegistrySearch for a value no longer clears the variable when the key or value is missing. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-bundles");
2110 }
2111 }
2112 }
2113
2114 private void ConvertUtilXmlConfigElement(XElement element)
2115 {
2116 this.ConvertInnerTextToAttribute(element, "Value");
2117 }
2118
2119 /// <summary>
2120 /// Converts a Wix element.
2121 /// </summary>
2122 /// <param name="element">The Wix element to convert.</param>
2123 /// <returns>The converted element.</returns>
2124 private void ConvertElementWithoutNamespace(XElement element)
2125 {
2126 if (this.OnInformation(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName))
2127 {
2128 element.Name = WixNamespace.GetName(element.Name.LocalName);
2129
2130 element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace.
2131
2132 foreach (var elementWithoutNamespace in element.DescendantsAndSelf().Where(e => XNamespace.None == e.Name.Namespace))
2133 {
2134 elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName);
2135 }
2136 }
2137 }
2138
2139 /// <summary>
2140 /// Converts a WixLocalization element.
2141 /// </summary>
2142 /// <param name="element">The WixLocalization element to convert.</param>
2143 /// <returns>The converted element.</returns>
2144 private void ConvertWixLocalizationElementWithoutNamespace(XElement element)
2145 {
2146 if (this.OnInformation(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WxlNamespace.NamespaceName))
2147 {
2148 element.Name = WxlNamespace.GetName(element.Name.LocalName);
2149
2150 element.Add(new XAttribute("xmlns", WxlNamespace.NamespaceName)); // set the default namespace.
2151
2152 foreach (var elementWithoutNamespace in element.DescendantsAndSelf().Where(e => XNamespace.None == e.Name.Namespace))
2153 {
2154 elementWithoutNamespace.Name = WxlNamespace.GetName(elementWithoutNamespace.Name.LocalName);
2155 }
2156 }
2157 }
2158
2159 private void ConvertWixLocalizationStringElement(XElement element)
2160 {
2161 this.ConvertInnerTextToAttribute(element, "Value");
2162 }
2163
2164 private void ConvertWixLocalizationUIElement(XElement element)
2165 {
2166 this.ConvertInnerTextToAttribute(element, "Text");
2167 }
2168
2169 private void ConvertInnerTextToAttribute(XElement element, string attributeName)
2170 {
2171 var collector = new InnerContentCollector();
2172
2173 if (collector.CollectInnerTextAndCommentsForAttributeValue(element, out string value))
2174 {
2175 // If the target attribute already exists, error if we have anything more than whitespace.
2176 var attribute = element.Attribute(attributeName);
2177 if (attribute != null)
2178 {
2179 this.OnError(ConverterTestType.InnerTextDeprecated, attribute, "Using {0} element text is deprecated. Remove the element's text and use only the '{1}' attribute. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages", element.Name.LocalName, attributeName);
2180 }
2181 else if (this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}' attribute instead.", element.Name.LocalName, attributeName))
2182 {
2183 using (var lab = new ConversionLab(element))
2184 {
2185 lab.RemoveOrphanTextNodes();
2186 element.Add(new XAttribute(attributeName, value));
2187 lab.AddCommentsAsSiblings(collector.Comments);
2188 }
2189 }
2190 }
2191 }
2192
2193 void RemoveAttributeIfPresent(XElement element, string attributeName, ConverterTestType type, string format)
2194 {
2195 var xAttribute = element.Attribute(attributeName);
2196 if (null != xAttribute && this.OnInformation(type, element, format, element.Name.LocalName, xAttribute.Name))
2197 {
2198 xAttribute.Remove();
2199 }
2200 }
2201
2202 private void RenameWin64ToBitness(XElement element)
2203 {
2204 var win64 = element.Attribute("Win64");
2205 if (win64 != null && this.OnInformation(ConverterTestType.Win64AttributeRenamed, element, "The {0} element's Win64 attribute has been renamed. Use the Bitness attribute instead.", element.Name.LocalName))
2206 {
2207 var value = this.UpdateWin64ValueToBitnessValue(win64);
2208 element.Add(new XAttribute("Bitness", value));
2209 win64.Remove();
2210 }
2211 }
2212
2213 private void UpdatePackageCacheAttribute(XElement element)
2214 {
2215 var cacheAttribute = element.Attribute("Cache");
2216 var cacheValue = cacheAttribute?.Value;
2217 string replacement = null;
2218
2219 switch (cacheValue)
2220 {
2221 case "yes":
2222 replacement = "keep";
2223 break;
2224 case "no":
2225 replacement = "remove";
2226 break;
2227 case "always":
2228 replacement = "force";
2229 break;
2230 }
2231
2232 if (!String.IsNullOrEmpty(replacement) &&
2233 this.OnInformation(ConverterTestType.BundlePackageCacheAttributeValueObsolete, element, "The chain package element 'Cache' attribute contains obsolete '{0}' value. The value should be '{1}' instead.", cacheValue, replacement))
2234 {
2235 cacheAttribute.SetValue(replacement);
2236 }
2237 }
2238
2239 private string UpdateWin64ValueToBitnessValue(XAttribute xWin64Attribute)
2240 {
2241 var value = xWin64Attribute.Value ?? String.Empty;
2242 switch (value)
2243 {
2244 case "yes":
2245 return "always64";
2246 case "no":
2247 return "always32";
2248 default:
2249 this.OnError(ConverterTestType.Win64AttributeRenameCannotBeAutomatic, xWin64Attribute, "Breaking change: The Win64 attribute's value '{0}' cannot be converted automatically to the new Bitness attribute. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages", value);
2250 return value;
2251 }
2252 }
2253
2254 private IEnumerable<ConverterTestType> YieldConverterTypes(IEnumerable<string> types)
2255 {
2256 if (null != types)
2257 {
2258 foreach (var type in types)
2259 {
2260 if (Enum.TryParse<ConverterTestType>(type, true, out var itt))
2261 {
2262 yield return itt;
2263 }
2264 else // not a known ConverterTestType
2265 {
2266 this.OnError(ConverterTestType.ConverterTestTypeUnknown, null, "Unknown error type: '{0}'.", type);
2267 }
2268 }
2269 }
2270 }
2271
2272 private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable<XElement> elements, Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces)
2273 {
2274 foreach (var element in elements)
2275 {
2276 if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out var ns))
2277 {
2278 element.Name = ns.GetName(element.Name.LocalName);
2279 }
2280
2281 // Remove all the attributes and add them back with their namespace updated (as necessary).
2282 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
2283 element.RemoveAttributes();
2284
2285 foreach (var attribute in attributes)
2286 {
2287 var convertedAttribute = attribute;
2288
2289 if (attribute.IsNamespaceDeclaration)
2290 {
2291 if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns))
2292 {
2293 if (ns == XNamespace.None)
2294 {
2295 continue;
2296 }
2297
2298 convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName);
2299 }
2300 }
2301 else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns))
2302 {
2303 convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value);
2304 }
2305
2306 if (convertedAttribute != attribute)
2307 {
2308 foreach (var message in attribute.Annotations<Message>())
2309 {
2310 convertedAttribute.AddAnnotation(message);
2311 }
2312 }
2313
2314 element.Add(convertedAttribute);
2315 }
2316 }
2317 }
2318
2319 /// <summary>
2320 /// Determine if the whitespace preceding a node is appropriate for its depth level.
2321 /// </summary>
2322 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
2323 /// <param name="level">The depth level that should match this whitespace.</param>
2324 /// <param name="whitespace">The whitespace to validate.</param>
2325 /// <returns>true if the whitespace is legal; false otherwise.</returns>
2326 private static bool LeadingWhitespaceValid(int indentationAmount, int level, string whitespace)
2327 {
2328 // Strip off leading newlines; there can be an arbitrary number of these.
2329 whitespace = whitespace.TrimStart(XDocumentNewLine);
2330
2331 var indentation = new string(' ', level * indentationAmount);
2332
2333 return whitespace == indentation;
2334 }
2335
2336 /// <summary>
2337 /// Fix the whitespace in a whitespace node.
2338 /// </summary>
2339 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
2340 /// <param name="level">The depth level of the desired whitespace.</param>
2341 /// <param name="whitespace">The whitespace node to fix.</param>
2342 private static void FixupWhitespace(int indentationAmount, int level, XText whitespace)
2343 {
2344 var value = new StringBuilder(whitespace.Value.Length);
2345
2346 // Keep any previous preceeding new lines.
2347 var newlines = whitespace.Value.TakeWhile(c => c == XDocumentNewLine).Count();
2348
2349 // Ensure there is always at least one new line before the indentation.
2350 value.Append(XDocumentNewLine, newlines == 0 ? 1 : newlines);
2351
2352 whitespace.Value = value.Append(' ', level * indentationAmount).ToString();
2353 }
2354
2355 private void ConvertMbaPrereqVariables()
2356 {
2357 XElement root = this.XRoot;
2358 VisitElement(root, x =>
2359 {
2360 if (x is XElement e && e.Attribute("Id") is XAttribute a)
2361 {
2362 if (e.Name == ExePackageElementName || e.Name == MsiPackageElementName ||
2363 e.Name == MspPackageElementName || e.Name == MsuPackageElementName)
2364 {
2365 if (!this.State.ChainPackageElementsById.TryGetValue(a.Value, out var elements))
2366 {
2367 elements = new List<XElement>();
2368 this.State.ChainPackageElementsById.Add(a.Value, elements);
2369 }
2370
2371 elements.Add(e);
2372 }
2373 else if (e.Name == WixVariableElementName && e.Attribute("Value") != null)
2374 {
2375 switch (a.Value)
2376 {
2377 case "WixMbaPrereqPackageId":
2378 this.State.WixMbaPrereqPackageIdElements.Add(e);
2379 break;
2380 case "WixMbaPrereqLicenseUrl":
2381 this.State.WixMbaPrereqLicenseUrlElements.Add(e);
2382 break;
2383 }
2384 }
2385 }
2386 return true;
2387 });
2388
2389 if (this.State.WixMbaPrereqPackageIdElements.Count == 1 && this.State.WixMbaPrereqLicenseUrlElements.Count < 2)
2390 {
2391 var wixMbaPrereqPackageIdElement = this.State.WixMbaPrereqPackageIdElements[0];
2392 var packageId = wixMbaPrereqPackageIdElement.Attribute("Value")?.Value;
2393 if (this.State.ChainPackageElementsById.TryGetValue(packageId, out var packageElements) && packageElements.Count == 1)
2394 {
2395 var packageElement = packageElements[0];
2396
2397 if (this.OnInformation(ConverterTestType.WixMbaPrereqPackageIdDeprecated, wixMbaPrereqPackageIdElement, "The magic WixVariable 'WixMbaPrereqPackageId' has been removed. Add bal:PrereqPackage=\"yes\" to the target package instead."))
2398 {
2399 packageElement.Add(new XAttribute(BalPrereqPackageAttributeName, "yes"));
2400
2401 using (var lab = new ConversionLab(wixMbaPrereqPackageIdElement))
2402 {
2403 lab.RemoveTargetElement();
2404 }
2405
2406 this.State.WixMbaPrereqPackageIdElements.Clear();
2407 }
2408
2409 if (this.State.WixMbaPrereqLicenseUrlElements.Count == 1)
2410 {
2411 var wixMbaPrereqLicenseUrlElement = this.State.WixMbaPrereqLicenseUrlElements[0];
2412 if (this.OnInformation(ConverterTestType.WixMbaPrereqLicenseUrlDeprecated, wixMbaPrereqLicenseUrlElement, "The magic WixVariable 'WixMbaPrereqLicenseUrl' has been removed. Add bal:PrereqLicenseUrl=\"<url>\" to a prereq package instead."))
2413 {
2414 var licenseUrl = wixMbaPrereqLicenseUrlElement.Attribute("Value")?.Value;
2415 packageElement.Add(new XAttribute(BalPrereqLicenseUrlAttributeName, licenseUrl));
2416 using (var lab = new ConversionLab(wixMbaPrereqLicenseUrlElement))
2417 {
2418 lab.RemoveTargetElement();
2419 }
2420
2421 this.State.WixMbaPrereqLicenseUrlElements.Clear();
2422 }
2423 }
2424 }
2425 }
2426
2427 foreach (var element in this.State.WixMbaPrereqPackageIdElements)
2428 {
2429 this.OnError(ConverterTestType.WixMbaPrereqPackageIdDeprecated, element, "The magic WixVariable 'WixMbaPrereqPackageId' has been removed. Add bal:PrereqPackage=\"yes\" to the target package instead. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-bundles");
2430 }
2431
2432 foreach (var element in this.State.WixMbaPrereqLicenseUrlElements)
2433 {
2434 this.OnError(ConverterTestType.WixMbaPrereqLicenseUrlDeprecated, element, "The magic WixVariable 'WixMbaPrereqLicenseUrl' has been removed. Add bal:PrereqLicenseUrl=\"<url>\" to a prereq package instead. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-bundles");
2435 }
2436 }
2437
2438 /// <summary>
2439 /// Removes unused namespaces from the element and its children.
2440 /// </summary>
2441 /// <param name="root">Root element to start at.</param>
2442 private void RemoveUnusedNamespaces(XElement root)
2443 {
2444 var declarations = new List<XAttribute>();
2445 var namespaces = new HashSet<string>();
2446
2447 VisitElement(root, x =>
2448 {
2449 if (x is XAttribute a && a.IsNamespaceDeclaration)
2450 {
2451 declarations.Add(a);
2452 namespaces.Add(a.Value);
2453 }
2454 return true;
2455 });
2456
2457 foreach (var ns in namespaces.ToList())
2458 {
2459 VisitElement(root, x =>
2460 {
2461 if ((x is XElement e && e.Name.Namespace == ns) ||
2462 (x is XAttribute a && !a.IsNamespaceDeclaration && a.Name.Namespace == ns))
2463 {
2464 namespaces.Remove(ns);
2465 return false;
2466 }
2467
2468 return true;
2469 });
2470 }
2471
2472 foreach (var declaration in declarations)
2473 {
2474 if (namespaces.Contains(declaration.Value) &&
2475 this.OnInformation(ConverterTestType.RemoveUnusedNamespaces, declaration, "The namespace '{0}' is not used. Remove unused namespaces.", declaration.Value))
2476 {
2477 declaration.Remove();
2478 }
2479 }
2480 }
2481
2482 private void MoveNamespacesToRoot(XElement root)
2483 {
2484 var rootNamespaces = new HashSet<string>();
2485 var rootNamespacePrefixes = new HashSet<string>();
2486 var nonRootDeclarations = new List<XAttribute>();
2487
2488 VisitElement(root, x =>
2489 {
2490 if (x is XAttribute a && a.IsNamespaceDeclaration)
2491 {
2492 if (x.Parent == root)
2493 {
2494 rootNamespaces.Add(a.Value);
2495 rootNamespacePrefixes.Add(a.Name.LocalName);
2496 }
2497 else
2498 {
2499 nonRootDeclarations.Add(a);
2500 }
2501 }
2502
2503 return true;
2504 });
2505
2506 foreach (var declaration in nonRootDeclarations)
2507 {
2508 if (this.OnInformation(ConverterTestType.MoveNamespacesToRoot, declaration, "Namespace should be defined on the root. The '{0}' namespace was move to the root element.", declaration.Value))
2509 {
2510 if (!rootNamespaces.Contains(declaration.Value))
2511 {
2512 var prefix = GetNamespacePrefix(declaration, rootNamespacePrefixes);
2513
2514 var rootDeclaration = new XAttribute(XNamespace.Xmlns + prefix, declaration.Value);
2515 root.Add(rootDeclaration);
2516
2517 rootNamespaces.Add(rootDeclaration.Value);
2518 rootNamespacePrefixes.Add(rootDeclaration.Name.LocalName);
2519 }
2520
2521 declaration.Remove();
2522 }
2523 }
2524 }
2525
2526 private int ReportMessages(XDocument document, bool saved)
2527 {
2528 var conversionCount = this.ConversionMessages.Count;
2529
2530 // If the converted/formatted document was saved, update the source line numbers
2531 // in the messages since they will possibly be wrong.
2532 if (saved && conversionCount > 0)
2533 {
2534 var fixedupMessages = new HashSet<Message>();
2535
2536 // Load the converted document so we can look up new line numbers for
2537 // messages.
2538 var convertedDocument = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
2539 var convertedNavigator = convertedDocument.CreateNavigator();
2540
2541 // Look through all nodes in the document and try to fix up their line numbers.
2542 foreach (var elements in document.Descendants())
2543 {
2544 var fixedup = this.FixupMessageLineNumbers(elements, convertedNavigator);
2545 fixedupMessages.AddRange(fixedup);
2546
2547 // Attributes are not considered nodes so they must be enumerated independently.
2548 if (elements is XElement element)
2549 {
2550 foreach (var attribute in element.Attributes())
2551 {
2552 fixedup = this.FixupMessageLineNumbers(attribute, convertedNavigator);
2553 fixedupMessages.AddRange(fixedup);
2554 }
2555 }
2556 }
2557
2558 // For any messages that couldn't be fixed up, remove their line numbers since they point at lines
2559 // that are possibly wrong after the conversion.
2560 for (var i = 0; i < this.ConversionMessages.Count; ++i)
2561 {
2562 var message = this.ConversionMessages[i];
2563
2564 if (!fixedupMessages.Contains(message))
2565 {
2566 this.ConversionMessages[i] = new Message(new SourceLineNumber(this.SourceFile), message.Level, message.Id, message.ResourceNameOrFormat, message.MessageArgs);
2567 }
2568 }
2569 }
2570
2571 foreach (var message in this.ConversionMessages)
2572 {
2573 this.Messaging.Write(message);
2574 }
2575
2576 return conversionCount;
2577 }
2578
2579 private IReadOnlyCollection<Message> FixupMessageLineNumbers(XObject obj, XPathNavigator savedDocument)
2580 {
2581 var messages = obj.Annotations<Message>().ToList();
2582
2583 if (messages.Count == 0)
2584 {
2585 return Array.Empty<Message>();
2586 }
2587
2588 // We can't fix up line numbers based on attributes but we can fix up using their parent element, so do so.
2589 if (obj is XAttribute attribute)
2590 {
2591 obj = attribute.Parent;
2592 }
2593
2594 var fixedupMessages = new List<Message>();
2595
2596 var modifiedLineNumber = this.GetModifiedNodeLineNumber((XElement)obj, savedDocument);
2597
2598 foreach (var message in messages)
2599 {
2600 var fixedupMessage = message;
2601
2602 // If the line number wasn't modified, the existing message's source line nuber is correct
2603 // so skip creating a new one.
2604 if (modifiedLineNumber != null)
2605 {
2606 var index = this.ConversionMessages.IndexOf(message);
2607
2608 fixedupMessage = new Message(modifiedLineNumber, message.Level, message.Id, message.ResourceNameOrFormat, message.MessageArgs);
2609
2610 this.ConversionMessages[index] = fixedupMessage;
2611 }
2612
2613 fixedupMessages.Add(fixedupMessage);
2614 }
2615
2616 return fixedupMessages;
2617 }
2618
2619 private SourceLineNumber GetModifiedNodeLineNumber(XElement element, XPathNavigator savedDocument)
2620 {
2621 var xpathToOld = CalculateXPath(element);
2622
2623 var newNode = savedDocument.SelectSingleNode(xpathToOld);
2624 var newLineNumber = (newNode as IXmlLineInfo).LineNumber;
2625
2626 var oldLineNumber = (element as IXmlLineInfo)?.LineNumber;
2627 return (oldLineNumber != newLineNumber) ? new SourceLineNumber(this.SourceFile, newLineNumber) : null;
2628 }
2629
2630 /// <summary>
2631 /// Output an error message to the console.
2632 /// </summary>
2633 /// <param name="converterTestType">The type of converter test.</param>
2634 /// <param name="node">The node that caused the error.</param>
2635 /// <param name="message">Detailed error message.</param>
2636 /// <param name="args">Additional formatted string arguments.</param>
2637 /// <returns>Returns true indicating that action should be taken on this error, and false if it should be ignored.</returns>
2638 private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args)
2639 {
2640 return this.OnMessage(MessageLevel.Error, converterTestType, node, message, args);
2641 }
2642
2643 /// <summary>
2644 /// Output an information message to the console.
2645 /// </summary>
2646 /// <param name="converterTestType">The type of converter test.</param>
2647 /// <param name="node">The node that caused the error.</param>
2648 /// <param name="message">Detailed error message.</param>
2649 /// <param name="args">Additional formatted string arguments.</param>
2650 /// <returns>Returns true indicating that action should be taken on this message, and false if it should be ignored.</returns>
2651 private bool OnInformation(ConverterTestType converterTestType, XObject node, string message, params object[] args)
2652 {
2653 return this.OnMessage(MessageLevel.Information, converterTestType, node, message, args);
2654 }
2655
2656 private bool OnMessage(MessageLevel level, ConverterTestType converterTestType, XObject node, string message, params object[] args)
2657 {
2658 // Ignore the error if explicitly ignored or outside the range of the current operation.
2659 if (this.IgnoreErrors.Contains(converterTestType) ||
2660 (this.Operation == ConvertOperation.Convert && converterTestType < ConverterTestType.EndIgnoreInConvert) ||
2661 (this.Operation == ConvertOperation.Format && converterTestType > ConverterTestType.BeginIgnoreInFormat))
2662 {
2663 return false;
2664 }
2665
2666 var sourceLine = SourceLineNumberForXmlLineInfo(this.SourceFile, node);
2667
2668 var prefix = String.Empty;
2669 if (level == MessageLevel.Information)
2670 {
2671 prefix = "[Converted] ";
2672 }
2673 else if (level == MessageLevel.Error && this.ErrorsAsWarnings.Contains(converterTestType))
2674 {
2675 level = MessageLevel.Warning;
2676 }
2677
2678 var format = prefix + message + $" ({converterTestType})";
2679
2680 var msg = new Message(sourceLine, level, (int)converterTestType, format, args);
2681
2682 // Add the message as a node annotation so it could be possible to remap source line numbers
2683 // to their new locations after converting/formatting (since lines of code could be moved).
2684 node?.AddAnnotation(msg);
2685 this.ConversionMessages.Add(msg);
2686
2687 return true;
2688 }
2689
2690 private static string CalculateXPath(XElement element)
2691 {
2692 var builder = new StringBuilder();
2693
2694 while (element != null)
2695 {
2696 var index = element.Parent?.Elements().TakeWhile(e => e != element).Count() ?? 0;
2697 builder.Insert(0, $"/*[{index + 1}]");
2698
2699 element = element.Parent;
2700 }
2701
2702 return builder.ToString();
2703 }
2704
2705 private static SourceLineNumber SourceLineNumberForXmlLineInfo(string sourceFile, IXmlLineInfo lineInfo)
2706 {
2707 return (lineInfo?.HasLineInfo() == true) ? new SourceLineNumber(sourceFile, lineInfo.LineNumber) : new SourceLineNumber(sourceFile ?? "wix.exe");
2708 }
2709
2710 /// <summary>
2711 /// Return an identifier based on passed file/directory name
2712 /// </summary>
2713 /// <param name="name">File/directory name to generate identifer from</param>
2714 /// <returns>A version of the name that is a legal identifier.</returns>
2715 /// <remarks>This is duplicated from WiX's Common class.</remarks>
2716 private static string GetIdentifierFromName(string name)
2717 {
2718 var result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
2719
2720 // MSI identifiers must begin with an alphabetic character or an
2721 // underscore. Prefix all other values with an underscore.
2722 if (AddPrefix.IsMatch(name))
2723 {
2724 result = String.Concat("_", result);
2725 }
2726
2727 return result;
2728 }
2729
2730 private static string GetNamespacePrefix(XAttribute declaration, HashSet<string> usedPrefixes)
2731 {
2732 var baseNamespace = String.Empty;
2733
2734 switch (declaration.Value)
2735 {
2736 case "http://wixtoolset.org/schemas/v4/wxs/bal":
2737 baseNamespace = "bal";
2738 break;
2739
2740 case "http://wixtoolset.org/schemas/v4/wxs/complus":
2741 baseNamespace = "complus";
2742 break;
2743
2744 case "http://wixtoolset.org/schemas/v4/wxs/dependency":
2745 baseNamespace = "dependency";
2746 break;
2747
2748 case "http://wixtoolset.org/schemas/v4/wxs/difxapp":
2749 baseNamespace = "difx";
2750 break;
2751
2752 case "http://wixtoolset.org/schemas/v4/wxs/directx":
2753 baseNamespace = "directx";
2754 break;
2755
2756 case "http://wixtoolset.org/schemas/v4/wxs/firewall":
2757 baseNamespace = "fw";
2758 break;
2759
2760 case "http://wixtoolset.org/schemas/v4/wxs/http":
2761 baseNamespace = "http";
2762 break;
2763
2764 case "http://wixtoolset.org/schemas/v4/wxs/iis":
2765 baseNamespace = "iis";
2766 break;
2767
2768 case "http://wixtoolset.org/schemas/v4/wxs/msmq":
2769 baseNamespace = "msmq";
2770 break;
2771
2772 case "http://wixtoolset.org/schemas/v4/wxs/netfx":
2773 baseNamespace = "netfx";
2774 break;
2775
2776 case "http://wixtoolset.org/schemas/v4/wxs/powershell":
2777 baseNamespace = "ps";
2778 break;
2779
2780 case "http://wixtoolset.org/schemas/v4/wxs/sql":
2781 baseNamespace = "sql";
2782 break;
2783
2784 case "http://wixtoolset.org/schemas/v4/wxs/ui":
2785 baseNamespace = "ui";
2786 break;
2787
2788 case "http://wixtoolset.org/schemas/v4/wxs/util":
2789 baseNamespace = "util";
2790 break;
2791
2792 case "http://wixtoolset.org/schemas/v4/wxs/vs":
2793 baseNamespace = "vs";
2794 break;
2795 }
2796
2797 var ns = baseNamespace;
2798
2799 for (var i = 1; usedPrefixes.Contains(ns); ++i)
2800 {
2801 ns = baseNamespace + i;
2802 }
2803
2804 return ns;
2805 }
2806
2807 private static string LowercaseFirstChar(string value)
2808 {
2809 if (!String.IsNullOrEmpty(value))
2810 {
2811 var c = Char.ToLowerInvariant(value[0]);
2812 if (c != value[0])
2813 {
2814 var remainder = value.Length > 1 ? value.Substring(1) : String.Empty;
2815 return c + remainder;
2816 }
2817 }
2818
2819 return value;
2820 }
2821
2822 private static string UppercaseFirstChar(string value)
2823 {
2824 if (!String.IsNullOrEmpty(value))
2825 {
2826 var c = Char.ToUpperInvariant(value[0]);
2827 if (c != value[0])
2828 {
2829 var remainder = value.Length > 1 ? value.Substring(1) : String.Empty;
2830 return c + remainder;
2831 }
2832 }
2833
2834 return value;
2835 }
2836
2837 private static void AddTargetDirDirectoryAttributeToComponents(XElement element)
2838 {
2839 // Move the TARGETDIR reference to the Component Directory attribute
2840 foreach (var xComponent in element.Elements(ComponentElementName).Where(x => x.Attribute("Directory") is null))
2841 {
2842 xComponent.Add(new XAttribute("Directory", "TARGETDIR"));
2843 }
2844 }
2845
2846 private static void RemoveElementKeepChildren(XElement element)
2847 {
2848 var parentElement = element.Parent;
2849
2850 element.Remove();
2851
2852 if (parentElement.FirstNode is XText text && String.IsNullOrWhiteSpace(text.Value))
2853 {
2854 parentElement.FirstNode.Remove();
2855 }
2856
2857 foreach (var child in element.Nodes())
2858 {
2859 parentElement.Add(child);
2860 }
2861
2862 element.RemoveAll();
2863
2864 if (parentElement.FirstNode is XText textAgain && String.IsNullOrWhiteSpace(textAgain.Value))
2865 {
2866 parentElement.FirstNode.Remove();
2867 }
2868 }
2869
2870 private static void RenameElementToStandardDirectory(XElement element)
2871 {
2872 element.Name = StandardDirectoryElementName;
2873
2874 foreach (var attrib in element.Attributes().Where(a => a.Name.LocalName != "Id").ToList())
2875 {
2876 attrib.Remove();
2877 }
2878 }
2879
2880 private static bool IsTextNode(XNode node, out XText text)
2881 {
2882 text = null;
2883
2884 if (node.NodeType == XmlNodeType.Text || node.NodeType == XmlNodeType.CDATA)
2885 {
2886 text = (XText)node;
2887 }
2888
2889 return text != null;
2890 }
2891
2892 private static void TrimLeadingText(XDocument document)
2893 {
2894 while (IsTextNode(document.Nodes().FirstOrDefault(), out var text))
2895 {
2896 text.Remove();
2897 }
2898 }
2899
2900 private static void RemoveChildren(XElement element)
2901 {
2902 var nodes = element.Nodes().ToList();
2903 foreach (var node in nodes)
2904 {
2905 node.Remove();
2906 }
2907 }
2908
2909 private static bool VisitElement(XElement element, Func<XObject, bool> visitor)
2910 {
2911 if (!visitor(element))
2912 {
2913 return false;
2914 }
2915
2916 if (!element.Attributes().All(a => visitor(a)))
2917 {
2918 return false;
2919 }
2920
2921 return element.Elements().All(e => VisitElement(e, visitor));
2922 }
2923
2924 private static bool WasImplicitlyStringTyped(string value)
2925 {
2926 if (value == null)
2927 {
2928 return false;
2929 }
2930 else if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
2931 {
2932 if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _))
2933 {
2934 return false;
2935 }
2936 else if (Version.TryParse(value.Substring(1), out var _))
2937 {
2938 return false;
2939 }
2940 }
2941 else if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _))
2942 {
2943 return false;
2944 }
2945
2946 return true;
2947 }
2948
2949 // This class encapsulates methods for extraving text and comments from XElements. Multiple calls can be made to the collection
2950 // methods to processs multiple XElements. The Comments property is used to extract the list of comments accumulated during the collection process.
2951 private class InnerContentCollector
2952 {
2953 public InnerContentCollector()
2954 {
2955 this.Comments = new List<XNode>();
2956 }
2957
2958 public List<XNode> Comments { get; private set; }
2959
2960 public bool CollectInnerTextAndCommentsForAttributeValue(XElement element, out string collectedText)
2961 {
2962 char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
2963 var nodes = element.Nodes().ToList();
2964 var inWhitespace = false;
2965 var cDataFound = false;
2966 var sb = new StringBuilder();
2967
2968 foreach (var node in nodes)
2969 {
2970 if (XmlNodeType.Comment == node.NodeType)
2971 {
2972 this.Comments.Add(node);
2973 }
2974 else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
2975 {
2976 var isCData = XmlNodeType.CDATA == node.NodeType;
2977
2978 if (isCData)
2979 {
2980 cDataFound = true;
2981 }
2982
2983 var text = node is XText xtext ? xtext.Value : String.Empty;
2984 var nodeSB = new StringBuilder();
2985
2986 foreach (var c in text)
2987 {
2988 char? emit = c;
2989
2990 // Replace contiguous whitespace with a single space.
2991 if (' ' == c || '\r' == c || '\n' == c || '\t' == c)
2992 {
2993 if (!inWhitespace)
2994 {
2995 inWhitespace = true;
2996 emit = ' ';
2997 }
2998 else
2999 {
3000 emit = null;
3001 }
3002 }
3003 else
3004 {
3005 inWhitespace = false;
3006 }
3007
3008 if (emit.HasValue)
3009 {
3010 nodeSB.Append(emit);
3011 }
3012 }
3013
3014 text = nodeSB.ToString().Trim(whitespaceChars);
3015 sb.Append(text);
3016 }
3017 }
3018 var found = false;
3019 collectedText = sb.ToString();
3020
3021 if (0 < collectedText.Length)
3022 {
3023 found = true;
3024 }
3025
3026 collectedText = collectedText.Trim(whitespaceChars);
3027
3028 if (cDataFound)
3029 {
3030 found = true;
3031
3032 if (0 == collectedText.Length)
3033 {
3034 collectedText = " ";
3035 }
3036 }
3037
3038 return found;
3039 }
3040
3041 public bool CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(XElement element, out string collectedText)
3042 {
3043 char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
3044 var nodes = element.Nodes().ToList();
3045 var inWhitespace = false;
3046 var cDataFound = false;
3047 var whitespaceFound = false;
3048 var sb = new StringBuilder();
3049
3050 foreach (var node in nodes)
3051 {
3052 if (XmlNodeType.Comment == node.NodeType)
3053 {
3054 this.Comments.Add(node);
3055 }
3056 else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
3057 {
3058 var isCData = XmlNodeType.CDATA == node.NodeType;
3059
3060 if (isCData)
3061 {
3062 cDataFound = true;
3063 }
3064
3065 var text = node is XText xtext ? xtext.Value : String.Empty;
3066 var nodeSB = new StringBuilder();
3067
3068 foreach (var c in text)
3069 {
3070 char? emit = c;
3071
3072 // Replace contiguous whitespace with a single space.
3073 if (' ' == c || '\r' == c || '\n' == c || '\t' == c)
3074 {
3075 if (!inWhitespace)
3076 {
3077 inWhitespace = true;
3078 whitespaceFound = true;
3079 emit = ' ';
3080 }
3081 else
3082 {
3083 emit = null;
3084 }
3085 }
3086 else
3087 {
3088 inWhitespace = false;
3089 }
3090
3091 if (emit.HasValue)
3092 {
3093 nodeSB.Append(emit);
3094 }
3095 }
3096
3097 text = nodeSB.ToString();
3098
3099 if (0 < text.Length)
3100 {
3101 text = text.Trim(whitespaceChars);
3102
3103 if (0 == text.Length)
3104 {
3105 text = " ";
3106 }
3107 }
3108
3109 sb.Append(text);
3110 }
3111 }
3112 var found = false;
3113 collectedText = sb.ToString();
3114
3115 if (0 < collectedText.Length)
3116 {
3117 found = true;
3118 }
3119
3120 collectedText = collectedText.Trim(whitespaceChars);
3121
3122 if (whitespaceFound)
3123 {
3124 found = true;
3125 }
3126
3127 if (cDataFound)
3128 {
3129 found = true;
3130
3131 if (0 == collectedText.Length)
3132 {
3133 collectedText = " ";
3134 }
3135 }
3136
3137 return found;
3138 }
3139
3140 public bool CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(XElement element, out string collectedText)
3141 {
3142 var value = String.Empty;
3143 char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
3144 var nodes = element.Nodes().ToList();
3145 var cDataFound = false;
3146 var sb = new StringBuilder();
3147
3148 foreach (var node in nodes)
3149 {
3150 if (XmlNodeType.Comment == node.NodeType)
3151 {
3152 this.Comments.Add(node);
3153 }
3154 else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
3155 {
3156 var isCData = XmlNodeType.CDATA == node.NodeType;
3157
3158 if (isCData)
3159 {
3160 cDataFound = true;
3161 }
3162
3163 var text = node is XText xtext ? xtext.Value.Trim(whitespaceChars) : String.Empty;
3164 sb.Append(text);
3165 }
3166 }
3167
3168 var found = false;
3169
3170 collectedText = sb.ToString();
3171
3172 if (0 < collectedText.Length)
3173 {
3174 found = true;
3175 }
3176
3177 collectedText = collectedText.Trim(whitespaceChars);
3178
3179 if (cDataFound)
3180 {
3181 found = true;
3182
3183 if (0 == collectedText.Length)
3184 {
3185 collectedText = " ";
3186 }
3187 }
3188
3189 return found;
3190 }
3191 }
3192
3193 /// <summary>
3194 /// Converter test types. These are used to condition error messages down to warnings.
3195 /// </summary>
3196 private enum ConverterTestType
3197 {
3198 /// <summary>
3199 /// Internal-only: displayed when a string cannot be converted to an ConverterTestType.
3200 /// </summary>
3201 ConverterTestTypeUnknown,
3202
3203 /// <summary>
3204 /// Displayed when an XML loading exception has occurred.
3205 /// </summary>
3206 XmlException,
3207
3208 /// <summary>
3209 /// Displayed when the whitespace preceding a node is wrong.
3210 /// </summary>
3211 WhitespacePrecedingNodeWrong,
3212
3213 /// <summary>
3214 /// Displayed when the whitespace preceding an end element is wrong.
3215 /// </summary>
3216 WhitespacePrecedingEndElementWrong,
3217
3218 /// Before this point, ignore errors on convert operation
3219 EndIgnoreInConvert,
3220
3221 /// <summary>
3222 /// Displayed when the XML declaration is present in the source file.
3223 /// </summary>
3224 DeclarationPresent,
3225
3226 /// <summary>
3227 /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file.
3228 /// </summary>
3229 UnauthorizedAccessException,
3230
3231 /// After this point, ignore errors on format operation
3232 BeginIgnoreInFormat,
3233
3234 /// <summary>
3235 /// Displayed when the xmlns attribute is missing from the document element.
3236 /// </summary>
3237 XmlnsMissing,
3238
3239 /// <summary>
3240 /// Displayed when the xmlns attribute on the document element is wrong.
3241 /// </summary>
3242 XmlnsValueWrong,
3243
3244 /// <summary>
3245 /// Displayed when inner text contains a deprecated $(loc.xxx) reference.
3246 /// </summary>
3247 DeprecatedLocalizationVariablePrefixInTextValue,
3248
3249 /// <summary>
3250 /// Displayed when an attribute value contains a deprecated $(loc.xxx) reference.
3251 /// </summary>
3252 DeprecatedLocalizationVariablePrefixInAttributeValue,
3253
3254 /// <summary>
3255 /// Assign an identifier to a File element when on Id attribute is specified.
3256 /// </summary>
3257 AssignAnonymousFileId,
3258
3259 /// <summary>
3260 /// SuppressSignatureVerification attribute is obsolete and corresponding functionality removed.
3261 /// </summary>
3262 SuppressSignatureVerificationObsolete,
3263
3264 /// <summary>
3265 /// WixCA Binary/@Id has been renamed to UtilCA.
3266 /// </summary>
3267 WixCABinaryIdRenamed,
3268
3269 /// <summary>
3270 /// QtExec custom actions have been renamed.
3271 /// </summary>
3272 QuietExecCustomActionsRenamed,
3273
3274 /// <summary>
3275 /// QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout.
3276 /// </summary>
3277 QtExecCmdTimeoutAmbiguous,
3278
3279 /// <summary>
3280 /// Directory/@ShortName may only be specified with Directory/@Name.
3281 /// </summary>
3282 AssignDirectoryNameFromShortName,
3283
3284 /// <summary>
3285 /// BootstrapperApplicationData attribute is deprecated and replaced with Unreal for MSI. Use BundleCustomData element for Bundles.
3286 /// </summary>
3287 BootstrapperApplicationDataDeprecated,
3288
3289 /// <summary>
3290 /// Inheritable is new and is now defaulted to 'yes' which is a change in behavior for all but children of CreateFolder.
3291 /// </summary>
3292 AssignPermissionExInheritable,
3293
3294 /// <summary>
3295 /// Column element's Category attribute is camel-case.
3296 /// </summary>
3297 ColumnCategoryCamelCase,
3298
3299 /// <summary>
3300 /// Column element's Modularize attribute is camel-case.
3301 /// </summary>
3302 ColumnModularizeCamelCase,
3303
3304 /// <summary>
3305 /// Inner text value should move to an attribute.
3306 /// </summary>
3307 InnerTextDeprecated,
3308
3309 /// <summary>
3310 /// Explicit auto-GUID unnecessary.
3311 /// </summary>
3312 AutoGuidUnnecessary,
3313
3314 /// <summary>
3315 /// The Feature Absent attribute renamed to AllowAbsent.
3316 /// </summary>
3317 FeatureAbsentAttributeReplaced,
3318
3319 /// <summary>
3320 /// The Feature AllowAdvertise attribute value deprecated.
3321 /// </summary>
3322 FeatureAllowAdvertiseValueDeprecated,
3323
3324 /// <summary>
3325 /// The Condition='1' attribute is unnecessary on Publish elements.
3326 /// </summary>
3327 PublishConditionOneUnnecessary,
3328
3329 /// <summary>
3330 /// DpiAwareness is new and is defaulted to 'perMonitorV2' which is a change in behavior.
3331 /// </summary>
3332 AssignBootstrapperApplicationDpiAwareness,
3333
3334 /// <summary>
3335 /// The string variable type was previously treated as formatted.
3336 /// </summary>
3337 AssignVariableTypeFormatted,
3338
3339 /// <summary>
3340 /// The CustomAction attributes have been renamed from BinaryKey and FileKey to BinaryRef and FileRef.
3341 /// </summary>
3342 CustomActionKeysAreNowRefs,
3343
3344 /// <summary>
3345 /// The Product and Package elements have been renamed and reorganized.
3346 /// </summary>
3347 ProductAndPackageRenamed,
3348
3349 /// <summary>
3350 /// The Module and Package elements have been renamed and reorganized.
3351 /// </summary>
3352 ModuleAndPackageRenamed,
3353
3354 /// <summary>
3355 /// A MediaTemplate with no attributes set is now provided by default.
3356 /// </summary>
3357 DefaultMediaTemplate,
3358
3359 /// <summary>
3360 /// util:RegistrySearch has breaking change when value is missing.
3361 /// </summary>
3362 UtilRegistryValueSearchBehaviorChange,
3363
3364 /// <summary>
3365 /// DisplayInternalUI can't be converted.
3366 /// </summary>
3367 DisplayInternalUiNotConvertable,
3368
3369 /// <summary>
3370 /// InstallerVersion has breaking change when omitted.
3371 /// </summary>
3372 InstallerVersionBehaviorChange,
3373
3374 /// <summary>
3375 /// Verb/@Target can't be converted.
3376 /// </summary>
3377 VerbTargetNotConvertable,
3378
3379 /// <summary>
3380 /// The bootstrapper application dll is now specified in its own element.
3381 /// </summary>
3382 BootstrapperApplicationDll,
3383
3384 /// <summary>
3385 /// The new bootstrapper application dll element is required.
3386 /// </summary>
3387 BootstrapperApplicationDllRequired,
3388
3389 /// <summary>
3390 /// bal:UseUILanguages is deprecated, 'true' is now the standard behavior.
3391 /// </summary>
3392 BalUseUILanguagesDeprecated,
3393
3394 /// <summary>
3395 /// The custom elements for built-in BAs are now required.
3396 /// </summary>
3397 BalBootstrapperApplicationRefToElement,
3398
3399 /// <summary>
3400 /// The ExePackage elements "XxxCommand" attributes have been renamed to "XxxArguments".
3401 /// </summary>
3402 RenameExePackageCommandToArguments,
3403
3404 /// <summary>
3405 /// The Win64 attribute has been renamed. Use the Bitness attribute instead.
3406 /// </summary>
3407 Win64AttributeRenamed,
3408
3409 /// <summary>
3410 /// Breaking change: The Win64 attribute's value '{0}' cannot be converted automatically to the new Bitness attribute.
3411 /// </summary>
3412 Win64AttributeRenameCannotBeAutomatic,
3413
3414 /// <summary>
3415 /// The Tag element has been renamed. Use the element 'SoftwareTag' name.
3416 /// </summary>
3417 TagElementRenamed,
3418
3419 /// <summary>
3420 /// The Dependency namespace has been incorporated into WiX v4 namespace.
3421 /// </summary>
3422 IntegratedDependencyNamespace,
3423
3424 /// <summary>
3425 /// Remove unused namespaces.
3426 /// </summary>
3427 RemoveUnusedNamespaces,
3428
3429 /// <summary>
3430 /// The Remote element has been renamed. Use the "XxxPackagePayload" element instead.
3431 /// </summary>
3432 RemotePayloadRenamed,
3433
3434 /// <summary>
3435 /// The XxxPackage/@Name attribute must be specified on the child XxxPackagePayload element when using a remote payload.
3436 /// </summary>
3437 NameAttributeMovedToRemotePayload,
3438
3439 /// <summary>
3440 /// The XxxPackage/@Compressed attribute should not be specified when using a remote payload.
3441 /// </summary>
3442 CompressedAttributeUnnecessaryForRemotePayload,
3443
3444 /// <summary>
3445 /// The XxxPackage/@DownloadUrl attribute must be specified on the child XxxPackagePayload element when using a remote payload.
3446 /// </summary>
3447 DownloadUrlAttributeMovedToRemotePayload,
3448
3449 /// <summary>
3450 /// The hash algorithm used for bundles changed from SHA1 to SHA512.
3451 /// </summary>
3452 BurnHashAlgorithmChanged,
3453
3454 /// <summary>
3455 /// CustomTable elements can't always be converted.
3456 /// </summary>
3457 CustomTableNotAlwaysConvertable,
3458
3459 /// <summary>
3460 /// CustomTable elements that don't contain the table definition are now CustomTableRef.
3461 /// </summary>
3462 CustomTableRef,
3463
3464 /// <summary>
3465 /// The RegistryKey element's Action attribute is obsolete.
3466 /// </summary>
3467 RegistryKeyActionObsolete,
3468
3469 /// <summary>
3470 /// The TagRef element has been renamed. Use the element 'SoftwareTagRef' name.
3471 /// </summary>
3472 TagRefElementRenamed,
3473
3474 /// <summary>
3475 /// The SoftwareTag element's Licensed attribute is obsolete.
3476 /// </summary>
3477 SoftwareTagLicensedObsolete,
3478
3479 /// <summary>
3480 /// The SoftwareTag element's Type attribute is obsolete.
3481 /// </summary>
3482 SoftwareTagTypeObsolete,
3483
3484 /// <summary>
3485 /// TARGETDIR directory should no longer be explicitly defined.
3486 /// </summary>
3487 TargetDirDeprecated,
3488
3489 /// <summary>
3490 /// Standard directories should no longer be defined using the Directory element.
3491 /// </summary>
3492 DefiningStandardDirectoryDeprecated,
3493
3494 /// <summary>
3495 /// Naked UI, custom action, and property references replaced with elements.
3496 /// </summary>
3497 ReferencesReplaced,
3498
3499 /// <summary>
3500 /// Cache attribute value updated.
3501 /// </summary>
3502 BundlePackageCacheAttributeValueObsolete,
3503
3504 /// <summary>
3505 /// The MsuPackage element contains obsolete '{0}' attribute. Windows no longer supports silently removing MSUs so the attribute is unnecessary. The attribute will be removed.
3506 /// </summary>
3507 MsuPackageKBObsolete,
3508
3509 /// <summary>
3510 /// The MsuPackage element contains obsolete '{0}' attribute. MSU packages are now always permanent because Windows no longer supports silently removing MSUs. The attribute will be removed.
3511 /// </summary>
3512 MsuPackagePermanentObsolete,
3513
3514 /// <summary>
3515 /// Namespace should be defined on the root. The '{0}' namespace was move to the root element.
3516 /// </summary>
3517 MoveNamespacesToRoot,
3518
3519 /// <summary>
3520 /// Custom action ids have changed in WiX v4 extensions. Because WiX v4 has platform-specific custom actions, the platform is applied as a suffix: _X86, _X64, _A64 (Arm64). When manually rescheduling custom actions, you must use the new custom action id, with platform suffix.
3521 /// </summary>
3522 CustomActionIdsIncludePlatformSuffix,
3523
3524 /// <summary>
3525 /// The {0} directory should no longer be explicitly referenced. Remove the DirectoryRef element with Id attribute '{0}'.
3526 /// </summary>
3527 StandardDirectoryRefDeprecated,
3528
3529 /// <summary>
3530 /// Referencing '{0}' directory directly is no longer supported. The DirectoryRef will not be removed but you will probably need to reference a more specific directory.
3531 /// </summary>
3532 EmptyStandardDirectoryRefNotConvertable,
3533
3534 /// <summary>
3535 /// The magic WixVariable 'WixMbaPrereqLicenseUrl' has been removed. Add bal:PrereqLicenseUrl="yes" to a prereq package instead.
3536 /// </summary>
3537 WixMbaPrereqLicenseUrlDeprecated,
3538
3539 /// <summary>
3540 /// The magic WixVariable 'WixMbaPrereqPackageId' has been removed. Add bal:PrereqPackage="yes" to the target package instead.
3541 /// </summary>
3542 WixMbaPrereqPackageIdDeprecated,
3543
3544 /// <summary>
3545 /// A reference to the TARGETDIR Directory was removed. This can cause unintended side effects. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages
3546 /// </summary>
3547 TargetDirRefRemoved,
3548
3549 /// <summary>
3550 /// The Certificate BinaryKey element has been renamed to BinaryRef.
3551 /// </summary>
3552 CertificateBinaryKeyIsNowBinaryRef,
3553
3554 /// <summary>
3555 /// The RelatedBundle element's Action attribute value must now be all lowercase. The Action='{0}' will be converted to '{1}'
3556 /// </summary>
3557 RelatedBundleActionLowercase,
3558
3559 /// <summary>
3560 /// The WixUIPrintEula custom action has been replaced with the MSI native MsiPrint control event in WiX v5 and no longer needs to be authored in a custom dialog set.
3561 /// </summary>
3562 WixUIPrintEulaCustomAction,
3563 }
3564 }
3565 }