aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-05-11 07:44:50 -0700
committerRob Mensching <rob@firegiant.com>2021-05-11 07:44:50 -0700
commit7ac5a6e39772aa0d31e538a6d2dcdcb087e2af88 (patch)
tree9826be2c8959b0054e40230d0206099aa1c5b1e3 /src
parentf0c6faa8f16569fced7512c5388ecad5a331d438 (diff)
parent612dc390ecd90bf4079ad7545f4a4832ce8f702d (diff)
downloadwix-7ac5a6e39772aa0d31e538a6d2dcdcb087e2af88.tar.gz
wix-7ac5a6e39772aa0d31e538a6d2dcdcb087e2af88.tar.bz2
wix-7ac5a6e39772aa0d31e538a6d2dcdcb087e2af88.zip
Merge Converters
Diffstat (limited to 'src')
-rw-r--r--src/wix/WixToolset.Converters.Symbolizer/ConvertSymbols.cs876
-rw-r--r--src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.csproj32
-rw-r--r--src/wix/WixToolset.Converters.sln85
-rw-r--r--src/wix/WixToolset.Converters.v3.ncrunchsolution6
-rw-r--r--src/wix/WixToolset.Converters/ConvertCommand.cs60
-rw-r--r--src/wix/WixToolset.Converters/ConverterExtensionCommandLine.cs46
-rw-r--r--src/wix/WixToolset.Converters/ConverterExtensionFactory.cs30
-rw-r--r--src/wix/WixToolset.Converters/FixupCommandBase.cs288
-rw-r--r--src/wix/WixToolset.Converters/FormatCommand.cs60
-rw-r--r--src/wix/WixToolset.Converters/WixConverter.cs2435
-rw-r--r--src/wix/WixToolset.Converters/WixToolset.Converters.csproj24
-rw-r--r--src/wix/WixToolset.Converters/WixToolsetCoreServiceProviderExtensions.cs25
-rw-r--r--src/wix/deps/wix.dllbin0 -> 1753088 bytes
-rw-r--r--src/wix/nuget.config2
-rw-r--r--src/wix/test/WixToolsetTest.Converters.Symbolizer/ConvertSymbolsFixture.cs606
-rw-r--r--src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wixoutbin0 -> 148559 bytes
-rw-r--r--src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wixproj47
-rw-r--r--src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wxs36
-rw-r--r--src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.csproj33
-rw-r--r--src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject9
-rw-r--r--src/wix/test/WixToolsetTest.Converters/BaseConverterFixture.cs33
-rw-r--r--src/wix/test/WixToolsetTest.Converters/BitnessFixture.cs187
-rw-r--r--src/wix/test/WixToolsetTest.Converters/BootstrapperApplicationFixture.cs418
-rw-r--r--src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs297
-rw-r--r--src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs449
-rw-r--r--src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs195
-rw-r--r--src/wix/test/WixToolsetTest.Converters/CustomActionFixture.cs88
-rw-r--r--src/wix/test/WixToolsetTest.Converters/CustomTableFixture.cs363
-rw-r--r--src/wix/test/WixToolsetTest.Converters/DependencyFixture.cs178
-rw-r--r--src/wix/test/WixToolsetTest.Converters/DirectoryFixture.cs92
-rw-r--r--src/wix/test/WixToolsetTest.Converters/ExePackageFixture.cs49
-rw-r--r--src/wix/test/WixToolsetTest.Converters/FeatureFixture.cs110
-rw-r--r--src/wix/test/WixToolsetTest.Converters/FirewallExtensionFixture.cs47
-rw-r--r--src/wix/test/WixToolsetTest.Converters/FormatFixture.cs117
-rw-r--r--src/wix/test/WixToolsetTest.Converters/IncludeFixture.cs68
-rw-r--r--src/wix/test/WixToolsetTest.Converters/Mocks/MockCoreServiceProvider.cs51
-rw-r--r--src/wix/test/WixToolsetTest.Converters/Mocks/MockMessaging.cs39
-rw-r--r--src/wix/test/WixToolsetTest.Converters/ProductPackageFixture.cs278
-rw-r--r--src/wix/test/WixToolsetTest.Converters/PropertyFixture.cs108
-rw-r--r--src/wix/test/WixToolsetTest.Converters/RegistryFixture.cs54
-rw-r--r--src/wix/test/WixToolsetTest.Converters/RemotePayloadFixture.cs104
-rw-r--r--src/wix/test/WixToolsetTest.Converters/SequenceFixture.cs49
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TagFixture.cs111
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/PackageSummaryInformation/TypicalV3.msibin0 -> 32768 bytes
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/PackageSummaryInformation/TypicalV3.wxs37
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/PermissionEx/v3.wxs26
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/PermissionEx/v4_expected.wxs26
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/ConvertedPreprocessor.wxs61
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/Preprocessor.wxs63
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/wixcop.settings.xml9
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/QtExec.bad/v3.wxs64
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/QtExec.bad/v4_expected.wxs63
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/QtExec/v3.wxs64
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/QtExec/v4_expected.wxs62
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/SingleFile/ConvertedSingleFile.wxs59
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/SingleFile/SingleFile.wxs61
-rw-r--r--src/wix/test/WixToolsetTest.Converters/UtilExtensionFixture.cs190
-rw-r--r--src/wix/test/WixToolsetTest.Converters/VariableFixture.cs86
-rw-r--r--src/wix/test/WixToolsetTest.Converters/Wix4ConversionFixture.cs54
-rw-r--r--src/wix/test/WixToolsetTest.Converters/WixToolsetTest.Converters.csproj31
60 files changed, 9140 insertions, 1 deletions
diff --git a/src/wix/WixToolset.Converters.Symbolizer/ConvertSymbols.cs b/src/wix/WixToolset.Converters.Symbolizer/ConvertSymbols.cs
new file mode 100644
index 00000000..6418b2a6
--- /dev/null
+++ b/src/wix/WixToolset.Converters.Symbolizer/ConvertSymbols.cs
@@ -0,0 +1,876 @@
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
3namespace WixToolset.Converters.Symbolizer
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller;
11 using Wix3 = Microsoft.Tools.WindowsInstallerXml;
12
13#pragma warning disable 1591 // TODO: add documentation
14 public static class ConvertSymbols
15 {
16 public static Intermediate ConvertFile(string path)
17 {
18 var output = Wix3.Output.Load(path, suppressVersionCheck: true, suppressSchema: true);
19 return ConvertOutput(output);
20 }
21
22 public static Intermediate ConvertOutput(Wix3.Output output)
23#pragma warning restore 1591
24 {
25 var section = new IntermediateSection(String.Empty, OutputType3ToSectionType4(output.Type));
26
27 var wixMediaByDiskId = IndexWixMediaTableByDiskId(output);
28 var componentsById = IndexById<Wix3.Row>(output, "Component");
29 var bindPathsById = IndexById<Wix3.Row>(output, "BindPath");
30 var fontsById = IndexById<Wix3.Row>(output, "Font");
31 var selfRegById = IndexById<Wix3.Row>(output, "SelfReg");
32 var wixDirectoryById = IndexById<Wix3.Row>(output, "WixDirectory");
33 var wixFileById = IndexById<Wix3.Row>(output, "WixFile");
34
35 foreach (Wix3.Table table in output.Tables)
36 {
37 foreach (Wix3.Row row in table.Rows)
38 {
39 var symbol = GenerateSymbolFromRow(row, wixMediaByDiskId, componentsById, fontsById, bindPathsById, selfRegById, wixFileById, wixDirectoryById);
40 if (symbol != null)
41 {
42 section.AddSymbol(symbol);
43 }
44 }
45 }
46
47 return new Intermediate(String.Empty, new[] { section }, localizationsByCulture: null);
48 }
49
50 private static Dictionary<int, Wix3.WixMediaRow> IndexWixMediaTableByDiskId(Wix3.Output output)
51 {
52 var wixMediaByDiskId = new Dictionary<int, Wix3.WixMediaRow>();
53 var wixMediaTable = output.Tables["WixMedia"];
54
55 if (wixMediaTable != null)
56 {
57 foreach (Wix3.WixMediaRow row in wixMediaTable.Rows)
58 {
59 wixMediaByDiskId.Add(FieldAsInt(row, 0), row);
60 }
61 }
62
63 return wixMediaByDiskId;
64 }
65
66 private static Dictionary<string, T> IndexById<T>(Wix3.Output output, string tableName) where T : Wix3.Row
67 {
68 var byId = new Dictionary<string, T>();
69 var table = output.Tables[tableName];
70
71 if (table != null)
72 {
73 foreach (T row in table.Rows)
74 {
75 byId.Add(FieldAsString(row, 0), row);
76 }
77 }
78
79 return byId;
80 }
81
82 private static IntermediateSymbol GenerateSymbolFromRow(Wix3.Row row, Dictionary<int, Wix3.WixMediaRow> wixMediaByDiskId, Dictionary<string, Wix3.Row> componentsById, Dictionary<string, Wix3.Row> fontsById, Dictionary<string, Wix3.Row> bindPathsById, Dictionary<string, Wix3.Row> selfRegById, Dictionary<string, Wix3.Row> wixFileById, Dictionary<string, Wix3.Row> wixDirectoryById)
83 {
84 var name = row.Table.Name;
85 switch (name)
86 {
87 case "_SummaryInformation":
88 return DefaultSymbolFromRow(typeof(SummaryInformationSymbol), row, columnZeroIsId: false);
89 case "ActionText":
90 return DefaultSymbolFromRow(typeof(ActionTextSymbol), row, columnZeroIsId: false);
91 case "AppId":
92 return DefaultSymbolFromRow(typeof(AppIdSymbol), row, columnZeroIsId: false);
93 case "AppSearch":
94 return DefaultSymbolFromRow(typeof(AppSearchSymbol), row, columnZeroIsId: false);
95 case "Billboard":
96 return DefaultSymbolFromRow(typeof(BillboardSymbol), row, columnZeroIsId: true);
97 case "Binary":
98 return DefaultSymbolFromRow(typeof(BinarySymbol), row, columnZeroIsId: true);
99 case "BindPath":
100 return null;
101 case "CCPSearch":
102 return DefaultSymbolFromRow(typeof(CCPSearchSymbol), row, columnZeroIsId: true);
103 case "Class":
104 return DefaultSymbolFromRow(typeof(ClassSymbol), row, columnZeroIsId: false);
105 case "CompLocator":
106 return DefaultSymbolFromRow(typeof(CompLocatorSymbol), row, columnZeroIsId: false);
107 case "Component":
108 {
109 var attributes = FieldAsNullableInt(row, 3);
110
111 var location = ComponentLocation.LocalOnly;
112 if ((attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly) == WindowsInstallerConstants.MsidbComponentAttributesSourceOnly)
113 {
114 location = ComponentLocation.SourceOnly;
115 }
116 else if ((attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional) == WindowsInstallerConstants.MsidbComponentAttributesOptional)
117 {
118 location = ComponentLocation.Either;
119 }
120
121 var keyPath = FieldAsString(row, 5);
122 var keyPathType = String.IsNullOrEmpty(keyPath) ? ComponentKeyPathType.Directory : ComponentKeyPathType.File;
123 if ((attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath) == WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)
124 {
125 keyPathType = ComponentKeyPathType.Registry;
126 }
127 else if ((attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource) == WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource)
128 {
129 keyPathType = ComponentKeyPathType.OdbcDataSource;
130 }
131
132 return new ComponentSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
133 {
134 ComponentId = FieldAsString(row, 1),
135 DirectoryRef = FieldAsString(row, 2),
136 Condition = FieldAsString(row, 4),
137 KeyPath = keyPath,
138 Location = location,
139 DisableRegistryReflection = (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection) == WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection,
140 NeverOverwrite = (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite) == WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite,
141 Permanent = (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent) == WindowsInstallerConstants.MsidbComponentAttributesPermanent,
142 SharedDllRefCount = (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount) == WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount,
143 Shared = (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared) == WindowsInstallerConstants.MsidbComponentAttributesShared,
144 Transitive = (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive) == WindowsInstallerConstants.MsidbComponentAttributesTransitive,
145 UninstallWhenSuperseded = (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence) == WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence,
146 Win64 = (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit) == WindowsInstallerConstants.MsidbComponentAttributes64bit,
147 KeyPathType = keyPathType,
148 };
149 }
150
151 case "Condition":
152 return DefaultSymbolFromRow(typeof(ConditionSymbol), row, columnZeroIsId: false);
153 case "CreateFolder":
154 return DefaultSymbolFromRow(typeof(CreateFolderSymbol), row, columnZeroIsId: false);
155 case "CustomAction":
156 {
157 var caType = FieldAsInt(row, 1);
158 var executionType = DetermineCustomActionExecutionType(caType);
159 var sourceType = DetermineCustomActionSourceType(caType);
160 var targetType = DetermineCustomActionTargetType(caType);
161
162 return new CustomActionSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
163 {
164 ExecutionType = executionType,
165 SourceType = sourceType,
166 Source = FieldAsString(row, 2),
167 TargetType = targetType,
168 Target = FieldAsString(row, 3),
169 Win64 = (caType & WindowsInstallerConstants.MsidbCustomActionType64BitScript) == WindowsInstallerConstants.MsidbCustomActionType64BitScript,
170 TSAware = (caType & WindowsInstallerConstants.MsidbCustomActionTypeTSAware) == WindowsInstallerConstants.MsidbCustomActionTypeTSAware,
171 Impersonate = (caType & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate) != WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate,
172 IgnoreResult = (caType & WindowsInstallerConstants.MsidbCustomActionTypeContinue) == WindowsInstallerConstants.MsidbCustomActionTypeContinue,
173 Hidden = (caType & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget) == WindowsInstallerConstants.MsidbCustomActionTypeHideTarget,
174 Async = (caType & WindowsInstallerConstants.MsidbCustomActionTypeAsync) == WindowsInstallerConstants.MsidbCustomActionTypeAsync,
175 };
176 }
177
178 case "Directory":
179 {
180 var id = FieldAsString(row, 0);
181 var splits = SplitDefaultDir(FieldAsString(row, 2));
182
183 var symbol = new DirectorySymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, id))
184 {
185 ParentDirectoryRef = FieldAsString(row, 1),
186 Name = splits[0],
187 ShortName = splits[1],
188 SourceName = splits[2],
189 SourceShortName = splits[3]
190 };
191
192 if (wixDirectoryById.TryGetValue(id, out var wixDirectoryRow))
193 {
194 symbol.ComponentGuidGenerationSeed = FieldAsString(wixDirectoryRow, 1);
195 }
196
197 return symbol;
198 }
199 case "DrLocator":
200 return DefaultSymbolFromRow(typeof(DrLocatorSymbol), row, columnZeroIsId: false);
201 case "DuplicateFile":
202 {
203 var splitName = FieldAsString(row, 3)?.Split('|');
204
205 var symbol = new DuplicateFileSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
206 {
207 ComponentRef = FieldAsString(row, 1),
208 FileRef = FieldAsString(row, 2),
209 DestinationName = splitName == null ? null : splitName.Length > 1 ? splitName[1] : splitName[0],
210 DestinationShortName = splitName == null ? null : splitName.Length > 1 ? splitName[0] : null,
211 DestinationFolder = FieldAsString(row, 4)
212 };
213
214 return symbol;
215 }
216 case "Error":
217 return DefaultSymbolFromRow(typeof(ErrorSymbol), row, columnZeroIsId: false);
218 case "Extension":
219 return DefaultSymbolFromRow(typeof(ExtensionSymbol), row, columnZeroIsId: false);
220 case "Feature":
221 {
222 var attributes = FieldAsInt(row, 7);
223 var installDefault = FeatureInstallDefault.Local;
224 if ((attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent) == WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)
225 {
226 installDefault = FeatureInstallDefault.FollowParent;
227 }
228 else if ((attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) == WindowsInstallerConstants.MsidbFeatureAttributesFavorSource)
229 {
230 installDefault = FeatureInstallDefault.Source;
231 }
232
233 return new FeatureSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
234 {
235 ParentFeatureRef = FieldAsString(row, 1),
236 Title = FieldAsString(row, 2),
237 Description = FieldAsString(row, 3),
238 Display = FieldAsInt(row, 4), // BUGBUGBUG: FieldAsNullableInt(row, 4),
239 Level = FieldAsInt(row, 5),
240 DirectoryRef = FieldAsString(row, 6),
241 DisallowAbsent = (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent) == WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent,
242 DisallowAdvertise = (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) == WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise,
243 InstallDefault = installDefault,
244 TypicalDefault = (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise) == WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise ? FeatureTypicalDefault.Advertise : FeatureTypicalDefault.Install,
245 };
246 }
247
248 case "FeatureComponents":
249 return DefaultSymbolFromRow(typeof(FeatureComponentsSymbol), row, columnZeroIsId: false);
250 case "File":
251 {
252 var attributes = FieldAsNullableInt(row, 6);
253
254 FileSymbolAttributes symbolAttributes = 0;
255 symbolAttributes |= (attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) == WindowsInstallerConstants.MsidbFileAttributesReadOnly ? FileSymbolAttributes.ReadOnly : 0;
256 symbolAttributes |= (attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) == WindowsInstallerConstants.MsidbFileAttributesHidden ? FileSymbolAttributes.Hidden : 0;
257 symbolAttributes |= (attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) == WindowsInstallerConstants.MsidbFileAttributesSystem ? FileSymbolAttributes.System : 0;
258 symbolAttributes |= (attributes & WindowsInstallerConstants.MsidbFileAttributesVital) == WindowsInstallerConstants.MsidbFileAttributesVital ? FileSymbolAttributes.Vital : 0;
259 symbolAttributes |= (attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) == WindowsInstallerConstants.MsidbFileAttributesChecksum ? FileSymbolAttributes.Checksum : 0;
260 symbolAttributes |= (attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed ? FileSymbolAttributes.Uncompressed : 0;
261 symbolAttributes |= (attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed ? FileSymbolAttributes.Compressed : 0;
262
263 var id = FieldAsString(row, 0);
264 var splitName = FieldAsString(row, 2).Split('|');
265
266 var symbol = new FileSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, id))
267 {
268 ComponentRef = FieldAsString(row, 1),
269 Name = splitName.Length > 1 ? splitName[1] : splitName[0],
270 ShortName = splitName.Length > 1 ? splitName[0] : null,
271 FileSize = FieldAsInt(row, 3),
272 Version = FieldAsString(row, 4),
273 Language = FieldAsString(row, 5),
274 Attributes = symbolAttributes
275 };
276
277 if (bindPathsById.TryGetValue(id, out var bindPathRow))
278 {
279 symbol.BindPath = FieldAsString(bindPathRow, 1) ?? String.Empty;
280 }
281
282 if (fontsById.TryGetValue(id, out var fontRow))
283 {
284 symbol.FontTitle = FieldAsString(fontRow, 1) ?? String.Empty;
285 }
286
287 if (selfRegById.TryGetValue(id, out var selfRegRow))
288 {
289 symbol.SelfRegCost = FieldAsNullableInt(selfRegRow, 1) ?? 0;
290 }
291
292 if (wixFileById.TryGetValue(id, out var wixFileRow))
293 {
294 symbol.DirectoryRef = FieldAsString(wixFileRow, 4);
295 symbol.DiskId = FieldAsNullableInt(wixFileRow, 5) ?? 0;
296 symbol.Source = new IntermediateFieldPathValue { Path = FieldAsString(wixFileRow, 6) };
297 symbol.PatchGroup = FieldAsInt(wixFileRow, 8);
298 symbol.PatchAttributes = (PatchAttributeType)FieldAsInt(wixFileRow, 10);
299 }
300
301 return symbol;
302 }
303 case "Font":
304 return null;
305 case "Icon":
306 return DefaultSymbolFromRow(typeof(IconSymbol), row, columnZeroIsId: true);
307 case "IniFile":
308 {
309 var splitName = FieldAsString(row, 1).Split('|');
310 var action = FieldAsInt(row, 6);
311
312 var symbol = new IniFileSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
313 {
314 FileName = splitName.Length > 1 ? splitName[1] : splitName[0],
315 ShortFileName = splitName.Length > 1 ? splitName[0] : null,
316 DirProperty = FieldAsString(row, 2),
317 Section = FieldAsString(row, 3),
318 Key = FieldAsString(row, 4),
319 Value = FieldAsString(row, 5),
320 Action = action == 3 ? IniFileActionType.AddTag : action == 1 ? IniFileActionType.CreateLine : IniFileActionType.AddLine,
321 ComponentRef = FieldAsString(row, 7),
322 };
323
324 return symbol;
325 }
326 case "IniLocator":
327 {
328 var splitName = FieldAsString(row, 1).Split('|');
329
330 var symbol = new IniLocatorSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
331 {
332 FileName = splitName.Length > 1 ? splitName[1] : splitName[0],
333 ShortFileName = splitName.Length > 1 ? splitName[0] : null,
334 Section = FieldAsString(row, 2),
335 Key = FieldAsString(row, 3),
336 Field = FieldAsInt(row, 4),
337 Type = FieldAsInt(row, 5),
338 };
339
340 return symbol;
341 }
342 case "LockPermissions":
343 return DefaultSymbolFromRow(typeof(LockPermissionsSymbol), row, columnZeroIsId: false);
344 case "Media":
345 {
346 var diskId = FieldAsInt(row, 0);
347 var symbol = new MediaSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, diskId))
348 {
349 DiskId = diskId,
350 LastSequence = FieldAsNullableInt(row, 1),
351 DiskPrompt = FieldAsString(row, 2),
352 Cabinet = FieldAsString(row, 3),
353 VolumeLabel = FieldAsString(row, 4),
354 Source = FieldAsString(row, 5)
355 };
356
357 if (wixMediaByDiskId.TryGetValue(diskId, out var wixMediaRow))
358 {
359 var compressionLevel = FieldAsString(wixMediaRow, 1);
360
361 symbol.CompressionLevel = String.IsNullOrEmpty(compressionLevel) ? null : (CompressionLevel?)Enum.Parse(typeof(CompressionLevel), compressionLevel, true);
362 symbol.Layout = wixMediaRow.Layout;
363 }
364
365 return symbol;
366 }
367 case "MIME":
368 return DefaultSymbolFromRow(typeof(MIMESymbol), row, columnZeroIsId: false);
369 case "ModuleIgnoreTable":
370 return DefaultSymbolFromRow(typeof(ModuleIgnoreTableSymbol), row, columnZeroIsId: true);
371 case "MoveFile":
372 return DefaultSymbolFromRow(typeof(MoveFileSymbol), row, columnZeroIsId: true);
373 case "MsiAssembly":
374 {
375 var componentId = FieldAsString(row, 0);
376 if (componentsById.TryGetValue(componentId, out var componentRow))
377 {
378 return new AssemblySymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(componentRow, 5)))
379 {
380 ComponentRef = componentId,
381 FeatureRef = FieldAsString(row, 1),
382 ManifestFileRef = FieldAsString(row, 2),
383 ApplicationFileRef = FieldAsString(row, 3),
384 Type = FieldAsNullableInt(row, 4) == 1 ? AssemblyType.Win32Assembly : AssemblyType.DotNetAssembly,
385 };
386 }
387
388 return null;
389 }
390 case "MsiLockPermissionsEx":
391 return DefaultSymbolFromRow(typeof(MsiLockPermissionsExSymbol), row, columnZeroIsId: true);
392 case "MsiShortcutProperty":
393 return DefaultSymbolFromRow(typeof(MsiShortcutPropertySymbol), row, columnZeroIsId: true);
394 case "ODBCDataSource":
395 return DefaultSymbolFromRow(typeof(ODBCDataSourceSymbol), row, columnZeroIsId: true);
396 case "ODBCDriver":
397 return DefaultSymbolFromRow(typeof(ODBCDriverSymbol), row, columnZeroIsId: true);
398 case "ODBCTranslator":
399 return DefaultSymbolFromRow(typeof(ODBCTranslatorSymbol), row, columnZeroIsId: true);
400 case "ProgId":
401 return DefaultSymbolFromRow(typeof(ProgIdSymbol), row, columnZeroIsId: false);
402 case "Property":
403 return DefaultSymbolFromRow(typeof(PropertySymbol), row, columnZeroIsId: true);
404 case "PublishComponent":
405 return DefaultSymbolFromRow(typeof(PublishComponentSymbol), row, columnZeroIsId: false);
406 case "Registry":
407 {
408 var value = FieldAsString(row, 4);
409 var valueType = RegistryValueType.String;
410 var valueAction = RegistryValueActionType.Write;
411
412 if (!String.IsNullOrEmpty(value))
413 {
414 if (value.StartsWith("#x", StringComparison.Ordinal))
415 {
416 valueType = RegistryValueType.Binary;
417 value = value.Substring(2);
418 }
419 else if (value.StartsWith("#%", StringComparison.Ordinal))
420 {
421 valueType = RegistryValueType.Expandable;
422 value = value.Substring(2);
423 }
424 else if (value.StartsWith("#", StringComparison.Ordinal))
425 {
426 valueType = RegistryValueType.Integer;
427 value = value.Substring(1);
428 }
429 else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal))
430 {
431 value = value.Substring(3, value.Length - 6);
432 valueType = RegistryValueType.MultiString;
433 valueAction = RegistryValueActionType.Write;
434 }
435 else if (value.StartsWith("[~]", StringComparison.Ordinal))
436 {
437 value = value.Substring(3);
438 valueType = RegistryValueType.MultiString;
439 valueAction = RegistryValueActionType.Append;
440 }
441 else if (value.EndsWith("[~]", StringComparison.Ordinal))
442 {
443 value = value.Substring(0, value.Length - 3);
444 valueType = RegistryValueType.MultiString;
445 valueAction = RegistryValueActionType.Prepend;
446 }
447 }
448
449 return new RegistrySymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
450 {
451 Root = (RegistryRootType)FieldAsInt(row, 1),
452 Key = FieldAsString(row, 2),
453 Name = FieldAsString(row, 3),
454 Value = value,
455 ComponentRef = FieldAsString(row, 5),
456 ValueAction = valueAction,
457 ValueType = valueType,
458 };
459 }
460 case "RegLocator":
461 {
462 var type = FieldAsInt(row, 4);
463
464 return new RegLocatorSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
465 {
466 Root = (RegistryRootType)FieldAsInt(row, 1),
467 Key = FieldAsString(row, 2),
468 Name = FieldAsString(row, 3),
469 Type = (RegLocatorType)(type & 0xF),
470 Win64 = (type & WindowsInstallerConstants.MsidbLocatorType64bit) == WindowsInstallerConstants.MsidbLocatorType64bit
471 };
472 }
473 case "RemoveFile":
474 {
475 var splitName = FieldAsString(row, 2).Split('|');
476 var installMode = FieldAsInt(row, 4);
477
478 return new RemoveFileSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
479 {
480 ComponentRef = FieldAsString(row, 1),
481 FileName = splitName.Length > 1 ? splitName[1] : splitName[0],
482 ShortFileName = splitName.Length > 1 ? splitName[0] : null,
483 DirPropertyRef = FieldAsString(row, 3),
484 OnInstall = (installMode & WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall) == WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall ? (bool?)true : null,
485 OnUninstall = (installMode & WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove) == WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove ? (bool?)true : null
486 };
487 }
488 case "RemoveRegistry":
489 {
490 return new RemoveRegistrySymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
491 {
492 Action = RemoveRegistryActionType.RemoveOnInstall,
493 Root = (RegistryRootType)FieldAsInt(row, 1),
494 Key = FieldAsString(row, 2),
495 Name = FieldAsString(row, 3),
496 ComponentRef = FieldAsString(row, 4),
497 };
498 }
499
500 case "ReserveCost":
501 return DefaultSymbolFromRow(typeof(ReserveCostSymbol), row, columnZeroIsId: true);
502 case "SelfReg":
503 return null;
504 case "ServiceControl":
505 {
506 var events = FieldAsInt(row, 2);
507 var wait = FieldAsNullableInt(row, 4);
508 return new ServiceControlSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
509 {
510 Name = FieldAsString(row, 1),
511 Arguments = FieldAsString(row, 3),
512 Wait = !wait.HasValue || wait.Value == 1,
513 ComponentRef = FieldAsString(row, 5),
514 InstallRemove = (events & WindowsInstallerConstants.MsidbServiceControlEventDelete) == WindowsInstallerConstants.MsidbServiceControlEventDelete,
515 UninstallRemove = (events & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete) == WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete,
516 InstallStart = (events & WindowsInstallerConstants.MsidbServiceControlEventStart) == WindowsInstallerConstants.MsidbServiceControlEventStart,
517 UninstallStart = (events & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart) == WindowsInstallerConstants.MsidbServiceControlEventUninstallStart,
518 InstallStop = (events & WindowsInstallerConstants.MsidbServiceControlEventStop) == WindowsInstallerConstants.MsidbServiceControlEventStop,
519 UninstallStop = (events & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop) == WindowsInstallerConstants.MsidbServiceControlEventUninstallStop,
520 };
521 }
522
523 case "ServiceInstall":
524 return DefaultSymbolFromRow(typeof(ServiceInstallSymbol), row, columnZeroIsId: true);
525 case "Shortcut":
526 {
527 var splitName = FieldAsString(row, 2).Split('|');
528
529 return new ShortcutSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
530 {
531 DirectoryRef = FieldAsString(row, 1),
532 Name = splitName.Length > 1 ? splitName[1] : splitName[0],
533 ShortName = splitName.Length > 1 ? splitName[0] : null,
534 ComponentRef = FieldAsString(row, 3),
535 Target = FieldAsString(row, 4),
536 Arguments = FieldAsString(row, 5),
537 Description = FieldAsString(row, 6),
538 Hotkey = FieldAsNullableInt(row, 7),
539 IconRef = FieldAsString(row, 8),
540 IconIndex = FieldAsNullableInt(row, 9),
541 Show = (ShortcutShowType?)FieldAsNullableInt(row, 10),
542 WorkingDirectory = FieldAsString(row, 11),
543 DisplayResourceDll = FieldAsString(row, 12),
544 DisplayResourceId = FieldAsNullableInt(row, 13),
545 DescriptionResourceDll = FieldAsString(row, 14),
546 DescriptionResourceId= FieldAsNullableInt(row, 15),
547 };
548 }
549 case "Signature":
550 return DefaultSymbolFromRow(typeof(SignatureSymbol), row, columnZeroIsId: true);
551 case "UIText":
552 return DefaultSymbolFromRow(typeof(UITextSymbol), row, columnZeroIsId: true);
553 case "Upgrade":
554 {
555 var attributes = FieldAsInt(row, 4);
556 return new UpgradeSymbol(SourceLineNumber4(row.SourceLineNumbers), new Identifier(AccessModifier.Global, FieldAsString(row, 0)))
557 {
558 UpgradeCode = FieldAsString(row, 0),
559 VersionMin = FieldAsString(row, 1),
560 VersionMax = FieldAsString(row, 2),
561 Language = FieldAsString(row, 3),
562 Remove = FieldAsString(row, 5),
563 ActionProperty = FieldAsString(row, 6),
564 MigrateFeatures = (attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures) == WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures,
565 OnlyDetect = (attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect,
566 IgnoreRemoveFailures = (attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure) == WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure,
567 VersionMinInclusive = (attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive,
568 VersionMaxInclusive = (attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive,
569 ExcludeLanguages = (attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive,
570 };
571 }
572 case "Verb":
573 return DefaultSymbolFromRow(typeof(VerbSymbol), row, columnZeroIsId: false);
574 case "WixAction":
575 {
576 var sequenceTable = FieldAsString(row, 0);
577 return new WixActionSymbol(SourceLineNumber4(row.SourceLineNumbers))
578 {
579 SequenceTable = (SequenceTable)Enum.Parse(typeof(SequenceTable), sequenceTable == "AdvtExecuteSequence" ? nameof(SequenceTable.AdvertiseExecuteSequence) : sequenceTable),
580 Action = FieldAsString(row, 1),
581 Condition = FieldAsString(row, 2),
582 Sequence = FieldAsNullableInt(row, 3),
583 Before = FieldAsString(row, 4),
584 After = FieldAsString(row, 5),
585 Overridable = FieldAsNullableInt(row, 6) != 0,
586 };
587 }
588 case "WixBootstrapperApplication":
589 return DefaultSymbolFromRow(typeof(WixBootstrapperApplicationSymbol), row, columnZeroIsId: true);
590 case "WixBundleContainer":
591 return DefaultSymbolFromRow(typeof(WixBundleContainerSymbol), row, columnZeroIsId: true);
592 case "WixBundleVariable":
593 return DefaultSymbolFromRow(typeof(WixBundleVariableSymbol), row, columnZeroIsId: true);
594 case "WixChainItem":
595 return DefaultSymbolFromRow(typeof(WixChainItemSymbol), row, columnZeroIsId: true);
596 case "WixCustomTable":
597 return DefaultSymbolFromRow(typeof(WixCustomTableSymbol), row, columnZeroIsId: true);
598 case "WixDirectory":
599 return null;
600 case "WixFile":
601 return null;
602 case "WixInstanceTransforms":
603 return DefaultSymbolFromRow(typeof(WixInstanceTransformsSymbol), row, columnZeroIsId: true);
604 case "WixMedia":
605 return null;
606 case "WixMerge":
607 return DefaultSymbolFromRow(typeof(WixMergeSymbol), row, columnZeroIsId: true);
608 case "WixPatchBaseline":
609 return DefaultSymbolFromRow(typeof(WixPatchBaselineSymbol), row, columnZeroIsId: true);
610 case "WixProperty":
611 {
612 var attributes = FieldAsInt(row, 1);
613 return new WixPropertySymbol(SourceLineNumber4(row.SourceLineNumbers))
614 {
615 PropertyRef = FieldAsString(row, 0),
616 Admin = (attributes & 0x1) == 0x1,
617 Hidden = (attributes & 0x2) == 0x2,
618 Secure = (attributes & 0x4) == 0x4,
619 };
620 }
621 case "WixSuppressModularization":
622 {
623 return new WixSuppressModularizationSymbol(SourceLineNumber4(row.SourceLineNumbers))
624 {
625 SuppressIdentifier = FieldAsString(row, 0)
626 };
627 }
628 case "WixUI":
629 return DefaultSymbolFromRow(typeof(WixUISymbol), row, columnZeroIsId: true);
630 case "WixVariable":
631 return DefaultSymbolFromRow(typeof(WixVariableSymbol), row, columnZeroIsId: true);
632 default:
633 return GenericSymbolFromCustomRow(row, columnZeroIsId: false);
634 }
635 }
636
637 private static CustomActionTargetType DetermineCustomActionTargetType(int type)
638 {
639 var targetType = default(CustomActionTargetType);
640
641 if ((type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) == WindowsInstallerConstants.MsidbCustomActionTypeVBScript)
642 {
643 targetType = CustomActionTargetType.VBScript;
644 }
645 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeJScript) == WindowsInstallerConstants.MsidbCustomActionTypeJScript)
646 {
647 targetType = CustomActionTargetType.JScript;
648 }
649 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeTextData) == WindowsInstallerConstants.MsidbCustomActionTypeTextData)
650 {
651 targetType = CustomActionTargetType.TextData;
652 }
653 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeExe) == WindowsInstallerConstants.MsidbCustomActionTypeExe)
654 {
655 targetType = CustomActionTargetType.Exe;
656 }
657 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeDll) == WindowsInstallerConstants.MsidbCustomActionTypeDll)
658 {
659 targetType = CustomActionTargetType.Dll;
660 }
661
662 return targetType;
663 }
664
665 private static CustomActionSourceType DetermineCustomActionSourceType(int type)
666 {
667 var sourceType = CustomActionSourceType.Binary;
668
669 if ((type & WindowsInstallerConstants.MsidbCustomActionTypeProperty) == WindowsInstallerConstants.MsidbCustomActionTypeProperty)
670 {
671 sourceType = CustomActionSourceType.Property;
672 }
673 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeDirectory) == WindowsInstallerConstants.MsidbCustomActionTypeDirectory)
674 {
675 sourceType = CustomActionSourceType.Directory;
676 }
677 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeSourceFile) == WindowsInstallerConstants.MsidbCustomActionTypeSourceFile)
678 {
679 sourceType = CustomActionSourceType.File;
680 }
681
682 return sourceType;
683 }
684
685 private static CustomActionExecutionType DetermineCustomActionExecutionType(int type)
686 {
687 var executionType = CustomActionExecutionType.Immediate;
688
689 if ((type & (WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit)) == (WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit))
690 {
691 executionType = CustomActionExecutionType.Commit;
692 }
693 else if ((type & (WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback)) == (WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback))
694 {
695 executionType = CustomActionExecutionType.Rollback;
696 }
697 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeInScript) == WindowsInstallerConstants.MsidbCustomActionTypeInScript)
698 {
699 executionType = CustomActionExecutionType.Deferred;
700 }
701 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat) == WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat)
702 {
703 executionType = CustomActionExecutionType.ClientRepeat;
704 }
705 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess) == WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess)
706 {
707 executionType = CustomActionExecutionType.OncePerProcess;
708 }
709 else if ((type & WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence) == WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence)
710 {
711 executionType = CustomActionExecutionType.FirstSequence;
712 }
713
714 return executionType;
715 }
716
717 private static IntermediateFieldType ColumnType3ToIntermediateFieldType4(Wix3.ColumnType columnType)
718 {
719 switch (columnType)
720 {
721 case Wix3.ColumnType.Number:
722 return IntermediateFieldType.Number;
723 case Wix3.ColumnType.Object:
724 return IntermediateFieldType.Path;
725 case Wix3.ColumnType.Unknown:
726 case Wix3.ColumnType.String:
727 case Wix3.ColumnType.Localized:
728 case Wix3.ColumnType.Preserved:
729 default:
730 return IntermediateFieldType.String;
731 }
732 }
733
734 private static IntermediateSymbol DefaultSymbolFromRow(Type symbolType, Wix3.Row row, bool columnZeroIsId)
735 {
736 var id = columnZeroIsId ? GetIdentifierForRow(row) : null;
737
738 var createSymbol = symbolType.GetConstructor(new[] { typeof(SourceLineNumber), typeof(Identifier) });
739 var symbol = (IntermediateSymbol)createSymbol.Invoke(new object[] { SourceLineNumber4(row.SourceLineNumbers), id });
740
741 SetSymbolFieldsFromRow(row, symbol, columnZeroIsId);
742
743 return symbol;
744 }
745
746 private static IntermediateSymbol GenericSymbolFromCustomRow(Wix3.Row row, bool columnZeroIsId)
747 {
748 var columnDefinitions = row.Table.Definition.Columns.Cast<Wix3.ColumnDefinition>();
749 var fieldDefinitions = columnDefinitions.Select(columnDefinition =>
750 new IntermediateFieldDefinition(columnDefinition.Name, ColumnType3ToIntermediateFieldType4(columnDefinition.Type))).ToArray();
751 var symbolDefinition = new IntermediateSymbolDefinition(row.Table.Name, fieldDefinitions, null);
752
753 var id = columnZeroIsId ? GetIdentifierForRow(row) : null;
754
755 var createSymbol = typeof(IntermediateSymbol).GetConstructor(new[] { typeof(IntermediateSymbolDefinition), typeof(SourceLineNumber), typeof(Identifier) });
756 var symbol = (IntermediateSymbol)createSymbol.Invoke(new object[] { symbolDefinition, SourceLineNumber4(row.SourceLineNumbers), id });
757
758 SetSymbolFieldsFromRow(row, symbol, columnZeroIsId);
759
760 return symbol;
761 }
762
763 private static void SetSymbolFieldsFromRow(Wix3.Row row, IntermediateSymbol symbol, bool columnZeroIsId)
764 {
765 var offset = 0;
766 if (columnZeroIsId)
767 {
768 offset = 1;
769 }
770
771 for (var i = offset; i < row.Fields.Length; ++i)
772 {
773 var column = row.Fields[i].Column;
774 switch (column.Type)
775 {
776 case Wix3.ColumnType.String:
777 case Wix3.ColumnType.Localized:
778 case Wix3.ColumnType.Object:
779 case Wix3.ColumnType.Preserved:
780 symbol.Set(i - offset, FieldAsString(row, i));
781 break;
782 case Wix3.ColumnType.Number:
783 int? nullableValue = FieldAsNullableInt(row, i);
784 // TODO: Consider whether null values should be coerced to their default value when
785 // a column is not nullable. For now, just pass through the null.
786 //int value = FieldAsInt(row, i);
787 //symbol.Set(i - offset, column.IsNullable ? nullableValue : value);
788 symbol.Set(i - offset, nullableValue);
789 break;
790 case Wix3.ColumnType.Unknown:
791 break;
792 }
793 }
794 }
795
796 private static Identifier GetIdentifierForRow(Wix3.Row row)
797 {
798 var column = row.Fields[0].Column;
799 switch (column.Type)
800 {
801 case Wix3.ColumnType.String:
802 case Wix3.ColumnType.Localized:
803 case Wix3.ColumnType.Object:
804 case Wix3.ColumnType.Preserved:
805 return new Identifier(AccessModifier.Global, (string)row.Fields[0].Data);
806 case Wix3.ColumnType.Number:
807 return new Identifier(AccessModifier.Global, FieldAsInt(row, 0));
808 default:
809 return null;
810 }
811 }
812
813 private static SectionType OutputType3ToSectionType4(Wix3.OutputType outputType)
814 {
815 switch (outputType)
816 {
817 case Wix3.OutputType.Bundle:
818 return SectionType.Bundle;
819 case Wix3.OutputType.Module:
820 return SectionType.Module;
821 case Wix3.OutputType.Patch:
822 return SectionType.Patch;
823 case Wix3.OutputType.PatchCreation:
824 return SectionType.PatchCreation;
825 case Wix3.OutputType.Product:
826 return SectionType.Product;
827 case Wix3.OutputType.Transform:
828 case Wix3.OutputType.Unknown:
829 default:
830 return SectionType.Unknown;
831 }
832 }
833
834 private static SourceLineNumber SourceLineNumber4(Wix3.SourceLineNumberCollection source)
835 {
836 return String.IsNullOrEmpty(source?.EncodedSourceLineNumbers) ? null : SourceLineNumber.CreateFromEncoded(source.EncodedSourceLineNumbers);
837 }
838
839 private static string FieldAsString(Wix3.Row row, int column)
840 {
841 return (string)row[column];
842 }
843
844 private static int FieldAsInt(Wix3.Row row, int column)
845 {
846 return Convert.ToInt32(row[column]);
847 }
848
849 private static int? FieldAsNullableInt(Wix3.Row row, int column)
850 {
851 var field = row.Fields[column];
852 if (field.Data == null)
853 {
854 return null;
855 }
856 else
857 {
858 return Convert.ToInt32(field.Data);
859 }
860 }
861
862 private static string[] SplitDefaultDir(string defaultDir)
863 {
864 var split1 = defaultDir.Split(':');
865 var targetSplit = split1.Length > 1 ? split1[1].Split('|') : split1[0].Split('|');
866 var sourceSplit = split1.Length > 1 ? split1[0].Split('|') : new[] { String.Empty };
867 return new[]
868 {
869 targetSplit.Length > 1 ? targetSplit[1] : targetSplit[0],
870 targetSplit.Length > 1 ? targetSplit[0] : null,
871 sourceSplit.Length > 1 ? sourceSplit[1] : sourceSplit[0],
872 sourceSplit.Length > 1 ? sourceSplit[0] : null
873 };
874 }
875 }
876}
diff --git a/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.csproj b/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.csproj
new file mode 100644
index 00000000..445c3500
--- /dev/null
+++ b/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.csproj
@@ -0,0 +1,32 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5
6 <PropertyGroup>
7 <TargetFrameworks>netstandard2.0</TargetFrameworks>
8 <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks>
9 <Description>Symbolizer</Description>
10 <Title>WiX Toolset Converters Tuplizer</Title>
11 <DebugType>embedded</DebugType>
12 <PublishRepositoryUrl>true</PublishRepositoryUrl>
13 <CreateDocumentationFile>true</CreateDocumentationFile>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <Reference Include="wix" HintPath="..\deps\wix.dll" />
18 <None Include="..\deps\wix.dll" Pack="true" PackagePath="lib\net461" />
19 <None Include="..\deps\wix.dll" Pack="true" PackagePath="lib\net472" />
20 <None Include="..\deps\wix.dll" Pack="true" PackagePath="lib\netstandard2.0" />
21 </ItemGroup>
22
23 <ItemGroup>
24 <PackageReference Include="WixToolset.Core" Version="4.0.*" />
25 <PackageReference Include="WixToolset.Core.WindowsInstaller" Version="4.0.*" />
26 </ItemGroup>
27
28 <ItemGroup>
29 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
30 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
31 </ItemGroup>
32</Project>
diff --git a/src/wix/WixToolset.Converters.sln b/src/wix/WixToolset.Converters.sln
new file mode 100644
index 00000000..ae8a3d92
--- /dev/null
+++ b/src/wix/WixToolset.Converters.sln
@@ -0,0 +1,85 @@
1
2Microsoft Visual Studio Solution File, Format Version 12.00
3# Visual Studio 15
4VisualStudioVersion = 15.0.26124.0
5MinimumVisualStudioVersion = 15.0.26124.0
6Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Converters", "WixToolset.Converters\WixToolset.Converters.csproj", "{6FAF6385-6598-4B89-972B-C31AFCA14538}"
7EndProject
8Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Converters.Symbolizer", "WixToolset.Converters.Symbolizer\WixToolset.Converters.Symbolizer.csproj", "{F051BCAF-698C-41D5-8427-164537CE5C5C}"
9EndProject
10Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{1B16A6C1-2B5D-4F9A-9DD5-FBC89B3CE31E}"
11EndProject
12Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Converters", "test\WixToolsetTest.Converters\WixToolsetTest.Converters.csproj", "{485C5038-97E1-4729-A54D-848CC69569FD}"
13EndProject
14Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Converters.Symbolizer", "test\WixToolsetTest.Converters.Symbolizer\WixToolsetTest.Converters.Symbolizer.csproj", "{9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}"
15EndProject
16Global
17 GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 Debug|Any CPU = Debug|Any CPU
19 Debug|x64 = Debug|x64
20 Debug|x86 = Debug|x86
21 Release|Any CPU = Release|Any CPU
22 Release|x64 = Release|x64
23 Release|x86 = Release|x86
24 EndGlobalSection
25 GlobalSection(ProjectConfigurationPlatforms) = postSolution
26 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Debug|x64.ActiveCfg = Debug|Any CPU
29 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Debug|x64.Build.0 = Debug|Any CPU
30 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Debug|x86.ActiveCfg = Debug|Any CPU
31 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Debug|x86.Build.0 = Debug|Any CPU
32 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Release|Any CPU.Build.0 = Release|Any CPU
34 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Release|x64.ActiveCfg = Release|Any CPU
35 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Release|x64.Build.0 = Release|Any CPU
36 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Release|x86.ActiveCfg = Release|Any CPU
37 {F051BCAF-698C-41D5-8427-164537CE5C5C}.Release|x86.Build.0 = Release|Any CPU
38 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Debug|x64.ActiveCfg = Debug|Any CPU
41 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Debug|x64.Build.0 = Debug|Any CPU
42 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Debug|x86.ActiveCfg = Debug|Any CPU
43 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Debug|x86.Build.0 = Debug|Any CPU
44 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Release|Any CPU.Build.0 = Release|Any CPU
46 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Release|x64.ActiveCfg = Release|Any CPU
47 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Release|x64.Build.0 = Release|Any CPU
48 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Release|x86.ActiveCfg = Release|Any CPU
49 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C}.Release|x86.Build.0 = Release|Any CPU
50 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Debug|x64.ActiveCfg = Debug|Any CPU
53 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Debug|x64.Build.0 = Debug|Any CPU
54 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Debug|x86.ActiveCfg = Debug|Any CPU
55 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Debug|x86.Build.0 = Debug|Any CPU
56 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Release|Any CPU.ActiveCfg = Release|Any CPU
57 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Release|Any CPU.Build.0 = Release|Any CPU
58 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Release|x64.ActiveCfg = Release|Any CPU
59 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Release|x64.Build.0 = Release|Any CPU
60 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Release|x86.ActiveCfg = Release|Any CPU
61 {6FAF6385-6598-4B89-972B-C31AFCA14538}.Release|x86.Build.0 = Release|Any CPU
62 {485C5038-97E1-4729-A54D-848CC69569FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
63 {485C5038-97E1-4729-A54D-848CC69569FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
64 {485C5038-97E1-4729-A54D-848CC69569FD}.Debug|x64.ActiveCfg = Debug|Any CPU
65 {485C5038-97E1-4729-A54D-848CC69569FD}.Debug|x64.Build.0 = Debug|Any CPU
66 {485C5038-97E1-4729-A54D-848CC69569FD}.Debug|x86.ActiveCfg = Debug|Any CPU
67 {485C5038-97E1-4729-A54D-848CC69569FD}.Debug|x86.Build.0 = Debug|Any CPU
68 {485C5038-97E1-4729-A54D-848CC69569FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
69 {485C5038-97E1-4729-A54D-848CC69569FD}.Release|Any CPU.Build.0 = Release|Any CPU
70 {485C5038-97E1-4729-A54D-848CC69569FD}.Release|x64.ActiveCfg = Release|Any CPU
71 {485C5038-97E1-4729-A54D-848CC69569FD}.Release|x64.Build.0 = Release|Any CPU
72 {485C5038-97E1-4729-A54D-848CC69569FD}.Release|x86.ActiveCfg = Release|Any CPU
73 {485C5038-97E1-4729-A54D-848CC69569FD}.Release|x86.Build.0 = Release|Any CPU
74 EndGlobalSection
75 GlobalSection(SolutionProperties) = preSolution
76 HideSolutionNode = FALSE
77 EndGlobalSection
78 GlobalSection(NestedProjects) = preSolution
79 {9DB36DB1-24A1-47A7-9E57-D48A2E33C13C} = {1B16A6C1-2B5D-4F9A-9DD5-FBC89B3CE31E}
80 {485C5038-97E1-4729-A54D-848CC69569FD} = {1B16A6C1-2B5D-4F9A-9DD5-FBC89B3CE31E}
81 EndGlobalSection
82 GlobalSection(ExtensibilityGlobals) = postSolution
83 SolutionGuid = {2E71F0EC-CF75-44DA-8353-7066EBD06564}
84 EndGlobalSection
85EndGlobal
diff --git a/src/wix/WixToolset.Converters.v3.ncrunchsolution b/src/wix/WixToolset.Converters.v3.ncrunchsolution
new file mode 100644
index 00000000..f774ab93
--- /dev/null
+++ b/src/wix/WixToolset.Converters.v3.ncrunchsolution
@@ -0,0 +1,6 @@
1<SolutionConfiguration>
2 <Settings>
3 <AllowParallelTestExecution>False</AllowParallelTestExecution>
4 <SolutionConfigured>True</SolutionConfigured>
5 </Settings>
6</SolutionConfiguration> \ No newline at end of file
diff --git a/src/wix/WixToolset.Converters/ConvertCommand.cs b/src/wix/WixToolset.Converters/ConvertCommand.cs
new file mode 100644
index 00000000..b6826f43
--- /dev/null
+++ b/src/wix/WixToolset.Converters/ConvertCommand.cs
@@ -0,0 +1,60 @@
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
3namespace WixToolset.Converters
4{
5 using System;
6 using System.Threading;
7 using System.Threading.Tasks;
8 using WixToolset.Extensibility.Services;
9
10 internal class ConvertCommand : FixupCommandBase
11 {
12 private const string SettingsFileDefault = "wix.convert.settings.xml";
13
14 public ConvertCommand(IServiceProvider serviceProvider)
15 {
16 this.Messaging = serviceProvider.GetService<IMessaging>();
17 }
18
19 private IMessaging Messaging { get; }
20
21 public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
22 {
23 if (this.ShowHelp)
24 {
25 DisplayHelp();
26 return Task.FromResult(-1);
27 }
28
29 this.ParseSettings(SettingsFileDefault);
30
31 var converter = new WixConverter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors);
32
33 var errors = base.Inspect(Inspector, cancellationToken);
34
35 return Task.FromResult(errors);
36
37 int Inspector(string file, bool fix)
38 {
39 return converter.ConvertFile(file, fix);
40 }
41 }
42
43 private static void DisplayHelp()
44 {
45 Console.WriteLine();
46 Console.WriteLine("Usage: wix convert [options] sourceFile [sourceFile ...]");
47 Console.WriteLine();
48 Console.WriteLine("Options:");
49 Console.WriteLine(" -h|--help Show command line help.");
50 Console.WriteLine(" --nologo Suppress displaying the logo information.");
51 Console.WriteLine(" -n|--dry-run Only display errors, do not update files.");
52 Console.WriteLine(" -r|--recurse Search for matching files in current dir and subdirs.");
53 Console.WriteLine(" -set1<file> Primary settings file.");
54 Console.WriteLine(" -set2<file> Secondary settings file (overrides primary).");
55 Console.WriteLine(" -indent:<n> Indentation multiple (overrides default of 4).");
56 Console.WriteLine();
57 Console.WriteLine(" sourceFile may use wildcards like *.wxs");
58 }
59 }
60}
diff --git a/src/wix/WixToolset.Converters/ConverterExtensionCommandLine.cs b/src/wix/WixToolset.Converters/ConverterExtensionCommandLine.cs
new file mode 100644
index 00000000..06d3658c
--- /dev/null
+++ b/src/wix/WixToolset.Converters/ConverterExtensionCommandLine.cs
@@ -0,0 +1,46 @@
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
3namespace WixToolset.Converters
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
11 /// <summary>
12 /// Parses the "convert" command-line command. See <c>ConvertCommand</c> for
13 /// the bulk of the command-line processing.
14 /// </summary>
15 internal class ConverterExtensionCommandLine : BaseExtensionCommandLine
16 {
17 public ConverterExtensionCommandLine(IServiceProvider serviceProvider)
18 {
19 this.ServiceProvider = serviceProvider;
20 }
21
22 private IServiceProvider ServiceProvider { get; }
23
24 public override IReadOnlyCollection<ExtensionCommandLineSwitch> CommandLineSwitches => new ExtensionCommandLineSwitch[]
25 {
26 new ExtensionCommandLineSwitch { Switch = "convert", Description = "Convert v3 source code to v4 source code." },
27 new ExtensionCommandLineSwitch { Switch = "format", Description = "Ensures consistent formatting of source code." },
28 };
29
30 public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command)
31 {
32 command = null;
33
34 if ("convert".Equals(argument, StringComparison.OrdinalIgnoreCase))
35 {
36 command = new ConvertCommand(this.ServiceProvider);
37 }
38 else if ("format".Equals(argument, StringComparison.OrdinalIgnoreCase))
39 {
40 command = new FormatCommand(this.ServiceProvider);
41 }
42
43 return command != null;
44 }
45 }
46}
diff --git a/src/wix/WixToolset.Converters/ConverterExtensionFactory.cs b/src/wix/WixToolset.Converters/ConverterExtensionFactory.cs
new file mode 100644
index 00000000..d4f480aa
--- /dev/null
+++ b/src/wix/WixToolset.Converters/ConverterExtensionFactory.cs
@@ -0,0 +1,30 @@
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
3namespace WixToolset.Converters
4{
5 using System;
6 using WixToolset.Extensibility;
7 using WixToolset.Extensibility.Services;
8
9 internal class ConverterExtensionFactory : IExtensionFactory
10 {
11 public ConverterExtensionFactory(IServiceProvider serviceProvider)
12 {
13 this.ServiceProvider = serviceProvider;
14 }
15
16 private IServiceProvider ServiceProvider { get; }
17
18 public bool TryCreateExtension(Type extensionType, out object extension)
19 {
20 extension = null;
21
22 if (extensionType == typeof(IExtensionCommandLine))
23 {
24 extension = new ConverterExtensionCommandLine(this.ServiceProvider);
25 }
26
27 return extension != null;
28 }
29 }
30}
diff --git a/src/wix/WixToolset.Converters/FixupCommandBase.cs b/src/wix/WixToolset.Converters/FixupCommandBase.cs
new file mode 100644
index 00000000..21282d07
--- /dev/null
+++ b/src/wix/WixToolset.Converters/FixupCommandBase.cs
@@ -0,0 +1,288 @@
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
3namespace WixToolset.Converters
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using System.Xml;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal abstract class FixupCommandBase : ICommandLineCommand
15 {
16 protected FixupCommandBase()
17 {
18 this.IndentationAmount = 4; // default indentation amount
19 this.ErrorsAsWarnings = new HashSet<string>();
20 this.ExemptFiles = new HashSet<string>();
21 this.IgnoreErrors = new HashSet<string>();
22 this.SearchPatternResults = new HashSet<string>();
23 this.SearchPatterns = new List<string>();
24 }
25
26 public bool ShowLogo { get; private set; }
27
28 public bool StopParsing { get; private set; }
29
30 protected bool ShowHelp { get; set; }
31
32 protected CustomTableTarget CustomTableSetting { get; set; }
33
34 protected bool DryRun { get; set; }
35
36 protected HashSet<string> ErrorsAsWarnings { get; }
37
38 protected HashSet<string> IgnoreErrors { get; }
39
40 protected HashSet<string> ExemptFiles { get; }
41
42 protected int IndentationAmount { get; set; }
43
44 protected bool Recurse { get; set; }
45
46 private HashSet<string> SearchPatternResults { get; }
47
48 private List<string> SearchPatterns { get; }
49
50 private string SettingsFile1 { get; set; }
51
52 private string SettingsFile2 { get; set; }
53
54 public bool TryParseArgument(ICommandLineParser parser, string argument)
55 {
56 if (!parser.IsSwitch(argument))
57 {
58 this.SearchPatterns.Add(argument);
59 return true;
60 }
61
62 var parameter = argument.Substring(1);
63 switch (parameter.ToLowerInvariant())
64 {
65 case "?":
66 case "h":
67 case "-help":
68 this.ShowHelp = true;
69 this.ShowLogo = true;
70 this.StopParsing = true;
71 return true;
72
73 case "-custom-table":
74 var customTableSetting = parser.GetNextArgumentOrError(argument);
75 switch (customTableSetting)
76 {
77 case "bundle":
78 this.CustomTableSetting = CustomTableTarget.Bundle;
79 break;
80 case "msi":
81 this.CustomTableSetting = CustomTableTarget.Msi;
82 break;
83 default:
84 parser.ReportErrorArgument(argument);
85 break;
86 }
87 return true;
88
89 case "n":
90 case "-dry-run":
91 this.DryRun = true;
92 return true;
93
94 case "nologo":
95 case "-nologo":
96 this.ShowLogo = false;
97 return true;
98
99 case "s":
100 case "r":
101 case "-recurse":
102 case "-recursive":
103 this.Recurse = true;
104 return true;
105
106 default: // other parameters
107 if (parameter.StartsWith("set1", StringComparison.Ordinal))
108 {
109 this.SettingsFile1 = parameter.Substring(4);
110 return true;
111 }
112 else if (parameter.StartsWith("set2", StringComparison.Ordinal))
113 {
114 this.SettingsFile2 = parameter.Substring(4);
115 return true;
116 }
117 else if (parameter.StartsWith("indent:", StringComparison.Ordinal))
118 {
119 try
120 {
121 this.IndentationAmount = Convert.ToInt32(parameter.Substring(7));
122 }
123 catch
124 {
125 parser.ReportErrorArgument(parameter); // $"Invalid numeric argument: {parameter}";
126 }
127 return true;
128 }
129
130 return false;
131 }
132 }
133
134 public abstract Task<int> ExecuteAsync(CancellationToken cancellationToken);
135
136 protected void ParseSettings(string defaultSettingsFile)
137 {
138 // parse the settings if any were specified
139 if (null != this.SettingsFile1 || null != this.SettingsFile2)
140 {
141 this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2);
142 }
143 else
144 {
145 if (File.Exists(defaultSettingsFile))
146 {
147 this.ParseSettingsFiles(defaultSettingsFile, null);
148 }
149 }
150 }
151
152 protected int Inspect(Func<string, bool, int> inspector, CancellationToken cancellationToken)
153 {
154 var errors = this.InspectSubDirectories(inspector, Path.GetFullPath("."), cancellationToken);
155
156 foreach (var searchPattern in this.SearchPatterns)
157 {
158 if (!this.SearchPatternResults.Contains(searchPattern))
159 {
160 Console.Error.WriteLine("Could not find file \"{0}\"", searchPattern);
161 errors++;
162 }
163 }
164
165 return errors;
166 }
167
168 /// <summary>
169 /// Inspect sub-directories.
170 /// </summary>
171 /// <param name="inspector"></param>
172 /// <param name="directory">The directory whose sub-directories will be inspected.</param>
173 /// <param name="cancellationToken"></param>
174 /// <returns>The number of errors that were found.</returns>
175 private int InspectSubDirectories(Func<string, bool, int> inspector, string directory, CancellationToken cancellationToken)
176 {
177 var errors = 0;
178
179 foreach (var searchPattern in this.SearchPatterns)
180 {
181 foreach (var sourceFilePath in GetFiles(directory, searchPattern))
182 {
183 cancellationToken.ThrowIfCancellationRequested();
184
185 var file = new FileInfo(sourceFilePath);
186
187 if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant()))
188 {
189 this.SearchPatternResults.Add(searchPattern);
190 errors += inspector(file.FullName, !this.DryRun);
191 }
192 }
193 }
194
195 if (this.Recurse)
196 {
197 foreach (var childDirectoryPath in Directory.GetDirectories(directory))
198 {
199 errors += this.InspectSubDirectories(inspector, childDirectoryPath, cancellationToken);
200 }
201 }
202
203 return errors;
204 }
205
206 /// <summary>
207 /// Parse the primary and secondary settings files.
208 /// </summary>
209 /// <param name="localSettingsFile1">The primary settings file.</param>
210 /// <param name="localSettingsFile2">The secondary settings file.</param>
211 private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2)
212 {
213 if (null == localSettingsFile1 && null != localSettingsFile2)
214 {
215 throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", nameof(localSettingsFile2));
216 }
217
218 var settingsFile = localSettingsFile1;
219 while (null != settingsFile)
220 {
221 var doc = new XmlDocument();
222 doc.Load(settingsFile);
223
224 // get the types of tests that will have their errors displayed as warnings
225 var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test");
226 foreach (XmlElement test in testsIgnoredElements)
227 {
228 var key = test.GetAttribute("Id");
229 this.IgnoreErrors.Add(key);
230 }
231
232 // get the types of tests that will have their errors displayed as warnings
233 var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test");
234 foreach (XmlElement test in testsAsWarningsElements)
235 {
236 var key = test.GetAttribute("Id");
237 this.ErrorsAsWarnings.Add(key);
238 }
239
240 // get the exempt files
241 var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File");
242 foreach (XmlElement file in localExemptFiles)
243 {
244 var key = file.GetAttribute("Name").ToUpperInvariant();
245 this.ExemptFiles.Add(key);
246 }
247
248 settingsFile = localSettingsFile2;
249 localSettingsFile2 = null;
250 }
251 }
252
253 /// <summary>
254 /// Get the files that match a search path pattern.
255 /// </summary>
256 /// <param name="baseDir">The base directory at which to begin the search.</param>
257 /// <param name="searchPath">The search path pattern.</param>
258 /// <returns>The files matching the pattern.</returns>
259 private static string[] GetFiles(string baseDir, string searchPath)
260 {
261 // convert alternate directory separators to the standard one
262 var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
263 var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
264 string[] files = null;
265
266 try
267 {
268 if (0 > lastSeparator)
269 {
270 files = Directory.GetFiles(baseDir, filePath);
271 }
272 else // found directory separator
273 {
274 var searchPattern = filePath.Substring(lastSeparator + 1);
275
276 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern);
277 }
278 }
279 catch (DirectoryNotFoundException)
280 {
281 // don't let this function throw the DirectoryNotFoundException. (this exception
282 // occurs for non-existant directories and invalid characters in the searchPattern)
283 }
284
285 return files;
286 }
287 }
288}
diff --git a/src/wix/WixToolset.Converters/FormatCommand.cs b/src/wix/WixToolset.Converters/FormatCommand.cs
new file mode 100644
index 00000000..0861fc51
--- /dev/null
+++ b/src/wix/WixToolset.Converters/FormatCommand.cs
@@ -0,0 +1,60 @@
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
3namespace WixToolset.Converters
4{
5 using System;
6 using System.Threading;
7 using System.Threading.Tasks;
8 using WixToolset.Extensibility.Services;
9
10 internal class FormatCommand : FixupCommandBase
11 {
12 private const string SettingsFileDefault = "wix.format.settings.xml";
13
14 public FormatCommand(IServiceProvider serviceProvider)
15 {
16 this.Messaging = serviceProvider.GetService<IMessaging>();
17 }
18
19 private IMessaging Messaging { get; }
20
21 public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
22 {
23 if (this.ShowHelp)
24 {
25 DisplayHelp();
26 return Task.FromResult(-1);
27 }
28
29 var converter = new WixConverter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors);
30
31 this.ParseSettings(SettingsFileDefault);
32
33 var errors = base.Inspect(Inspector, cancellationToken);
34
35 return Task.FromResult(errors);
36
37 int Inspector(string file, bool fix)
38 {
39 return converter.FormatFile(file, fix);
40 }
41 }
42
43 private static void DisplayHelp()
44 {
45 Console.WriteLine();
46 Console.WriteLine("Usage: wix format [options] sourceFile [sourceFile ...]");
47 Console.WriteLine();
48 Console.WriteLine("Options:");
49 Console.WriteLine(" -h|--help Show command line help.");
50 Console.WriteLine(" --nologo Suppress displaying the logo information.");
51 Console.WriteLine(" -n|--dry-run Only display errors, do not update files.");
52 Console.WriteLine(" -r|--recurse Search for matching files in current dir and subdirs.");
53 Console.WriteLine(" -set1<file> Primary settings file.");
54 Console.WriteLine(" -set2<file> Secondary settings file (overrides primary).");
55 Console.WriteLine(" -indent:<n> Indentation multiple (overrides default of 4).");
56 Console.WriteLine();
57 Console.WriteLine(" sourceFile may use wildcards like *.wxs");
58 }
59 }
60}
diff --git a/src/wix/WixToolset.Converters/WixConverter.cs b/src/wix/WixToolset.Converters/WixConverter.cs
new file mode 100644
index 00000000..e42d0605
--- /dev/null
+++ b/src/wix/WixToolset.Converters/WixConverter.cs
@@ -0,0 +1,2435 @@
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
3namespace 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 WixToolset.Data;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Extensibility.Services;
17
18 /// <summary>
19 /// How to convert CustomTable elements.
20 /// </summary>
21 public enum CustomTableTarget
22 {
23 /// <summary>
24 /// Ambiguous elements will be left alone.
25 /// </summary>
26 Unknown,
27
28 /// <summary>
29 /// Use CustomTable, CustomTableRef, and Unreal.
30 /// </summary>
31 Msi,
32
33 /// <summary>
34 /// Use BundleCustomData and BundleCustomDataRef.
35 /// </summary>
36 Bundle,
37 }
38
39 /// <summary>
40 /// WiX source code converter.
41 /// </summary>
42 public sealed class WixConverter
43 {
44 private enum ConvertOperation
45 {
46 Convert,
47 Format,
48 }
49
50 private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
51 private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
52
53 private const char XDocumentNewLine = '\n'; // XDocument normalizes "\r\n" to just "\n".
54 private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
55 private static readonly XNamespace Wix3Namespace = "http://schemas.microsoft.com/wix/2006/wi";
56 private static readonly XNamespace WixBalNamespace = "http://wixtoolset.org/schemas/v4/wxs/bal";
57 private static readonly XNamespace WixDependencyNamespace = "http://wixtoolset.org/schemas/v4/wxs/dependency";
58 private static readonly XNamespace WixUtilNamespace = "http://wixtoolset.org/schemas/v4/wxs/util";
59 private static readonly XNamespace WixFirewallNamespace = "http://wixtoolset.org/schemas/v4/wxs/firewall";
60
61 private static readonly XName AdminExecuteSequenceElementName = WixNamespace + "AdminExecuteSequence";
62 private static readonly XName AdminUISequenceSequenceElementName = WixNamespace + "AdminUISequence";
63 private static readonly XName AdvertiseExecuteSequenceElementName = WixNamespace + "AdvertiseExecuteSequence";
64 private static readonly XName InstallExecuteSequenceElementName = WixNamespace + "InstallExecuteSequence";
65 private static readonly XName InstallUISequenceSequenceElementName = WixNamespace + "InstallUISequence";
66 private static readonly XName BootstrapperApplicationElementName = WixNamespace + "BootstrapperApplication";
67 private static readonly XName BootstrapperApplicationDllElementName = WixNamespace + "BootstrapperApplicationDll";
68 private static readonly XName BootstrapperApplicationRefElementName = WixNamespace + "BootstrapperApplicationRef";
69 private static readonly XName ApprovedExeForElevationElementName = WixNamespace + "ApprovedExeForElevation";
70 private static readonly XName BundleAttributeElementName = WixNamespace + "BundleAttribute";
71 private static readonly XName BundleAttributeDefinitionElementName = WixNamespace + "BundleAttributeDefinition";
72 private static readonly XName BundleCustomDataElementName = WixNamespace + "BundleCustomData";
73 private static readonly XName BundleCustomDataRefElementName = WixNamespace + "BundleCustomDataRef";
74 private static readonly XName BundleElementElementName = WixNamespace + "BundleElement";
75 private static readonly XName CustomTableElementName = WixNamespace + "CustomTable";
76 private static readonly XName CustomTableRefElementName = WixNamespace + "CustomTableRef";
77 private static readonly XName CatalogElementName = WixNamespace + "Catalog";
78 private static readonly XName ColumnElementName = WixNamespace + "Column";
79 private static readonly XName ComponentElementName = WixNamespace + "Component";
80 private static readonly XName ControlElementName = WixNamespace + "Control";
81 private static readonly XName ConditionElementName = WixNamespace + "Condition";
82 private static readonly XName CreateFolderElementName = WixNamespace + "CreateFolder";
83 private static readonly XName DataElementName = WixNamespace + "Data";
84 private static readonly XName OldProvidesElementName = WixDependencyNamespace + "Provides";
85 private static readonly XName OldRequiresElementName = WixDependencyNamespace + "Requires";
86 private static readonly XName OldRequiresRefElementName = WixDependencyNamespace + "RequiresRef";
87 private static readonly XName DirectoryElementName = WixNamespace + "Directory";
88 private static readonly XName EmbeddedChainerElementName = WixNamespace + "EmbeddedChainer";
89 private static readonly XName ErrorElementName = WixNamespace + "Error";
90 private static readonly XName FeatureElementName = WixNamespace + "Feature";
91 private static readonly XName FileElementName = WixNamespace + "File";
92 private static readonly XName FragmentElementName = WixNamespace + "Fragment";
93 private static readonly XName FirewallRemoteAddressElementName = WixFirewallNamespace + "RemoteAddress";
94 private static readonly XName LaunchElementName = WixNamespace + "Launch";
95 private static readonly XName LevelElementName = WixNamespace + "Level";
96 private static readonly XName ExePackageElementName = WixNamespace + "ExePackage";
97 private static readonly XName ExePackagePayloadElementName = WixNamespace + "ExePackagePayload";
98 private static readonly XName ModuleElementName = WixNamespace + "Module";
99 private static readonly XName MsiPackageElementName = WixNamespace + "MsiPackage";
100 private static readonly XName MspPackageElementName = WixNamespace + "MspPackage";
101 private static readonly XName MsuPackageElementName = WixNamespace + "MsuPackage";
102 private static readonly XName MsuPackagePayloadElementName = WixNamespace + "MsuPackagePayload";
103 private static readonly XName PackageElementName = WixNamespace + "Package";
104 private static readonly XName PayloadElementName = WixNamespace + "Payload";
105 private static readonly XName PermissionExElementName = WixNamespace + "PermissionEx";
106 private static readonly XName ProductElementName = WixNamespace + "Product";
107 private static readonly XName ProgressTextElementName = WixNamespace + "ProgressText";
108 private static readonly XName PropertyRefElementName = WixNamespace + "PropertyRef";
109 private static readonly XName PublishElementName = WixNamespace + "Publish";
110 private static readonly XName ProvidesElementName = WixNamespace + "Provides";
111 private static readonly XName RequiresElementName = WixNamespace + "Requires";
112 private static readonly XName RequiresRefElementName = WixNamespace + "RequiresRef";
113 private static readonly XName MultiStringValueElementName = WixNamespace + "MultiStringValue";
114 private static readonly XName RemotePayloadElementName = WixNamespace + "RemotePayload";
115 private static readonly XName RegistryKeyElementName = WixNamespace + "RegistryKey";
116 private static readonly XName RegistrySearchElementName = WixNamespace + "RegistrySearch";
117 private static readonly XName RequiredPrivilegeElementName = WixNamespace + "RequiredPrivilege";
118 private static readonly XName RowElementName = WixNamespace + "Row";
119 private static readonly XName ServiceArgumentElementName = WixNamespace + "ServiceArgument";
120 private static readonly XName SetDirectoryElementName = WixNamespace + "SetDirectory";
121 private static readonly XName SetPropertyElementName = WixNamespace + "SetProperty";
122 private static readonly XName ShortcutPropertyElementName = WixNamespace + "ShortcutProperty";
123 private static readonly XName SoftwareTagElementName = WixNamespace + "SoftwareTag";
124 private static readonly XName SoftwareTagRefElementName = WixNamespace + "SoftwareTagRef";
125 private static readonly XName StandardDirectoryElementName = WixNamespace + "StandardDirectory";
126 private static readonly XName TagElementName = XNamespace.None + "Tag";
127 private static readonly XName TagRefElementName = XNamespace.None + "TagRef";
128 private static readonly XName TextElementName = WixNamespace + "Text";
129 private static readonly XName UITextElementName = WixNamespace + "UIText";
130 private static readonly XName VariableElementName = WixNamespace + "Variable";
131 private static readonly XName VerbElementName = WixNamespace + "Verb";
132 private static readonly XName BalUseUILanguagesName = WixBalNamespace + "UseUILanguages";
133 private static readonly XName BalStandardBootstrapperApplicationName = WixBalNamespace + "WixStandardBootstrapperApplication";
134 private static readonly XName BalManagedBootstrapperApplicationHostName = WixBalNamespace + "WixManagedBootstrapperApplicationHost";
135 private static readonly XName BalOldDotNetCoreBootstrapperApplicationName = WixBalNamespace + "WixDotNetCoreBootstrapperApplication";
136 private static readonly XName BalNewDotNetCoreBootstrapperApplicationName = WixBalNamespace + "WixDotNetCoreBootstrapperApplicationHost";
137 private static readonly XName UtilCloseApplicationElementName = WixUtilNamespace + "CloseApplication";
138 private static readonly XName UtilPermissionExElementName = WixUtilNamespace + "PermissionEx";
139 private static readonly XName UtilRegistrySearchName = WixUtilNamespace + "RegistrySearch";
140 private static readonly XName UtilXmlConfigElementName = WixUtilNamespace + "XmlConfig";
141 private static readonly XName CustomActionElementName = WixNamespace + "CustomAction";
142 private static readonly XName CustomActionRefElementName = WixNamespace + "CustomActionRef";
143 private static readonly XName PropertyElementName = WixNamespace + "Property";
144 private static readonly XName Wix4ElementName = WixNamespace + "Wix";
145 private static readonly XName Wix3ElementName = Wix3Namespace + "Wix";
146 private static readonly XName WixElementWithoutNamespaceName = XNamespace.None + "Wix";
147 private static readonly XName Include4ElementName = WixNamespace + "Include";
148 private static readonly XName Include3ElementName = Wix3Namespace + "Include";
149 private static readonly XName IncludeElementWithoutNamespaceName = XNamespace.None + "Include";
150 private static readonly XName SummaryInformationElementName = WixNamespace + "SummaryInformation";
151 private static readonly XName MediaTemplateElementName = WixNamespace + "MediaTemplate";
152
153 private static readonly XName DependencyCheckAttributeName = WixDependencyNamespace + "Check";
154 private static readonly XName DependencyEnforceAttributeName = WixDependencyNamespace + "Enforce";
155
156 private static readonly Dictionary<string, XNamespace> OldToNewNamespaceMapping = new Dictionary<string, XNamespace>()
157 {
158 { "http://schemas.microsoft.com/wix/BalExtension", "http://wixtoolset.org/schemas/v4/wxs/bal" },
159 { "http://schemas.microsoft.com/wix/ComPlusExtension", "http://wixtoolset.org/schemas/v4/wxs/complus" },
160 { "http://schemas.microsoft.com/wix/DependencyExtension", WixDependencyNamespace },
161 { "http://schemas.microsoft.com/wix/DifxAppExtension", "http://wixtoolset.org/schemas/v4/wxs/difxapp" },
162 { "http://schemas.microsoft.com/wix/FirewallExtension", "http://wixtoolset.org/schemas/v4/wxs/firewall" },
163 { "http://schemas.microsoft.com/wix/HttpExtension", "http://wixtoolset.org/schemas/v4/wxs/http" },
164 { "http://schemas.microsoft.com/wix/IIsExtension", "http://wixtoolset.org/schemas/v4/wxs/iis" },
165 { "http://schemas.microsoft.com/wix/MsmqExtension", "http://wixtoolset.org/schemas/v4/wxs/msmq" },
166 { "http://schemas.microsoft.com/wix/NetFxExtension", "http://wixtoolset.org/schemas/v4/wxs/netfx" },
167 { "http://schemas.microsoft.com/wix/PSExtension", "http://wixtoolset.org/schemas/v4/wxs/powershell" },
168 { "http://schemas.microsoft.com/wix/SqlExtension", "http://wixtoolset.org/schemas/v4/wxs/sql" },
169 { "http://schemas.microsoft.com/wix/TagExtension", XNamespace.None },
170 { "http://schemas.microsoft.com/wix/UtilExtension", WixUtilNamespace },
171 { "http://schemas.microsoft.com/wix/VSExtension", "http://wixtoolset.org/schemas/v4/wxs/vs" },
172 { "http://wixtoolset.org/schemas/thmutil/2010", "http://wixtoolset.org/schemas/v4/thmutil" },
173 { "http://schemas.microsoft.com/wix/2009/Lux", "http://wixtoolset.org/schemas/v4/lux" },
174 { "http://schemas.microsoft.com/wix/2006/wi", "http://wixtoolset.org/schemas/v4/wxs" },
175 { "http://schemas.microsoft.com/wix/2006/localization", "http://wixtoolset.org/schemas/v4/wxl" },
176 { "http://schemas.microsoft.com/wix/2006/libraries", "http://wixtoolset.org/schemas/v4/wixlib" },
177 { "http://schemas.microsoft.com/wix/2006/objects", "http://wixtoolset.org/schemas/v4/wixobj" },
178 { "http://schemas.microsoft.com/wix/2006/outputs", "http://wixtoolset.org/schemas/v4/wixout" },
179 { "http://schemas.microsoft.com/wix/2007/pdbs", "http://wixtoolset.org/schemas/v4/wixpdb" },
180 { "http://schemas.microsoft.com/wix/2003/04/actions", "http://wixtoolset.org/schemas/v4/wi/actions" },
181 { "http://schemas.microsoft.com/wix/2006/tables", "http://wixtoolset.org/schemas/v4/wi/tables" },
182 { "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" },
183 };
184
185 private readonly Dictionary<XName, Action<XElement>> ConvertElementMapping;
186 private readonly Regex DeprecatedPrefixRegex = new Regex(@"(?<=(^|[^\$])(\$\$)*)\$(?=\(loc\.[^.].*\))",
187 RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
188
189 /// <summary>
190 /// Instantiate a new Converter class.
191 /// </summary>
192 /// <param name="messaging"></param>
193 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
194 /// <param name="errorsAsWarnings">Test errors to display as warnings.</param>
195 /// <param name="ignoreErrors">Test errors to ignore.</param>
196 /// <param name="customTableTarget">How to convert CustomTable elements.</param>
197 public WixConverter(IMessaging messaging, int indentationAmount, IEnumerable<string> errorsAsWarnings = null, IEnumerable<string> ignoreErrors = null, CustomTableTarget customTableTarget = CustomTableTarget.Unknown)
198 {
199 this.ConvertElementMapping = new Dictionary<XName, Action<XElement>>
200 {
201 { WixConverter.AdminExecuteSequenceElementName, this.ConvertSequenceElement },
202 { WixConverter.AdminUISequenceSequenceElementName, this.ConvertSequenceElement },
203 { WixConverter.AdvertiseExecuteSequenceElementName, this.ConvertSequenceElement },
204 { WixConverter.InstallUISequenceSequenceElementName, this.ConvertSequenceElement },
205 { WixConverter.InstallExecuteSequenceElementName, this.ConvertSequenceElement },
206 { WixConverter.BootstrapperApplicationElementName, this.ConvertBootstrapperApplicationElement },
207 { WixConverter.BootstrapperApplicationRefElementName, this.ConvertBootstrapperApplicationRefElement },
208 { WixConverter.ApprovedExeForElevationElementName, this.ConvertApprovedExeForElevationElement },
209 { WixConverter.CatalogElementName, this.ConvertCatalogElement },
210 { WixConverter.ColumnElementName, this.ConvertColumnElement },
211 { WixConverter.ComponentElementName, this.ConvertComponentElement },
212 { WixConverter.ControlElementName, this.ConvertControlElement },
213 { WixConverter.CustomActionElementName, this.ConvertCustomActionElement },
214 { WixConverter.CustomTableElementName, this.ConvertCustomTableElement },
215 { WixConverter.DataElementName, this.ConvertDataElement },
216 { WixConverter.DirectoryElementName, this.ConvertDirectoryElement },
217 { WixConverter.FeatureElementName, this.ConvertFeatureElement },
218 { WixConverter.FileElementName, this.ConvertFileElement },
219 { WixConverter.FragmentElementName, this.ConvertFragmentElement },
220 { WixConverter.FirewallRemoteAddressElementName, this.ConvertFirewallRemoteAddressElement },
221 { WixConverter.EmbeddedChainerElementName, this.ConvertEmbeddedChainerElement },
222 { WixConverter.ErrorElementName, this.ConvertErrorElement },
223 { WixConverter.ExePackageElementName, this.ConvertExePackageElement },
224 { WixConverter.ModuleElementName, this.ConvertModuleElement },
225 { WixConverter.MsiPackageElementName, this.ConvertWindowsInstallerPackageElement },
226 { WixConverter.MspPackageElementName, this.ConvertWindowsInstallerPackageElement },
227 { WixConverter.MsuPackageElementName, this.ConvertSuppressSignatureValidation },
228 { WixConverter.OldProvidesElementName, this.ConvertProvidesElement },
229 { WixConverter.OldRequiresElementName, this.ConvertRequiresElement },
230 { WixConverter.OldRequiresRefElementName, this.ConvertRequiresRefElement },
231 { WixConverter.PayloadElementName, this.ConvertSuppressSignatureValidation },
232 { WixConverter.PermissionExElementName, this.ConvertPermissionExElement },
233 { WixConverter.ProductElementName, this.ConvertProductElement },
234 { WixConverter.ProgressTextElementName, this.ConvertProgressTextElement },
235 { WixConverter.PropertyRefElementName, this.ConvertPropertyRefElement },
236 { WixConverter.PublishElementName, this.ConvertPublishElement },
237 { WixConverter.MultiStringValueElementName, this.ConvertMultiStringValueElement },
238 { WixConverter.RegistryKeyElementName, this.ConvertRegistryKeyElement },
239 { WixConverter.RegistrySearchElementName, this.ConvertRegistrySearchElement },
240 { WixConverter.RemotePayloadElementName, this.ConvertRemotePayloadElement },
241 { WixConverter.RequiredPrivilegeElementName, this.ConvertRequiredPrivilegeElement },
242 { WixConverter.CustomActionRefElementName, this.ConvertCustomActionRefElement },
243 { WixConverter.ServiceArgumentElementName, this.ConvertServiceArgumentElement },
244 { WixConverter.SetDirectoryElementName, this.ConvertSetDirectoryElement },
245 { WixConverter.SetPropertyElementName, this.ConvertSetPropertyElement },
246 { WixConverter.ShortcutPropertyElementName, this.ConvertShortcutPropertyElement },
247 { WixConverter.TagElementName, this.ConvertTagElement },
248 { WixConverter.TagRefElementName, this.ConvertTagRefElement },
249 { WixConverter.TextElementName, this.ConvertTextElement },
250 { WixConverter.UITextElementName, this.ConvertUITextElement },
251 { WixConverter.VariableElementName, this.ConvertVariableElement },
252 { WixConverter.UtilCloseApplicationElementName, this.ConvertUtilCloseApplicationElementName },
253 { WixConverter.UtilPermissionExElementName, this.ConvertUtilPermissionExElement },
254 { WixConverter.UtilRegistrySearchName, this.ConvertUtilRegistrySearchElement },
255 { WixConverter.UtilXmlConfigElementName, this.ConvertUtilXmlConfigElement },
256 { WixConverter.PropertyElementName, this.ConvertPropertyElement },
257 { WixConverter.WixElementWithoutNamespaceName, this.ConvertElementWithoutNamespace },
258 { WixConverter.IncludeElementWithoutNamespaceName, this.ConvertElementWithoutNamespace },
259 { WixConverter.VerbElementName, this.ConvertVerbElement },
260 };
261
262 this.Messaging = messaging;
263
264 this.IndentationAmount = indentationAmount;
265
266 this.ErrorsAsWarnings = new HashSet<ConverterTestType>(this.YieldConverterTypes(errorsAsWarnings));
267
268 this.IgnoreErrors = new HashSet<ConverterTestType>(this.YieldConverterTypes(ignoreErrors));
269
270 this.CustomTableSetting = customTableTarget;
271 }
272
273 private CustomTableTarget CustomTableSetting { get; }
274
275 private int Errors { get; set; }
276
277 private HashSet<ConverterTestType> ErrorsAsWarnings { get; set; }
278
279 private HashSet<ConverterTestType> IgnoreErrors { get; set; }
280
281 private IMessaging Messaging { get; }
282
283 private int IndentationAmount { get; set; }
284
285 private ConvertOperation Operation { get; set; }
286
287 private string SourceFile { get; set; }
288
289 private int SourceVersion { get; set; }
290
291 /// <summary>
292 /// Convert a file.
293 /// </summary>
294 /// <param name="sourceFile">The file to convert.</param>
295 /// <param name="saveConvertedFile">Option to save the converted errors that are found.</param>
296 /// <returns>The number of errors found.</returns>
297 public int ConvertFile(string sourceFile, bool saveConvertedFile)
298 {
299 var document = this.OpenSourceFile(sourceFile);
300
301 if (document is null)
302 {
303 return 1;
304 }
305
306 this.ConvertDocument(document);
307
308 // Fix errors if requested and necessary.
309 if (saveConvertedFile && 0 < this.Errors)
310 {
311 this.SaveDocument(document);
312 }
313
314 return this.Errors;
315 }
316
317 /// <summary>
318 /// Convert a document.
319 /// </summary>
320 /// <param name="document">The document to convert.</param>
321 /// <returns>The number of errors found.</returns>
322 public int ConvertDocument(XDocument document)
323 {
324 // Reset the instance info.
325 this.Errors = 0;
326 this.SourceVersion = 0;
327 this.Operation = ConvertOperation.Convert;
328
329 // Remove the declaration.
330 if (null != document.Declaration
331 && this.OnError(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line."))
332 {
333 document.Declaration = null;
334 TrimLeadingText(document);
335 }
336
337 // Start converting the nodes at the top.
338 this.ConvertNodes(document.Nodes(), 0);
339 this.RemoveUnusedNamespaces(document.Root);
340
341 return this.Errors;
342 }
343
344 /// <summary>
345 /// Format a file.
346 /// </summary>
347 /// <param name="sourceFile">The file to format.</param>
348 /// <param name="saveConvertedFile">Option to save the format errors that are found.</param>
349 /// <returns>The number of errors found.</returns>
350 public int FormatFile(string sourceFile, bool saveConvertedFile)
351 {
352 var document = this.OpenSourceFile(sourceFile);
353
354 if (document is null)
355 {
356 return 1;
357 }
358
359 this.FormatDocument(document);
360
361 // Fix errors if requested and necessary.
362 if (saveConvertedFile && 0 < this.Errors)
363 {
364 this.SaveDocument(document);
365 }
366
367 return this.Errors;
368 }
369
370 /// <summary>
371 /// Format a document.
372 /// </summary>
373 /// <param name="document">The document to format.</param>
374 /// <returns>The number of errors found.</returns>
375 public int FormatDocument(XDocument document)
376 {
377 // Reset the instance info.
378 this.Errors = 0;
379 this.SourceVersion = 0;
380 this.Operation = ConvertOperation.Format;
381
382 // Remove the declaration.
383 if (null != document.Declaration
384 && this.OnError(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line."))
385 {
386 document.Declaration = null;
387 TrimLeadingText(document);
388 }
389
390 // Start converting the nodes at the top.
391 this.ConvertNodes(document.Nodes(), 0);
392 this.RemoveUnusedNamespaces(document.Root);
393
394 return this.Errors;
395 }
396
397 private XDocument OpenSourceFile(string sourceFile)
398 {
399 this.SourceFile = sourceFile;
400
401 try
402 {
403 return XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
404 }
405 catch (XmlException e)
406 {
407 this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message);
408 }
409
410 return null;
411 }
412
413 private void SaveDocument(XDocument document)
414 {
415 var ignoreDeclarationError = this.IgnoreErrors.Contains(ConverterTestType.DeclarationPresent);
416
417 try
418 {
419 using (var writer = XmlWriter.Create(this.SourceFile, new XmlWriterSettings { OmitXmlDeclaration = !ignoreDeclarationError }))
420 {
421 document.Save(writer);
422 }
423 }
424 catch (UnauthorizedAccessException)
425 {
426 this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file.");
427 }
428 }
429
430 private void ConvertNodes(IEnumerable<XNode> nodes, int level)
431 {
432 // Note we operate on a copy of the node list since we may
433 // remove some whitespace nodes during this processing.
434 foreach (var node in nodes.ToList())
435 {
436 if (node is XText text)
437 {
438 if (null != text.Value)
439 {
440 if (this.TryFixDeprecatedLocalizationPrefixes(node, text.Value, out var newValue, ConverterTestType.DeprecatedLocalizationVariablePrefixInTextValue))
441 {
442 text.Value = newValue;
443 }
444 }
445 if (!String.IsNullOrWhiteSpace(text.Value))
446 {
447 text.Value = text.Value.Trim();
448 }
449 else if (node.NextNode is XCData cdata)
450 {
451 this.EnsurePrecedingWhitespaceRemoved(text, node, ConverterTestType.WhitespacePrecedingNodeWrong);
452 }
453 else if (node.NextNode is XElement element)
454 {
455 this.EnsurePrecedingWhitespaceCorrect(text, node, level, ConverterTestType.WhitespacePrecedingNodeWrong);
456 }
457 else if (node.NextNode is null) // this is the space before the close element
458 {
459 if (node.PreviousNode is null || node.PreviousNode is XCData)
460 {
461 this.EnsurePrecedingWhitespaceRemoved(text, node.Parent, ConverterTestType.WhitespacePrecedingEndElementWrong);
462 }
463 else if (level == 0) // root element's close tag
464 {
465 this.EnsurePrecedingWhitespaceCorrect(text, node, 0, ConverterTestType.WhitespacePrecedingEndElementWrong);
466 }
467 else
468 {
469 this.EnsurePrecedingWhitespaceCorrect(text, node, level - 1, ConverterTestType.WhitespacePrecedingEndElementWrong);
470 }
471 }
472 }
473 else if (node is XElement element)
474 {
475 this.ConvertElement(element);
476
477 var before = element.Nodes().ToList();
478
479 this.ConvertNodes(before, level + 1);
480
481 // If any nodes were added during the processing of the children,
482 // ensure those added children get processed as well.
483 var added = element.Nodes().Except(before).ToList();
484
485 if (added.Any())
486 {
487 this.ConvertNodes(added, level + 1);
488 }
489 }
490 }
491 }
492
493 private bool TryFixDeprecatedLocalizationPrefixes(XNode node, string value, out string newValue, ConverterTestType testType)
494 {
495 newValue = this.DeprecatedPrefixRegex.Replace(value, "!");
496
497 if (object.ReferenceEquals(newValue, value))
498 {
499 return false;
500 }
501
502 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.";
503
504 return this.OnError(testType, node, message);
505 }
506
507 private void EnsurePrecedingWhitespaceCorrect(XText whitespace, XNode node, int level, ConverterTestType testType)
508 {
509 if (!WixConverter.LeadingWhitespaceValid(this.IndentationAmount, level, whitespace.Value))
510 {
511 var message = testType == ConverterTestType.WhitespacePrecedingEndElementWrong ? "The whitespace preceding this end element is incorrect." : "The whitespace preceding this node is incorrect.";
512
513 if (this.OnError(testType, node, message))
514 {
515 WixConverter.FixupWhitespace(this.IndentationAmount, level, whitespace);
516 }
517 }
518 }
519
520 private void EnsurePrecedingWhitespaceRemoved(XText whitespace, XNode node, ConverterTestType testType)
521 {
522 if (!String.IsNullOrEmpty(whitespace.Value) && whitespace.NodeType != XmlNodeType.CDATA)
523 {
524 var message = testType == ConverterTestType.WhitespacePrecedingEndElementWrong ? "The whitespace preceding this end element is incorrect." : "The whitespace preceding this node is incorrect.";
525
526 if (this.OnError(testType, node, message))
527 {
528 whitespace.Remove();
529 }
530 }
531 }
532
533 private void ConvertElement(XElement element)
534 {
535 var deprecatedToUpdatedNamespaces = new Dictionary<XNamespace, XNamespace>();
536
537 foreach (var attribute in element.Attributes())
538 {
539 if (attribute.IsNamespaceDeclaration)
540 {
541 // Gather any deprecated namespaces, then update this element tree based on those deprecations.
542 var declaration = attribute;
543
544 if (element.Name == Wix3ElementName || element.Name == Include3ElementName)
545 {
546 this.SourceVersion = 3;
547 }
548 else if (element.Name == Wix4ElementName || element.Name == Include4ElementName)
549 {
550 this.SourceVersion = 4;
551 }
552
553 if (WixConverter.OldToNewNamespaceMapping.TryGetValue(declaration.Value, out var ns))
554 {
555 if (this.OnError(ConverterTestType.XmlnsValueWrong, declaration, "The namespace '{0}' is out of date. It must be '{1}'.", declaration.Value, ns.NamespaceName))
556 {
557 deprecatedToUpdatedNamespaces.Add(declaration.Value, ns);
558 }
559 }
560 }
561 else
562 {
563 if (null != attribute.Value)
564 {
565 if (this.TryFixDeprecatedLocalizationPrefixes(element, attribute.Value, out var newValue, ConverterTestType.DeprecatedLocalizationVariablePrefixInAttributeValue))
566 {
567 attribute.Value = newValue;
568 }
569 }
570 }
571 }
572
573 if (deprecatedToUpdatedNamespaces.Any())
574 {
575 WixConverter.UpdateElementsWithDeprecatedNamespaces(element.DescendantsAndSelf(), deprecatedToUpdatedNamespaces);
576 }
577
578 // Apply any specialized conversion actions.
579 if (this.ConvertElementMapping.TryGetValue(element.Name, out var convert))
580 {
581 convert(element);
582 }
583 }
584
585 private void ConvertBootstrapperApplicationElement(XElement element)
586 {
587 var xUseUILanguages = element.Attribute(BalUseUILanguagesName);
588 if (xUseUILanguages != null &&
589 this.OnError(ConverterTestType.BalUseUILanguagesDeprecated, element, "bal:UseUILanguages is deprecated, 'true' is now the standard behavior."))
590 {
591 xUseUILanguages.Remove();
592 }
593
594 var xBADll = element.Elements(BootstrapperApplicationDllElementName).FirstOrDefault();
595 if (xBADll == null)
596 {
597 xBADll = this.CreateBootstrapperApplicationDllElement(element);
598
599 if (xBADll != null)
600 {
601 element.Add(Environment.NewLine);
602 element.Add(xBADll);
603 element.Add(Environment.NewLine);
604 }
605 }
606 }
607
608 private XElement CreateBootstrapperApplicationDllElement(XElement element)
609 {
610 XElement xBADll = null;
611 var xSource = element.Attribute("SourceFile");
612 var xDpiAwareness = element.Attribute("DpiAwareness");
613
614 if (xSource != null)
615 {
616 if (xBADll != null || CreateBADllElement(element, out xBADll))
617 {
618 MoveAttribute(element, "SourceFile", xBADll);
619 MoveAttribute(element, "Name", xBADll);
620 }
621 }
622 else if (xDpiAwareness != null || this.SourceVersion < 4) // older code might be relying on old behavior of first Payload element being the BA dll.
623 {
624 var xFirstChild = element.Elements().FirstOrDefault();
625 if (xFirstChild?.Name == PayloadElementName)
626 {
627 if (xBADll != null || CreateBADllElement(element, out xBADll))
628 {
629 var attributes = xFirstChild.Attributes().ToList();
630 xFirstChild.Remove();
631
632 foreach (var attribute in attributes)
633 {
634 xBADll.Add(attribute);
635 }
636 }
637 }
638 else
639 {
640 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.");
641 }
642 }
643
644 if (xDpiAwareness != null)
645 {
646 if (xBADll != null || CreateBADllElement(element, out xBADll))
647 {
648 MoveAttribute(element, "DpiAwareness", xBADll);
649 }
650 }
651 else if (this.SourceVersion < 4 && xBADll != null &&
652 this.OnError(ConverterTestType.AssignBootstrapperApplicationDpiAwareness, element, "The BootstrapperApplicationDll DpiAwareness attribute is being set to 'unaware' to ensure it remains the same as the v3 default"))
653 {
654 xBADll.Add(new XAttribute("DpiAwareness", "unaware"));
655 }
656
657 return xBADll;
658
659 bool CreateBADllElement(XObject node, out XElement xCreatedBADll)
660 {
661 var create = this.OnError(ConverterTestType.BootstrapperApplicationDll, node, "The bootstrapper application dll is now specified in the BootstrapperApplicationDll element.");
662 xCreatedBADll = create ? new XElement(BootstrapperApplicationDllElementName) : null;
663 return create;
664 }
665 }
666
667 private void ConvertBootstrapperApplicationRefElement(XElement element)
668 {
669 var xUseUILanguages = element.Attribute(BalUseUILanguagesName);
670 if (xUseUILanguages != null &&
671 this.OnError(ConverterTestType.BalUseUILanguagesDeprecated, element, "bal:UseUILanguages is deprecated, 'true' is now the standard behavior."))
672 {
673 xUseUILanguages.Remove();
674 }
675
676 var xId = element.Attribute("Id");
677 if (xId != null)
678 {
679 XName balBAName = null;
680 XName oldBalBAName = null;
681 string theme = null;
682
683 switch (xId.Value)
684 {
685 case "WixStandardBootstrapperApplication.RtfLicense":
686 balBAName = BalStandardBootstrapperApplicationName;
687 theme = "rtfLicense";
688 break;
689 case "WixStandardBootstrapperApplication.RtfLargeLicense":
690 balBAName = BalStandardBootstrapperApplicationName;
691 theme = "rtfLargeLicense";
692 break;
693 case "WixStandardBootstrapperApplication.HyperlinkLicense":
694 balBAName = BalStandardBootstrapperApplicationName;
695 theme = "hyperlinkLicense";
696 break;
697 case "WixStandardBootstrapperApplication.HyperlinkLargeLicense":
698 balBAName = BalStandardBootstrapperApplicationName;
699 theme = "hyperlinkLargeLicense";
700 break;
701 case "WixStandardBootstrapperApplication.HyperlinkSidebarLicense":
702 balBAName = BalStandardBootstrapperApplicationName;
703 theme = "hyperlinkSidebarLicense";
704 break;
705 case "WixStandardBootstrapperApplication.Foundation":
706 balBAName = BalStandardBootstrapperApplicationName;
707 theme = "none";
708 break;
709 case "ManagedBootstrapperApplicationHost":
710 case "ManagedBootstrapperApplicationHost.RtfLicense":
711 balBAName = BalManagedBootstrapperApplicationHostName;
712 theme = "standard";
713 break;
714 case "ManagedBootstrapperApplicationHost.Minimal":
715 case "ManagedBootstrapperApplicationHost.RtfLicense.Minimal":
716 case "ManagedBootstrapperApplicationHost.Foundation":
717 balBAName = BalManagedBootstrapperApplicationHostName;
718 theme = "none";
719 break;
720 case "DotNetCoreBootstrapperApplicationHost":
721 case "DotNetCoreBootstrapperApplicationHost.RtfLicense":
722 balBAName = BalNewDotNetCoreBootstrapperApplicationName;
723 oldBalBAName = BalOldDotNetCoreBootstrapperApplicationName;
724 theme = "standard";
725 break;
726 case "DotNetCoreBootstrapperApplicationHost.Minimal":
727 case "DotNetCoreBootstrapperApplicationHost.RtfLicense.Minimal":
728 case "DotNetCoreBootstrapperApplicationHost.Foundation":
729 balBAName = BalNewDotNetCoreBootstrapperApplicationName;
730 oldBalBAName = BalOldDotNetCoreBootstrapperApplicationName;
731 theme = "none";
732 break;
733 }
734
735 if (balBAName != null && theme != null &&
736 this.OnError(ConverterTestType.BalBootstrapperApplicationRefToElement, element, "Built-in bootstrapper applications must be referenced through their custom element"))
737 {
738 element.Name = BootstrapperApplicationElementName;
739 xId.Remove();
740 this.ConvertBalBootstrapperApplicationRef(element, theme, balBAName, oldBalBAName);
741 }
742 }
743 }
744
745 private void ConvertApprovedExeForElevationElement(XElement element)
746 {
747 this.RenameWin64ToBitness(element);
748 }
749
750 private void ConvertBalBootstrapperApplicationRef(XElement element, string theme, XName balBAElementName, XName oldBalBAElementName = null)
751 {
752 var xBalBa = element.Element(oldBalBAElementName ?? balBAElementName);
753 if (xBalBa == null)
754 {
755 xBalBa = new XElement(balBAElementName);
756 element.Add(Environment.NewLine);
757 element.Add(xBalBa);
758 element.Add(Environment.NewLine);
759 }
760 else if (oldBalBAElementName != null)
761 {
762 xBalBa.Name = BalNewDotNetCoreBootstrapperApplicationName;
763 }
764
765 if (theme != "standard")
766 {
767 xBalBa.Add(new XAttribute("Theme", theme));
768 }
769 }
770
771 private void ConvertCatalogElement(XElement element)
772 {
773 if (this.OnError(ConverterTestType.BundleSignatureValidationObsolete, element, "The Catalog element is obsolete. Signature validation is no longer supported. The element will be removed."))
774 {
775 element.Remove();
776 }
777 }
778
779 private void ConvertColumnElement(XElement element)
780 {
781 var category = element.Attribute("Category");
782 if (category != null)
783 {
784 var camelCaseValue = LowercaseFirstChar(category.Value);
785 if (category.Value != camelCaseValue &&
786 this.OnError(ConverterTestType.ColumnCategoryCamelCase, element, "The CustomTable Category attribute contains an incorrectly cased '{0}' value. Lowercase the first character instead.", category.Name))
787 {
788 category.Value = camelCaseValue;
789 }
790 }
791
792 var modularization = element.Attribute("Modularize");
793 if (modularization != null)
794 {
795 var camelCaseValue = LowercaseFirstChar(modularization.Value);
796 if (modularization.Value != camelCaseValue &&
797 this.OnError(ConverterTestType.ColumnModularizeCamelCase, element, "The CustomTable Modularize attribute contains an incorrectly cased '{0}' value. Lowercase the first character instead.", modularization.Name))
798 {
799 modularization.Value = camelCaseValue;
800 }
801 }
802 }
803
804 private void ConvertCustomTableElement(XElement element)
805 {
806 var bootstrapperApplicationData = element.Attribute("BootstrapperApplicationData");
807 if (bootstrapperApplicationData?.Value == "no")
808 {
809 if (this.OnError(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'Unreal' attribute instead.", bootstrapperApplicationData.Name))
810 {
811 bootstrapperApplicationData.Remove();
812 }
813 }
814 else
815 {
816 if (element.Elements(ColumnElementName).Any() || bootstrapperApplicationData != null)
817 {
818 // Table definition
819 if (bootstrapperApplicationData != null)
820 {
821 switch (this.CustomTableSetting)
822 {
823 case CustomTableTarget.Bundle:
824 if (this.OnError(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'BundleCustomData' element for Bundles.", bootstrapperApplicationData.Name))
825 {
826 element.Name = WixConverter.BundleCustomDataElementName;
827 bootstrapperApplicationData.Remove();
828 this.ConvertCustomTableElementToBundle(element);
829 }
830 break;
831 case CustomTableTarget.Msi:
832 if (this.OnError(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'Unreal' attribute instead.", bootstrapperApplicationData.Name))
833 {
834 element.Add(new XAttribute("Unreal", bootstrapperApplicationData.Value));
835 bootstrapperApplicationData.Remove();
836 }
837 break;
838 default:
839 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'", bootstrapperApplicationData.Name);
840 break;
841 }
842 }
843 }
844 else
845 {
846 // Table ref
847 switch (this.CustomTableSetting)
848 {
849 case CustomTableTarget.Bundle:
850 if (this.OnError(ConverterTestType.CustomTableRef, element, "CustomTable elements that don't contain the table definition are now BundleCustomDataRef for Bundles."))
851 {
852 element.Name = WixConverter.BundleCustomDataRefElementName;
853 this.ConvertCustomTableElementToBundle(element);
854 }
855 break;
856 case CustomTableTarget.Msi:
857 if (this.OnError(ConverterTestType.CustomTableRef, element, "CustomTable elements that don't contain the table definition are now CustomTableRef for MSI."))
858 {
859 element.Name = WixConverter.CustomTableRefElementName;
860 }
861 break;
862 default:
863 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'");
864 break;
865 }
866 }
867 }
868 }
869
870 private void ConvertCustomTableElementToBundle(XElement element)
871 {
872 foreach (var xColumn in element.Elements(ColumnElementName))
873 {
874 xColumn.Name = WixConverter.BundleAttributeDefinitionElementName;
875
876 foreach (var xAttribute in xColumn.Attributes().ToList())
877 {
878 if (xAttribute.Name.LocalName != "Id" &&
879 (xAttribute.Name.Namespace == WixConverter.Wix3Namespace ||
880 xAttribute.Name.Namespace == WixConverter.WixNamespace ||
881 String.IsNullOrEmpty(xAttribute.Name.Namespace.NamespaceName)))
882 {
883 xAttribute.Remove();
884 }
885 }
886 }
887
888 foreach (var xRow in element.Elements(RowElementName))
889 {
890 xRow.Name = WixConverter.BundleElementElementName;
891
892 foreach (var xData in xRow.Elements(DataElementName))
893 {
894 xData.Name = WixConverter.BundleAttributeElementName;
895
896 var xColumn = xData.Attribute("Column");
897 if (xColumn != null)
898 {
899 xData.Add(new XAttribute("Id", xColumn.Value));
900 xColumn.Remove();
901 }
902
903 this.ConvertInnerTextToAttribute(xData, "Value");
904 }
905 }
906 }
907
908 private void ConvertControlElement(XElement element)
909 {
910 var remove = new List<XElement>();
911
912 foreach (var xCondition in element.Elements(ConditionElementName))
913 {
914 var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value);
915 if (!String.IsNullOrEmpty(action) &&
916 TryGetInnerText(xCondition, out var text) &&
917 this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action))
918 {
919 element.Add(new XAttribute(action + "Condition", text));
920 remove.Add(xCondition);
921 }
922 }
923
924 for (var i = remove.Count - 1; i >= 0; i--)
925 {
926 remove[i].Remove();
927 }
928 }
929
930 private void ConvertComponentElement(XElement element)
931 {
932 var guid = element.Attribute("Guid");
933 if (guid != null && guid.Value == "*")
934 {
935 if (this.OnError(ConverterTestType.AutoGuidUnnecessary, element, "Using '*' for the Component Guid attribute is unnecessary. Remove the attribute to remove the redundancy."))
936 {
937 guid.Remove();
938 }
939 }
940
941 var xCondition = element.Element(ConditionElementName);
942 if (xCondition != null)
943 {
944 if (TryGetInnerText(xCondition, out var text) &&
945 this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
946 {
947 element.Add(new XAttribute("Condition", text));
948 xCondition.Remove();
949 }
950 }
951
952 this.RenameWin64ToBitness(element);
953 }
954
955 private void ConvertDirectoryElement(XElement element)
956 {
957 if (null == element.Attribute("Name"))
958 {
959 var attribute = element.Attribute("ShortName");
960 if (null != attribute)
961 {
962 var shortName = attribute.Value;
963 if (this.OnError(ConverterTestType.AssignDirectoryNameFromShortName, element, "The directory ShortName attribute is being renamed to Name since Name wasn't specified for value '{0}'", shortName))
964 {
965 element.Add(new XAttribute("Name", shortName));
966 attribute.Remove();
967 }
968 }
969 }
970
971 var id = element.Attribute("Id")?.Value;
972
973 if (id == "TARGETDIR" &&
974 this.OnError(ConverterTestType.TargetDirDeprecated, element, "The TARGETDIR directory should not longer be explicitly defined. Remove the Directory element with Id attribute 'TARGETDIR'."))
975 {
976 var parentElement = element.Parent;
977
978 element.Remove();
979
980 if (parentElement.FirstNode is XText text && String.IsNullOrWhiteSpace(text.Value))
981 {
982 parentElement.FirstNode.Remove();
983 }
984
985 foreach (var child in element.Nodes())
986 {
987 parentElement.Add(child);
988 }
989
990 element.RemoveAll();
991
992 if (parentElement.FirstNode is XText textAgain && String.IsNullOrWhiteSpace(textAgain.Value))
993 {
994 parentElement.FirstNode.Remove();
995 }
996 }
997 else if (id != null &&
998 WindowsInstallerStandard.IsStandardDirectory(id) &&
999 this.OnError(ConverterTestType.DefiningStandardDirectoryDeprecated, element, "Standard directories such as '{0}' should no longer be defined using the Directory element. Use the StandardDirectory element instead.", id))
1000 {
1001 element.Name = StandardDirectoryElementName;
1002
1003 foreach (var attrib in element.Attributes().Where(a => a.Name.LocalName != "Id").ToList())
1004 {
1005 attrib.Remove();
1006 }
1007 }
1008 }
1009
1010 private void ConvertFeatureElement(XElement element)
1011 {
1012 var xAbsent = element.Attribute("Absent");
1013 if (xAbsent != null &&
1014 this.OnError(ConverterTestType.FeatureAbsentAttributeReplaced, element, "The Feature element's Absent attribute has been replaced with the AllowAbsent attribute. Use the 'AllowAbsent' attribute instead."))
1015 {
1016 if (xAbsent.Value == "disallow")
1017 {
1018 element.Add(new XAttribute("AllowAbsent", "no"));
1019 }
1020 xAbsent.Remove();
1021 }
1022
1023 var xAllowAdvertise = element.Attribute("AllowAdvertise");
1024 if (xAllowAdvertise != null)
1025 {
1026 if ((xAllowAdvertise.Value == "system" || xAllowAdvertise.Value == "allow") &&
1027 this.OnError(ConverterTestType.FeatureAllowAdvertiseValueDeprecated, element, "The AllowAdvertise attribute's '{0}' value is deprecated. Set the value to 'yes' instead.", xAllowAdvertise.Value))
1028 {
1029 xAllowAdvertise.Value = "yes";
1030 }
1031 else if (xAllowAdvertise.Value == "disallow" &&
1032 this.OnError(ConverterTestType.FeatureAllowAdvertiseValueDeprecated, element, "The AllowAdvertise attribute's '{0}' value is deprecated. Remove the value instead.", xAllowAdvertise.Value))
1033 {
1034 xAllowAdvertise.Remove();
1035 }
1036 }
1037
1038 var xCondition = element.Element(ConditionElementName);
1039 if (xCondition != null)
1040 {
1041 var level = xCondition.Attribute("Level")?.Value;
1042 if (!String.IsNullOrEmpty(level) &&
1043 TryGetInnerText(xCondition, out var text) &&
1044 this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName))
1045 {
1046 xCondition.AddAfterSelf(new XElement(LevelElementName,
1047 new XAttribute("Value", level),
1048 new XAttribute("Condition", text)
1049 ));
1050 xCondition.Remove();
1051 }
1052 }
1053 }
1054
1055 private void ConvertFileElement(XElement element)
1056 {
1057 if (this.SourceVersion < 4 && null == element.Attribute("Id"))
1058 {
1059 var attribute = element.Attribute("Name");
1060
1061 if (null == attribute)
1062 {
1063 attribute = element.Attribute("Source");
1064 }
1065
1066 if (null != attribute)
1067 {
1068 var name = Path.GetFileName(attribute.Value);
1069
1070 if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the v3 default", name))
1071 {
1072 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
1073 element.RemoveAttributes();
1074 element.Add(new XAttribute("Id", GetIdentifierFromName(name)));
1075 element.Add(attributes);
1076 }
1077 }
1078 }
1079 }
1080
1081 private void ConvertFragmentElement(XElement element)
1082 {
1083 var remove = new List<XElement>();
1084
1085 foreach (var xCondition in element.Elements(ConditionElementName))
1086 {
1087 var message = xCondition.Attribute("Message")?.Value;
1088
1089 if (!String.IsNullOrEmpty(message) &&
1090 TryGetInnerText(xCondition, out var text) &&
1091 this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", xCondition.Name.LocalName))
1092 {
1093 xCondition.AddAfterSelf(new XElement(LaunchElementName,
1094 new XAttribute("Condition", text),
1095 new XAttribute("Message", message)
1096 ));
1097 remove.Add(xCondition);
1098 }
1099 }
1100
1101 for (var i = remove.Count - 1; i >= 0; i--)
1102 {
1103 remove[i].Remove();
1104 }
1105 }
1106
1107 private void ConvertFirewallRemoteAddressElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1108
1109 private void ConvertEmbeddedChainerElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition");
1110
1111 private void ConvertErrorElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Message");
1112
1113 private void ConvertExePackageElement(XElement element)
1114 {
1115 this.ConvertSuppressSignatureValidation(element);
1116
1117 foreach (var attributeName in new[] { "InstallCommand", "RepairCommand", "UninstallCommand" })
1118 {
1119 var newName = attributeName.Replace("Command", "Arguments");
1120 var attribute = element.Attribute(attributeName);
1121
1122 if (attribute != null &&
1123 this.OnError(ConverterTestType.RenameExePackageCommandToArguments, element, "The {0} element {1} attribute has been renamed {2}.", element.Name.LocalName, attribute.Name.LocalName, newName))
1124 {
1125 element.Add(new XAttribute(newName, attribute.Value));
1126 attribute.Remove();
1127 }
1128 }
1129 }
1130
1131 private void ConvertPermissionExElement(XElement element)
1132 {
1133 var xCondition = element.Element(ConditionElementName);
1134 if (xCondition != null)
1135 {
1136 if (TryGetInnerText(xCondition, out var text) &&
1137 this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
1138 {
1139 element.Add(new XAttribute("Condition", text));
1140 xCondition.Remove();
1141 }
1142 }
1143 }
1144
1145 private void ConvertProgressTextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Message");
1146
1147 private void ConvertModuleElement(XElement element)
1148 {
1149 if (element.Attribute("Guid") == null // skip already-converted Module elements
1150 && this.OnError(ConverterTestType.ModuleAndPackageRenamed, element, "The Module and Package elements have been renamed and reorganized for simplicity."))
1151 {
1152 var xModule = element;
1153
1154 var xSummaryInformation = xModule.Element(PackageElementName);
1155 if (xSummaryInformation != null)
1156 {
1157 xSummaryInformation.Name = SummaryInformationElementName;
1158
1159 var xInstallerVersion = xSummaryInformation.Attribute("InstallerVersion");
1160 if (this.SourceVersion < 4 && xInstallerVersion == null)
1161 {
1162 this.OnError(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.");
1163 }
1164
1165 RemoveAttribute(xSummaryInformation, "AdminImage");
1166 RemoveAttribute(xSummaryInformation, "Comments");
1167 MoveAttribute(xSummaryInformation, "Id", xModule, "Guid");
1168 MoveAttribute(xSummaryInformation, "InstallerVersion", xModule);
1169 RemoveAttribute(xSummaryInformation, "Languages");
1170 RemoveAttribute(xSummaryInformation, "Platform");
1171 RemoveAttribute(xSummaryInformation, "Platforms");
1172 RemoveAttribute(xSummaryInformation, "ReadOnly");
1173 MoveAttribute(xSummaryInformation, "SummaryCodepage", xSummaryInformation, "Codepage", defaultValue: "1252");
1174
1175 if (!xSummaryInformation.HasAttributes)
1176 {
1177 xSummaryInformation.Remove();
1178 }
1179 }
1180 }
1181 }
1182
1183 private void ConvertProductElement(XElement element)
1184 {
1185 var id = element.Attribute("Id");
1186 if (id != null && id.Value == "*")
1187 {
1188 if (this.OnError(ConverterTestType.AutoGuidUnnecessary, element, "Using '*' for the Product Id attribute is unnecessary. Remove the attribute to remove the redundancy."))
1189 {
1190 id.Remove();
1191 }
1192 }
1193
1194 var xConditions = element.Elements(ConditionElementName).ToList();
1195 foreach (var xCondition in xConditions)
1196 {
1197 var message = xCondition.Attribute("Message")?.Value;
1198
1199 if (!String.IsNullOrEmpty(message) &&
1200 TryGetInnerText(xCondition, out var text) &&
1201 this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", xCondition.Name.LocalName))
1202 {
1203 xCondition.AddAfterSelf(new XElement(LaunchElementName,
1204 new XAttribute("Condition", text),
1205 new XAttribute("Message", message)
1206 ));
1207 xCondition.Remove();
1208 }
1209 }
1210
1211 var xMediaTemplate = element.Element(MediaTemplateElementName);
1212 if (xMediaTemplate?.HasAttributes == false
1213 && this.OnError(ConverterTestType.DefaultMediaTemplate, element, "A MediaTemplate with no attributes set is now provided by default. Remove the element."))
1214 {
1215 xMediaTemplate.Remove();
1216 }
1217
1218 if (this.OnError(ConverterTestType.ProductAndPackageRenamed, element, "The Product and Package elements have been renamed and reorganized for simplicity."))
1219 {
1220 var xPackage = element;
1221 xPackage.Name = PackageElementName;
1222
1223 var xSummaryInformation = xPackage.Element(PackageElementName);
1224 if (xSummaryInformation != null)
1225 {
1226 xSummaryInformation.Name = SummaryInformationElementName;
1227
1228 var xInstallerVersion = xSummaryInformation.Attribute("InstallerVersion");
1229 if (this.SourceVersion < 4 && xInstallerVersion == null)
1230 {
1231 this.OnError(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.");
1232 }
1233
1234 if (xSummaryInformation.Attribute("Compressed") == null)
1235 {
1236 xPackage.SetAttributeValue("Compressed", "no");
1237 }
1238 else
1239 {
1240 MoveAttribute(xSummaryInformation, "Compressed", xPackage, defaultValue: "yes");
1241 }
1242
1243 RemoveAttribute(xSummaryInformation, "AdminImage");
1244 RemoveAttribute(xSummaryInformation, "Comments");
1245 RemoveAttribute(xSummaryInformation, "Id");
1246 MoveAttribute(xSummaryInformation, "InstallerVersion", xPackage, defaultValue: "500");
1247 MoveAttribute(xSummaryInformation, "InstallScope", xPackage, "Scope", defaultValue: "perMachine");
1248 RemoveAttribute(xSummaryInformation, "Languages");
1249 RemoveAttribute(xSummaryInformation, "Platform");
1250 RemoveAttribute(xSummaryInformation, "Platforms");
1251 RemoveAttribute(xSummaryInformation, "ReadOnly");
1252 MoveAttribute(xSummaryInformation, "ShortNames", xPackage);
1253 MoveAttribute(xSummaryInformation, "SummaryCodepage", xSummaryInformation, "Codepage", defaultValue: "1252");
1254 MoveAttribute(xPackage, "Id", xPackage, "ProductCode");
1255
1256 var xInstallPrivileges = xSummaryInformation.Attribute("InstallPrivileges");
1257 switch (xInstallPrivileges?.Value)
1258 {
1259 case "limited":
1260 xPackage.SetAttributeValue("Scope", "perUser");
1261 break;
1262 case "elevated":
1263 {
1264 var xAllUsers = xPackage.Elements(PropertyElementName).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS");
1265 if (xAllUsers?.Attribute("Value")?.Value == "1")
1266 {
1267 xAllUsers?.Remove();
1268 }
1269 }
1270 break;
1271 }
1272
1273 xInstallPrivileges?.Remove();
1274
1275 if (!xSummaryInformation.HasAttributes)
1276 {
1277 xSummaryInformation.Remove();
1278 }
1279 }
1280 }
1281 }
1282
1283 private static void MoveAttribute(XElement xSource, string attributeName, XElement xDestination, string destinationAttributeName = null, string defaultValue = null)
1284 {
1285 var xAttribute = xSource.Attribute(attributeName);
1286 if (xAttribute != null && (defaultValue == null || xAttribute.Value != defaultValue))
1287 {
1288 xDestination.SetAttributeValue(destinationAttributeName ?? attributeName, xAttribute.Value);
1289 }
1290
1291 xAttribute?.Remove();
1292 }
1293
1294 private static void RemoveAttribute(XElement xSummaryInformation, string attributeName)
1295 {
1296 var xAttribute = xSummaryInformation.Attribute(attributeName);
1297 xAttribute?.Remove();
1298 }
1299
1300 private void ConvertPropertyRefElement(XElement element)
1301 {
1302 var newElementName = String.Empty;
1303
1304 var id = element.Attribute("Id");
1305 switch (id?.Value)
1306 {
1307 case "WIX_SUITE_BACKOFFICE":
1308 case "WIX_SUITE_BLADE":
1309 case "WIX_SUITE_COMMUNICATIONS":
1310 case "WIX_SUITE_COMPUTE_SERVER":
1311 case "WIX_SUITE_DATACENTER":
1312 case "WIX_SUITE_EMBEDDED_RESTRICTED":
1313 case "WIX_SUITE_EMBEDDEDNT":
1314 case "WIX_SUITE_ENTERPRISE":
1315 case "WIX_SUITE_MEDIACENTER":
1316 case "WIX_SUITE_PERSONAL":
1317 case "WIX_SUITE_SECURITY_APPLIANCE":
1318 case "WIX_SUITE_SERVERR2":
1319 case "WIX_SUITE_SINGLEUSERTS":
1320 case "WIX_SUITE_SMALLBUSINESS":
1321 case "WIX_SUITE_SMALLBUSINESS_RESTRICTED":
1322 case "WIX_SUITE_STARTER":
1323 case "WIX_SUITE_STORAGE_SERVER":
1324 case "WIX_SUITE_TABLETPC":
1325 case "WIX_SUITE_TERMINAL":
1326 case "WIX_SUITE_WH_SERVER":
1327 newElementName = "QueryWindowsSuiteInfo";
1328 break;
1329 case "WIX_DIR_ADMINTOOLS":
1330 case "WIX_DIR_ALTSTARTUP":
1331 case "WIX_DIR_CDBURN_AREA":
1332 case "WIX_DIR_COMMON_ADMINTOOLS":
1333 case "WIX_DIR_COMMON_ALTSTARTUP":
1334 case "WIX_DIR_COMMON_DOCUMENTS":
1335 case "WIX_DIR_COMMON_FAVORITES":
1336 case "WIX_DIR_COMMON_MUSIC":
1337 case "WIX_DIR_COMMON_PICTURES":
1338 case "WIX_DIR_COMMON_VIDEO":
1339 case "WIX_DIR_COOKIES":
1340 case "WIX_DIR_DESKTOP":
1341 case "WIX_DIR_HISTORY":
1342 case "WIX_DIR_INTERNET_CACHE":
1343 case "WIX_DIR_MYMUSIC":
1344 case "WIX_DIR_MYPICTURES":
1345 case "WIX_DIR_MYVIDEO":
1346 case "WIX_DIR_NETHOOD":
1347 case "WIX_DIR_PERSONAL":
1348 case "WIX_DIR_PRINTHOOD":
1349 case "WIX_DIR_PROFILE":
1350 case "WIX_DIR_RECENT":
1351 case "WIX_DIR_RESOURCES":
1352 newElementName = "QueryWindowsDirectories";
1353 break;
1354 case "WIX_DWM_COMPOSITION_ENABLED":
1355 case "WIX_WDDM_DRIVER_PRESENT":
1356 newElementName = "QueryWindowsDriverInfo";
1357 break;
1358 case "WIX_ACCOUNT_LOCALSYSTEM":
1359 case "WIX_ACCOUNT_LOCALSERVICE":
1360 case "WIX_ACCOUNT_NETWORKSERVICE":
1361 case "WIX_ACCOUNT_ADMINISTRATORS":
1362 case "WIX_ACCOUNT_USERS":
1363 case "WIX_ACCOUNT_GUESTS":
1364 case "WIX_ACCOUNT_PERFLOGUSERS":
1365 case "WIX_ACCOUNT_PERFLOGUSERS_NODOMAIN":
1366 newElementName = "QueryWindowsWellKnownSIDs";
1367 break;
1368 }
1369
1370 if (!String.IsNullOrEmpty(newElementName)
1371 && this.OnError(ConverterTestType.UtilReferencesReplaced, element, "Custom action and property reference {0} to WixUtilExtension have been replaced with strongly-typed elements.", id))
1372 {
1373 element.AddAfterSelf(new XElement(WixUtilNamespace + newElementName));
1374 element.Remove();
1375 }
1376 }
1377
1378 private void ConvertCustomActionRefElement(XElement element)
1379 {
1380 var newElementName = String.Empty;
1381
1382 var id = element.Attribute("Id");
1383 switch (id?.Value)
1384 {
1385 case "WixBroadcastSettingChange":
1386 case "WixBroadcastEnvironmentChange":
1387 case "WixCheckRebootRequired":
1388 case "WixExitEarlyWithSuccess":
1389 case "WixFailWhenDeferred":
1390 case "WixWaitForEvent":
1391 case "WixWaitForEventDeferred":
1392 newElementName = id?.Value.Substring(3); // strip leading Wix
1393 break;
1394 }
1395
1396 if (!String.IsNullOrEmpty(newElementName)
1397 && this.OnError(ConverterTestType.UtilReferencesReplaced, element, "Custom action and property reference {0} to WixUtilExtension have been replaced with strongly-typed elements.", id))
1398 {
1399 element.AddAfterSelf(new XElement(WixUtilNamespace + newElementName));
1400 element.Remove();
1401 }
1402 }
1403
1404 private void ConvertPublishElement(XElement element)
1405 {
1406 this.ConvertInnerTextToAttribute(element, "Condition");
1407
1408 var xCondition = element.Attribute("Condition");
1409 if (xCondition?.Value == "1" &&
1410 this.OnError(ConverterTestType.PublishConditionOneUnnecessary, element, "Adding Condition='1' on {0} elements is no longer necessary. Remove the Condition attribute.", xCondition.Name.LocalName))
1411 {
1412 xCondition.Remove();
1413 }
1414 }
1415
1416 private void ConvertMultiStringValueElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1417
1418 private void ConvertRegistryKeyElement(XElement element)
1419 {
1420 var xAction = element.Attribute("Action");
1421
1422 if (xAction != null
1423 && this.OnError(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'."))
1424 {
1425 switch (xAction?.Value)
1426 {
1427 case "create":
1428 element.SetAttributeValue("ForceCreateOnInstall", "yes");
1429 break;
1430 case "createAndRemoveOnUninstall":
1431 element.SetAttributeValue("ForceCreateOnInstall", "yes");
1432 element.SetAttributeValue("ForceDeleteOnUninstall", "yes");
1433 break;
1434 }
1435
1436 xAction.Remove();
1437 }
1438 }
1439
1440 private void ConvertRemotePayloadElement(XElement element)
1441 {
1442 var xParent = element.Parent;
1443
1444 if (xParent.Name == ExePackageElementName &&
1445 this.OnError(ConverterTestType.RemotePayloadRenamed, element, "The RemotePayload element has been renamed. Use the 'ExePackagePayload' instead."))
1446 {
1447 element.Name = ExePackagePayloadElementName;
1448 }
1449 else if (xParent.Name == MsuPackageElementName &&
1450 this.OnError(ConverterTestType.RemotePayloadRenamed, element, "The RemotePayload element has been renamed. Use the 'MsuPackagePayload' instead."))
1451 {
1452 element.Name = MsuPackagePayloadElementName;
1453 }
1454
1455 var xName = xParent.Attribute("Name");
1456 if (xName != null &&
1457 this.OnError(ConverterTestType.NameAttributeMovedToRemotePayload, xParent, "The Name attribute must be specified on the child XxxPackagePayload element when using a remote payload."))
1458 {
1459 element.SetAttributeValue("Name", xName.Value);
1460 xName.Remove();
1461 }
1462
1463 var xDownloadUrl = xParent.Attribute("DownloadUrl");
1464 if (xDownloadUrl != null &&
1465 this.OnError(ConverterTestType.DownloadUrlAttributeMovedToRemotePayload, xParent, "The DownloadUrl attribute must be specified on the child XxxPackagePayload element when using a remote payload."))
1466 {
1467 element.SetAttributeValue("DownloadUrl", xDownloadUrl.Value);
1468 xDownloadUrl.Remove();
1469 }
1470
1471 var xCompressed = xParent.Attribute("Compressed");
1472 if (xCompressed != null &&
1473 this.OnError(ConverterTestType.CompressedAttributeUnnecessaryForRemotePayload, xParent, "The Compressed attribute should not be specified when using a remote payload."))
1474 {
1475 xCompressed.Remove();
1476 }
1477
1478 this.OnError(ConverterTestType.BurnHashAlgorithmChanged, element, "The hash algorithm for bundles changed from SHA1 to SHA512.");
1479
1480 this.RemoveAttributeIfPresent(element, "CertificatePublicKey", ConverterTestType.BundleSignatureValidationObsolete, "The {0} element contains obsolete '{1}' attribute. Signature validation is no longer supported. The attribute will be removed.");
1481 this.RemoveAttributeIfPresent(element, "CertificateThumbprint", ConverterTestType.BundleSignatureValidationObsolete, "The {0} element contains obsolete '{1}' attribute. Signature validation is no longer supported. The attribute will be removed.");
1482 }
1483
1484 private void ConvertRegistrySearchElement(XElement element)
1485 {
1486 this.RenameWin64ToBitness(element);
1487 }
1488
1489 private void ConvertRequiredPrivilegeElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Name");
1490
1491 private void ConvertDataElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1492
1493 private void ConvertSequenceElement(XElement element)
1494 {
1495 foreach (var child in element.Elements())
1496 {
1497 this.ConvertInnerTextToAttribute(child, "Condition");
1498 }
1499 }
1500
1501 private void ConvertServiceArgumentElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1502
1503 private void ConvertSetDirectoryElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition");
1504
1505 private void ConvertSetPropertyElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition");
1506
1507 private void ConvertShortcutPropertyElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1508
1509 private void ConvertProvidesElement(XElement element)
1510 {
1511 if (this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The Provides element has been integrated into the WiX v4 namespace. Remove the namespace."))
1512 {
1513 element.Name = ProvidesElementName;
1514 }
1515
1516 if (element.Parent.Name == ComponentElementName &&
1517 this.OnError(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."))
1518 {
1519 element.Add(new XAttribute(DependencyCheckAttributeName, "yes"));
1520 }
1521 }
1522
1523 private void ConvertRequiresElement(XElement element)
1524 {
1525 if (this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The Requires element has been integrated into the WiX v4 namespace. Remove the namespace."))
1526 {
1527 element.Name = RequiresElementName;
1528 }
1529
1530 if (element.Parent.Name == ProvidesElementName &&
1531 element.Parent.Parent?.Name == ComponentElementName &&
1532 this.OnError(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."))
1533 {
1534 element.Add(new XAttribute(DependencyEnforceAttributeName, "yes"));
1535 }
1536 }
1537
1538 private void ConvertRequiresRefElement(XElement element)
1539 {
1540 if (this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The RequiresRef element has been integrated into the WiX v4 namespace. Remove the namespace."))
1541 {
1542 element.Name = RequiresRefElementName;
1543 }
1544
1545 if (element.Parent.Name == ProvidesElementName &&
1546 element.Parent.Parent?.Name == ComponentElementName &&
1547 this.OnError(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."))
1548 {
1549 element.Add(new XAttribute(DependencyEnforceAttributeName, "yes"));
1550 }
1551 }
1552
1553 private void ConvertSuppressSignatureValidation(XElement element)
1554 {
1555 var suppressSignatureValidation = element.Attribute("SuppressSignatureValidation");
1556
1557 if (null != suppressSignatureValidation
1558 && this.OnError(ConverterTestType.BundleSignatureValidationObsolete, element, "The chain package element contains obsolete '{0}' attribute. Signature validation is no longer supported. The attribute will be removed.", suppressSignatureValidation.Name))
1559 {
1560 suppressSignatureValidation.Remove();
1561 }
1562 }
1563
1564 private void ConvertTagElement(XElement element)
1565 {
1566 if (this.OnError(ConverterTestType.TagElementRenamed, element, "The Tag element has been renamed. Use the 'SoftwareTag' element instead."))
1567 {
1568 element.Name = SoftwareTagElementName;
1569 }
1570
1571 this.RemoveAttributeIfPresent(element, "Licensed", ConverterTestType.SoftwareTagLicensedObsolete, "The {0} element contains obsolete '{1}' attribute. The attribute will be removed.");
1572 this.RemoveAttributeIfPresent(element, "Type", ConverterTestType.SoftwareTagLicensedObsolete, "The {0} element contains obsolete '{1}' attribute. The attribute will be removed.");
1573 this.RenameWin64ToBitness(element);
1574 }
1575
1576 private void ConvertTagRefElement(XElement element)
1577 {
1578 if (this.OnError(ConverterTestType.TagRefElementRenamed, element, "The TagRef element has been renamed. Use the 'SoftwareTagRef' element instead."))
1579 {
1580 element.Name = SoftwareTagRefElementName;
1581 }
1582 }
1583
1584 private void ConvertTextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1585
1586 private void ConvertUITextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1587
1588 private void ConvertWindowsInstallerPackageElement(XElement element)
1589 {
1590 this.ConvertSuppressSignatureValidation(element);
1591
1592 if (null != element.Attribute("DisplayInternalUI"))
1593 {
1594 this.OnError(ConverterTestType.DisplayInternalUiNotConvertable, element, "The DisplayInternalUI functionality has fundamentally changed and requires BootstrapperApplication support.");
1595 }
1596 }
1597
1598 private void ConvertVerbElement(XElement element)
1599 {
1600 if (null != element.Attribute("Target"))
1601 {
1602 this.OnError(ConverterTestType.VerbTargetNotConvertable, element, "The Verb/@Target attribute has been replaced with typed @TargetFile and @TargetProperty attributes.");
1603 }
1604 }
1605
1606 private void ConvertCustomActionElement(XElement xCustomAction)
1607 {
1608 var xBinaryKey = xCustomAction.Attribute("BinaryKey");
1609 if (xBinaryKey != null && this.OnError(ConverterTestType.CustomActionKeysAreNowRefs, xCustomAction, "The CustomAction attributes have been renamed from BinaryKey and FileKey to BinaryRef and FileRef."))
1610 {
1611 xCustomAction.SetAttributeValue("BinaryRef", xBinaryKey.Value);
1612 xBinaryKey.Remove();
1613 xBinaryKey = xCustomAction.Attribute("BinaryRef");
1614 }
1615
1616 var xFileKey = xCustomAction.Attribute("FileKey");
1617 if (xFileKey != null && this.OnError(ConverterTestType.CustomActionKeysAreNowRefs, xCustomAction, "The CustomAction attributes have been renamed from BinaryKey and FileKey to BinaryRef and FileRef."))
1618 {
1619 xCustomAction.SetAttributeValue("FileRef", xFileKey.Value);
1620 xFileKey.Remove();
1621 }
1622
1623 if (xBinaryKey?.Value == "WixCA" || xBinaryKey?.Value == "UtilCA")
1624 {
1625 if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA custom action DLL Binary table id has been renamed. Use the id 'Wix4UtilCA_X86' instead."))
1626 {
1627 xBinaryKey.Value = "Wix4UtilCA_X86";
1628 }
1629 }
1630
1631 if (xBinaryKey?.Value == "WixCA_x64" || xBinaryKey?.Value == "UtilCA_x64")
1632 {
1633 if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA_x64 custom action DLL Binary table id has been renamed. Use the id 'Wix4UtilCA_X64' instead."))
1634 {
1635 xBinaryKey.Value = "Wix4UtilCA_X64";
1636 }
1637 }
1638
1639 var xDllEntry = xCustomAction.Attribute("DllEntry");
1640
1641 if (xDllEntry?.Value == "CAQuietExec" || xDllEntry?.Value == "CAQuietExec64")
1642 {
1643 if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The CAQuietExec and CAQuietExec64 custom action ids have been renamed. Use the ids 'WixQuietExec' and 'WixQuietExec64' instead."))
1644 {
1645 xDllEntry.Value = xDllEntry.Value.Replace("CAQuietExec", "WixQuietExec");
1646 }
1647 }
1648
1649 var xProperty = xCustomAction.Attribute("Property");
1650
1651 if (xProperty?.Value == "QtExecCmdLine" || xProperty?.Value == "QtExec64CmdLine")
1652 {
1653 if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The QtExecCmdLine and QtExec64CmdLine property ids have been renamed. Use the ids 'WixQuietExecCmdLine' and 'WixQuietExec64CmdLine' instead."))
1654 {
1655 xProperty.Value = xProperty.Value.Replace("QtExec", "WixQuietExec");
1656 }
1657 }
1658
1659 var xScript = xCustomAction.Attribute("Script");
1660
1661 if (xScript != null && TryGetInnerText(xCustomAction, out var scriptText))
1662 {
1663 if (this.OnError(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))
1664 {
1665 var scriptFolder = Path.GetDirectoryName(this.SourceFile) ?? String.Empty;
1666 var id = xCustomAction.Attribute("Id")?.Value ?? Guid.NewGuid().ToString("N");
1667 var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt";
1668
1669 var scriptFile = Path.Combine(scriptFolder, id + ext);
1670 File.WriteAllText(scriptFile, scriptText);
1671
1672 RemoveChildren(xCustomAction);
1673 xCustomAction.Add(new XAttribute("ScriptSourceFile", scriptFile));
1674 }
1675 }
1676 }
1677
1678 private void ConvertVariableElement(XElement xVariable)
1679 {
1680 var xType = xVariable.Attribute("Type");
1681 var xValue = xVariable.Attribute("Value");
1682 if (this.SourceVersion < 4)
1683 {
1684 if (xType == null)
1685 {
1686 if (WasImplicitlyStringTyped(xValue?.Value) &&
1687 this.OnError(ConverterTestType.AssignVariableTypeFormatted, xVariable, "The \"string\" variable type now denotes a literal string. Use \"formatted\" to keep the previous behavior."))
1688 {
1689 xVariable.Add(new XAttribute("Type", "formatted"));
1690 }
1691 }
1692 else if (xType.Value == "string" &&
1693 this.OnError(ConverterTestType.AssignVariableTypeFormatted, xVariable, "The \"string\" variable type now denotes a literal string. Use \"formatted\" to keep the previous behavior."))
1694 {
1695 xType.Value = "formatted";
1696 }
1697 }
1698 }
1699
1700 private void ConvertPropertyElement(XElement xProperty)
1701 {
1702 var xId = xProperty.Attribute("Id");
1703
1704 if (xId.Value == "QtExecCmdTimeout")
1705 {
1706 this.OnError(ConverterTestType.QtExecCmdTimeoutAmbiguous, xProperty, "QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout.");
1707 }
1708
1709 this.ConvertInnerTextToAttribute(xProperty, "Value");
1710 }
1711
1712 private void ConvertUtilCloseApplicationElementName(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition");
1713
1714 private void ConvertUtilPermissionExElement(XElement element)
1715 {
1716 if (this.SourceVersion < 4 && null == element.Attribute("Inheritable"))
1717 {
1718 var inheritable = element.Parent.Name == CreateFolderElementName;
1719 if (!inheritable)
1720 {
1721 if (this.OnError(ConverterTestType.AssignPermissionExInheritable, element, "The PermissionEx Inheritable attribute is being set to 'no' to ensure it remains the same as the v3 default."))
1722 {
1723 element.Add(new XAttribute("Inheritable", "no"));
1724 }
1725 }
1726 }
1727 }
1728
1729 private void ConvertUtilRegistrySearchElement(XElement element)
1730 {
1731 this.RenameWin64ToBitness(element);
1732
1733 if (this.SourceVersion < 4)
1734 {
1735 var result = element.Attribute("Result")?.Value;
1736 if (result == null || result == "value")
1737 {
1738 this.OnError(ConverterTestType.UtilRegistryValueSearchBehaviorChange, element, "Breaking change: util:RegistrySearch for a value no longer clears the variable when the key or value is missing.");
1739 }
1740 }
1741 }
1742
1743 private void ConvertUtilXmlConfigElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1744
1745 /// <summary>
1746 /// Converts a Wix element.
1747 /// </summary>
1748 /// <param name="element">The Wix element to convert.</param>
1749 /// <returns>The converted element.</returns>
1750 private void ConvertElementWithoutNamespace(XElement element)
1751 {
1752 if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName))
1753 {
1754 element.Name = WixNamespace.GetName(element.Name.LocalName);
1755
1756 element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace.
1757
1758 foreach (var elementWithoutNamespace in element.DescendantsAndSelf().Where(e => XNamespace.None == e.Name.Namespace))
1759 {
1760 elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName);
1761 }
1762 }
1763 }
1764
1765 private void ConvertInnerTextToAttribute(XElement element, string attributeName)
1766 {
1767 if (TryGetInnerText(element, out var text) &&
1768 this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}' attribute instead.", element.Name.LocalName, attributeName))
1769 {
1770 element.Add(new XAttribute(attributeName, text));
1771 RemoveChildren(element);
1772 }
1773 }
1774
1775 void RemoveAttributeIfPresent(XElement element, string attributeName, ConverterTestType type, string format)
1776 {
1777 var xAttribute = element.Attribute(attributeName);
1778 if (null != xAttribute && this.OnError(type, element, format, element.Name.LocalName, xAttribute.Name))
1779 {
1780 xAttribute.Remove();
1781 }
1782 }
1783
1784 private void RenameWin64ToBitness(XElement element)
1785 {
1786 var win64 = element.Attribute("Win64");
1787 if (win64 != null && this.OnError(ConverterTestType.Win64AttributeRenamed, element, "The {0} element's Win64 attribute has been renamed. Use the Bitness attribute instead.", element.Name.LocalName))
1788 {
1789 var value = this.UpdateWin64ValueToBitnessValue(win64);
1790 element.Add(new XAttribute("Bitness", value));
1791 win64.Remove();
1792 }
1793 }
1794
1795 private string UpdateWin64ValueToBitnessValue(XAttribute xWin64Attribute)
1796 {
1797 var value = xWin64Attribute.Value ?? String.Empty;
1798 switch (value)
1799 {
1800 case "yes":
1801 return "always64";
1802 case "no":
1803 return "always32";
1804 default:
1805 this.OnError(ConverterTestType.Win64AttributeRenameCannotBeAutomatic, xWin64Attribute, "Breaking change: The Win64 attribute's value '{0}' cannot be converted automatically to the new Bitness attribute.", value);
1806 return value;
1807 }
1808 }
1809
1810 private IEnumerable<ConverterTestType> YieldConverterTypes(IEnumerable<string> types)
1811 {
1812 if (null != types)
1813 {
1814 foreach (var type in types)
1815 {
1816 if (Enum.TryParse<ConverterTestType>(type, true, out var itt))
1817 {
1818 yield return itt;
1819 }
1820 else // not a known ConverterTestType
1821 {
1822 this.OnError(ConverterTestType.ConverterTestTypeUnknown, null, "Unknown error type: '{0}'.", type);
1823 }
1824 }
1825 }
1826 }
1827
1828 private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable<XElement> elements, Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces)
1829 {
1830 foreach (var element in elements)
1831 {
1832 if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out var ns))
1833 {
1834 element.Name = ns.GetName(element.Name.LocalName);
1835 }
1836
1837 // Remove all the attributes and add them back to with their namespace updated (as necessary).
1838 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
1839 element.RemoveAttributes();
1840
1841 foreach (var attribute in attributes)
1842 {
1843 var convertedAttribute = attribute;
1844
1845 if (attribute.IsNamespaceDeclaration)
1846 {
1847 if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns))
1848 {
1849 if (ns == XNamespace.None)
1850 {
1851 continue;
1852 }
1853
1854 convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName);
1855 }
1856 }
1857 else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns))
1858 {
1859 convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value);
1860 }
1861
1862 element.Add(convertedAttribute);
1863 }
1864 }
1865 }
1866
1867 /// <summary>
1868 /// Determine if the whitespace preceding a node is appropriate for its depth level.
1869 /// </summary>
1870 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
1871 /// <param name="level">The depth level that should match this whitespace.</param>
1872 /// <param name="whitespace">The whitespace to validate.</param>
1873 /// <returns>true if the whitespace is legal; false otherwise.</returns>
1874 private static bool LeadingWhitespaceValid(int indentationAmount, int level, string whitespace)
1875 {
1876 // Strip off leading newlines; there can be an arbitrary number of these.
1877 whitespace = whitespace.TrimStart(XDocumentNewLine);
1878
1879 var indentation = new string(' ', level * indentationAmount);
1880
1881 return whitespace == indentation;
1882 }
1883
1884 /// <summary>
1885 /// Fix the whitespace in a whitespace node.
1886 /// </summary>
1887 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
1888 /// <param name="level">The depth level of the desired whitespace.</param>
1889 /// <param name="whitespace">The whitespace node to fix.</param>
1890 private static void FixupWhitespace(int indentationAmount, int level, XText whitespace)
1891 {
1892 var value = new StringBuilder(whitespace.Value.Length);
1893
1894 // Keep any previous preceeding new lines.
1895 var newlines = whitespace.Value.TakeWhile(c => c == XDocumentNewLine).Count();
1896
1897 // Ensure there is always at least one new line before the indentation.
1898 value.Append(XDocumentNewLine, newlines == 0 ? 1 : newlines);
1899
1900 whitespace.Value = value.Append(' ', level * indentationAmount).ToString();
1901 }
1902
1903 /// <summary>
1904 /// Removes unused namespaces from the element and its children.
1905 /// </summary>
1906 /// <param name="root">Root element to start at.</param>
1907 private void RemoveUnusedNamespaces(XElement root)
1908 {
1909 var declarations = new List<XAttribute>();
1910 var namespaces = new HashSet<string>();
1911
1912 VisitElement(root, x =>
1913 {
1914 if (x is XAttribute a && a.IsNamespaceDeclaration)
1915 {
1916 declarations.Add(a);
1917 namespaces.Add(a.Value);
1918 }
1919 return true;
1920 });
1921
1922 foreach (var ns in namespaces.ToList())
1923 {
1924 VisitElement(root, x =>
1925 {
1926 if ((x is XElement e && e.Name.Namespace == ns) ||
1927 (x is XAttribute a && !a.IsNamespaceDeclaration && a.Name.Namespace == ns))
1928 {
1929 namespaces.Remove(ns);
1930 return false;
1931 }
1932
1933 return true;
1934 });
1935 }
1936
1937 foreach (var declaration in declarations)
1938 {
1939 if (namespaces.Contains(declaration.Value) &&
1940 this.OnError(ConverterTestType.RemoveUnusedNamespaces, declaration, "The namespace '{0}' is not used. Remove unused namespaces.", declaration.Value))
1941 {
1942 declaration.Remove();
1943 }
1944 }
1945 }
1946
1947 /// <summary>
1948 /// Output an error message to the console.
1949 /// </summary>
1950 /// <param name="converterTestType">The type of converter test.</param>
1951 /// <param name="node">The node that caused the error.</param>
1952 /// <param name="message">Detailed error message.</param>
1953 /// <param name="args">Additional formatted string arguments.</param>
1954 /// <returns>Returns true indicating that action should be taken on this error, and false if it should be ignored.</returns>
1955 private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args)
1956 {
1957 // Ignore the error if explicitly ignored or outside the range of the current operation.
1958 if (this.IgnoreErrors.Contains(converterTestType) ||
1959 (this.Operation == ConvertOperation.Convert && converterTestType < ConverterTestType.EndIgnoreInConvert) ||
1960 (this.Operation == ConvertOperation.Format && converterTestType > ConverterTestType.BeginIgnoreInFormat))
1961 {
1962 return false;
1963 }
1964
1965 // Increase the error count.
1966 this.Errors++;
1967
1968 var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wix.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber);
1969 var warning = this.ErrorsAsWarnings.Contains(converterTestType);
1970 var display = String.Format(CultureInfo.CurrentCulture, message, args);
1971
1972 var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString());
1973
1974 this.Messaging.Write(msg);
1975
1976 return true;
1977 }
1978
1979 /// <summary>
1980 /// Return an identifier based on passed file/directory name
1981 /// </summary>
1982 /// <param name="name">File/directory name to generate identifer from</param>
1983 /// <returns>A version of the name that is a legal identifier.</returns>
1984 /// <remarks>This is duplicated from WiX's Common class.</remarks>
1985 private static string GetIdentifierFromName(string name)
1986 {
1987 var result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
1988
1989 // MSI identifiers must begin with an alphabetic character or an
1990 // underscore. Prefix all other values with an underscore.
1991 if (AddPrefix.IsMatch(name))
1992 {
1993 result = String.Concat("_", result);
1994 }
1995
1996 return result;
1997 }
1998
1999 private static string LowercaseFirstChar(string value)
2000 {
2001 if (!String.IsNullOrEmpty(value))
2002 {
2003 var c = Char.ToLowerInvariant(value[0]);
2004 if (c != value[0])
2005 {
2006 var remainder = value.Length > 1 ? value.Substring(1) : String.Empty;
2007 return c + remainder;
2008 }
2009 }
2010
2011 return value;
2012 }
2013
2014 private static string UppercaseFirstChar(string value)
2015 {
2016 if (!String.IsNullOrEmpty(value))
2017 {
2018 var c = Char.ToUpperInvariant(value[0]);
2019 if (c != value[0])
2020 {
2021 var remainder = value.Length > 1 ? value.Substring(1) : String.Empty;
2022 return c + remainder;
2023 }
2024 }
2025
2026 return value;
2027 }
2028
2029 private static bool TryGetInnerText(XElement element, out string value)
2030 {
2031 value = null;
2032
2033 var nodes = element.Nodes();
2034
2035 if (nodes.All(e => e.NodeType == XmlNodeType.Text || e.NodeType == XmlNodeType.CDATA))
2036 {
2037 value = String.Join(String.Empty, nodes.Cast<XText>().Select(TrimTextValue));
2038 }
2039
2040 return !String.IsNullOrEmpty(value);
2041 }
2042
2043 private static bool IsTextNode(XNode node, out XText text)
2044 {
2045 text = null;
2046
2047 if (node.NodeType == XmlNodeType.Text || node.NodeType == XmlNodeType.CDATA)
2048 {
2049 text = (XText)node;
2050 }
2051
2052 return text != null;
2053 }
2054
2055 private static void TrimLeadingText(XDocument document)
2056 {
2057 while (IsTextNode(document.Nodes().FirstOrDefault(), out var text))
2058 {
2059 text.Remove();
2060 }
2061 }
2062
2063 private static string TrimTextValue(XText text)
2064 {
2065 var value = text.Value;
2066
2067 if (String.IsNullOrEmpty(value))
2068 {
2069 return String.Empty;
2070 }
2071 else if (text.NodeType == XmlNodeType.CDATA && String.IsNullOrWhiteSpace(value))
2072 {
2073 return " ";
2074 }
2075
2076 return value.Trim();
2077 }
2078
2079 private static void RemoveChildren(XElement element)
2080 {
2081 var nodes = element.Nodes().ToList();
2082 foreach (var node in nodes)
2083 {
2084 node.Remove();
2085 }
2086 }
2087
2088 private static bool VisitElement(XElement element, Func<XObject, bool> visitor)
2089 {
2090 if (!visitor(element))
2091 {
2092 return false;
2093 }
2094
2095 if (!element.Attributes().All(a => visitor(a)))
2096 {
2097 return false;
2098 }
2099
2100 return element.Elements().All(e => VisitElement(e, visitor));
2101 }
2102
2103 private static bool WasImplicitlyStringTyped(string value)
2104 {
2105 if (value == null)
2106 {
2107 return false;
2108 }
2109 else if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
2110 {
2111 if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _))
2112 {
2113 return false;
2114 }
2115 else if (Version.TryParse(value.Substring(1), out var _))
2116 {
2117 return false;
2118 }
2119 }
2120 else if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _))
2121 {
2122 return false;
2123 }
2124
2125 return true;
2126 }
2127
2128 /// <summary>
2129 /// Converter test types. These are used to condition error messages down to warnings.
2130 /// </summary>
2131 private enum ConverterTestType
2132 {
2133 /// <summary>
2134 /// Internal-only: displayed when a string cannot be converted to an ConverterTestType.
2135 /// </summary>
2136 ConverterTestTypeUnknown,
2137
2138 /// <summary>
2139 /// Displayed when an XML loading exception has occurred.
2140 /// </summary>
2141 XmlException,
2142
2143 /// <summary>
2144 /// Displayed when the whitespace preceding a node is wrong.
2145 /// </summary>
2146 WhitespacePrecedingNodeWrong,
2147
2148 /// <summary>
2149 /// Displayed when the whitespace preceding an end element is wrong.
2150 /// </summary>
2151 WhitespacePrecedingEndElementWrong,
2152
2153 /// Before this point, ignore errors on convert operation
2154 EndIgnoreInConvert,
2155
2156 /// <summary>
2157 /// Displayed when the XML declaration is present in the source file.
2158 /// </summary>
2159 DeclarationPresent,
2160
2161 /// <summary>
2162 /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file.
2163 /// </summary>
2164 UnauthorizedAccessException,
2165
2166 /// After this point, ignore errors on format operation
2167 BeginIgnoreInFormat,
2168
2169 /// <summary>
2170 /// Displayed when the xmlns attribute is missing from the document element.
2171 /// </summary>
2172 XmlnsMissing,
2173
2174 /// <summary>
2175 /// Displayed when the xmlns attribute on the document element is wrong.
2176 /// </summary>
2177 XmlnsValueWrong,
2178
2179 /// <summary>
2180 /// Displayed when inner text contains a deprecated $(loc.xxx) reference.
2181 /// </summary>
2182 DeprecatedLocalizationVariablePrefixInTextValue,
2183
2184 /// <summary>
2185 /// Displayed when an attribute value contains a deprecated $(loc.xxx) reference.
2186 /// </summary>
2187 DeprecatedLocalizationVariablePrefixInAttributeValue,
2188
2189 /// <summary>
2190 /// Assign an identifier to a File element when on Id attribute is specified.
2191 /// </summary>
2192 AssignAnonymousFileId,
2193
2194 /// <summary>
2195 /// SuppressSignatureValidation attribute is obsolete and corresponding functionality removed.
2196 /// </summary>
2197 BundleSignatureValidationObsolete,
2198
2199 /// <summary>
2200 /// WixCA Binary/@Id has been renamed to UtilCA.
2201 /// </summary>
2202 WixCABinaryIdRenamed,
2203
2204 /// <summary>
2205 /// QtExec custom actions have been renamed.
2206 /// </summary>
2207 QuietExecCustomActionsRenamed,
2208
2209 /// <summary>
2210 /// QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout.
2211 /// </summary>
2212 QtExecCmdTimeoutAmbiguous,
2213
2214 /// <summary>
2215 /// Directory/@ShortName may only be specified with Directory/@Name.
2216 /// </summary>
2217 AssignDirectoryNameFromShortName,
2218
2219 /// <summary>
2220 /// BootstrapperApplicationData attribute is deprecated and replaced with Unreal for MSI. Use BundleCustomData element for Bundles.
2221 /// </summary>
2222 BootstrapperApplicationDataDeprecated,
2223
2224 /// <summary>
2225 /// Inheritable is new and is now defaulted to 'yes' which is a change in behavior for all but children of CreateFolder.
2226 /// </summary>
2227 AssignPermissionExInheritable,
2228
2229 /// <summary>
2230 /// Column element's Category attribute is camel-case.
2231 /// </summary>
2232 ColumnCategoryCamelCase,
2233
2234 /// <summary>
2235 /// Column element's Modularize attribute is camel-case.
2236 /// </summary>
2237 ColumnModularizeCamelCase,
2238
2239 /// <summary>
2240 /// Inner text value should move to an attribute.
2241 /// </summary>
2242 InnerTextDeprecated,
2243
2244 /// <summary>
2245 /// Explicit auto-GUID unnecessary.
2246 /// </summary>
2247 AutoGuidUnnecessary,
2248
2249 /// <summary>
2250 /// The Feature Absent attribute renamed to AllowAbsent.
2251 /// </summary>
2252 FeatureAbsentAttributeReplaced,
2253
2254 /// <summary>
2255 /// The Feature AllowAdvertise attribute value deprecated.
2256 /// </summary>
2257 FeatureAllowAdvertiseValueDeprecated,
2258
2259 /// <summary>
2260 /// The Condition='1' attribute is unnecessary on Publish elements.
2261 /// </summary>
2262 PublishConditionOneUnnecessary,
2263
2264 /// <summary>
2265 /// DpiAwareness is new and is defaulted to 'perMonitorV2' which is a change in behavior.
2266 /// </summary>
2267 AssignBootstrapperApplicationDpiAwareness,
2268
2269 /// <summary>
2270 /// The string variable type was previously treated as formatted.
2271 /// </summary>
2272 AssignVariableTypeFormatted,
2273
2274 /// <summary>
2275 /// The CustomAction attributes have been renamed from BinaryKey and FileKey to BinaryRef and FileRef.
2276 /// </summary>
2277 CustomActionKeysAreNowRefs,
2278
2279 /// <summary>
2280 /// The Product and Package elements have been renamed and reorganized.
2281 /// </summary>
2282 ProductAndPackageRenamed,
2283
2284 /// <summary>
2285 /// The Module and Package elements have been renamed and reorganized.
2286 /// </summary>
2287 ModuleAndPackageRenamed,
2288
2289 /// <summary>
2290 /// A MediaTemplate with no attributes set is now provided by default.
2291 /// </summary>
2292 DefaultMediaTemplate,
2293
2294 /// <summary>
2295 /// util:RegistrySearch has breaking change when value is missing.
2296 /// </summary>
2297 UtilRegistryValueSearchBehaviorChange,
2298
2299 /// <summary>
2300 /// DisplayInternalUI can't be converted.
2301 /// </summary>
2302 DisplayInternalUiNotConvertable,
2303
2304 /// <summary>
2305 /// InstallerVersion has breaking change when missing.
2306 /// </summary>
2307 InstallerVersionBehaviorChange,
2308
2309 /// <summary>
2310 /// Verb/@Target can't be converted.
2311 /// </summary>
2312 VerbTargetNotConvertable,
2313
2314 /// <summary>
2315 /// The bootstrapper application dll is now specified in its own element.
2316 /// </summary>
2317 BootstrapperApplicationDll,
2318
2319 /// <summary>
2320 /// The new bootstrapper application dll element is required.
2321 /// </summary>
2322 BootstrapperApplicationDllRequired,
2323
2324 /// <summary>
2325 /// bal:UseUILanguages is deprecated, 'true' is now the standard behavior.
2326 /// </summary>
2327 BalUseUILanguagesDeprecated,
2328
2329 /// <summary>
2330 /// The custom elements for built-in BAs are now required.
2331 /// </summary>
2332 BalBootstrapperApplicationRefToElement,
2333
2334 /// <summary>
2335 /// The ExePackage elements "XxxCommand" attributes have been renamed to "XxxArguments".
2336 /// </summary>
2337 RenameExePackageCommandToArguments,
2338
2339 /// <summary>
2340 /// The Win64 attribute has been renamed. Use the Bitness attribute instead.
2341 /// </summary>
2342 Win64AttributeRenamed,
2343
2344 /// <summary>
2345 /// Breaking change: The Win64 attribute's value '{0}' cannot be converted automatically to the new Bitness attribute.
2346 /// </summary>
2347 Win64AttributeRenameCannotBeAutomatic,
2348
2349 /// <summary>
2350 /// The Tag element has been renamed. Use the element 'SoftwareTag' name.
2351 /// </summary>
2352 TagElementRenamed,
2353
2354 /// <summary>
2355 /// The Dependency namespace has been incorporated into WiX v4 namespace.
2356 /// </summary>
2357 IntegratedDependencyNamespace,
2358
2359 /// <summary>
2360 /// Remove unused namespaces.
2361 /// </summary>
2362 RemoveUnusedNamespaces,
2363
2364 /// <summary>
2365 /// The Remote element has been renamed. Use the "XxxPackagePayload" element instead.
2366 /// </summary>
2367 RemotePayloadRenamed,
2368
2369 /// <summary>
2370 /// The XxxPackage/@Name attribute must be specified on the child XxxPackagePayload element when using a remote payload.
2371 /// </summary>
2372 NameAttributeMovedToRemotePayload,
2373
2374 /// <summary>
2375 /// The XxxPackage/@Compressed attribute should not be specified when using a remote payload.
2376 /// </summary>
2377 CompressedAttributeUnnecessaryForRemotePayload,
2378
2379 /// <summary>
2380 /// The XxxPackage/@DownloadUrl attribute must be specified on the child XxxPackagePayload element when using a remote payload.
2381 /// </summary>
2382 DownloadUrlAttributeMovedToRemotePayload,
2383
2384 /// <summary>
2385 /// The hash algorithm used for bundles changed from SHA1 to SHA512.
2386 /// </summary>
2387 BurnHashAlgorithmChanged,
2388
2389 /// <summary>
2390 /// CustomTable elements can't always be converted.
2391 /// </summary>
2392 CustomTableNotAlwaysConvertable,
2393
2394 /// <summary>
2395 /// CustomTable elements that don't contain the table definition are now CustomTableRef.
2396 /// </summary>
2397 CustomTableRef,
2398
2399 /// <summary>
2400 /// The RegistryKey element's Action attribute is obsolete.
2401 /// </summary>
2402 RegistryKeyActionObsolete,
2403
2404 /// <summary>
2405 /// The TagRef element has been renamed. Use the element 'SoftwareTagRef' name.
2406 /// </summary>
2407 TagRefElementRenamed,
2408
2409 /// <summary>
2410 /// The SoftwareTag element's Licensed attribute is obsolete.
2411 /// </summary>
2412 SoftwareTagLicensedObsolete,
2413
2414 /// <summary>
2415 /// The SoftwareTag element's Type attribute is obsolete.
2416 /// </summary>
2417 SoftwareTagTypeObsolete,
2418
2419 /// <summary>
2420 /// TARGETDIR directory should not longer be explicitly defined.
2421 /// </summary>
2422 TargetDirDeprecated,
2423
2424 /// <summary>
2425 /// Standard directories should no longer be defined using the Directory element.
2426 /// </summary>
2427 DefiningStandardDirectoryDeprecated,
2428
2429 /// <summary>
2430 /// Naked custom action and property references replaced with WixUtilExtension elements.
2431 /// </summary>
2432 UtilReferencesReplaced,
2433 }
2434 }
2435}
diff --git a/src/wix/WixToolset.Converters/WixToolset.Converters.csproj b/src/wix/WixToolset.Converters/WixToolset.Converters.csproj
new file mode 100644
index 00000000..7dddefa5
--- /dev/null
+++ b/src/wix/WixToolset.Converters/WixToolset.Converters.csproj
@@ -0,0 +1,24 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5
6 <PropertyGroup>
7 <TargetFrameworks>netstandard2.0</TargetFrameworks>
8 <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks>
9 <Description>Converter</Description>
10 <Title>WiX Toolset Converters</Title>
11 <DebugType>embedded</DebugType>
12 <PublishRepositoryUrl>true</PublishRepositoryUrl>
13 <CreateDocumentationFile>true</CreateDocumentationFile>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" />
18 </ItemGroup>
19
20 <ItemGroup>
21 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
22 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
23 </ItemGroup>
24</Project>
diff --git a/src/wix/WixToolset.Converters/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Converters/WixToolsetCoreServiceProviderExtensions.cs
new file mode 100644
index 00000000..084d3b92
--- /dev/null
+++ b/src/wix/WixToolset.Converters/WixToolsetCoreServiceProviderExtensions.cs
@@ -0,0 +1,25 @@
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
3namespace WixToolset.Converters
4{
5 using WixToolset.Extensibility.Services;
6
7 /// <summary>
8 /// Extension methods for adding Converters services.
9 /// </summary>
10 public static class WixToolsetCoreServiceProviderExtensions
11 {
12 /// <summary>
13 /// Adds Converters services.
14 /// </summary>
15 /// <param name="coreProvider"></param>
16 /// <returns></returns>
17 public static IWixToolsetCoreServiceProvider AddConverter(this IWixToolsetCoreServiceProvider coreProvider)
18 {
19 var extensionManager = coreProvider.GetService<IExtensionManager>();
20 extensionManager.Add(typeof(ConverterExtensionFactory).Assembly);
21
22 return coreProvider;
23 }
24 }
25}
diff --git a/src/wix/deps/wix.dll b/src/wix/deps/wix.dll
new file mode 100644
index 00000000..64f70f75
--- /dev/null
+++ b/src/wix/deps/wix.dll
Binary files differ
diff --git a/src/wix/nuget.config b/src/wix/nuget.config
index 022f9240..f985e459 100644
--- a/src/wix/nuget.config
+++ b/src/wix/nuget.config
@@ -10,4 +10,4 @@
10 <add key="wixbuildtools" value="https://ci.appveyor.com/nuget/wixbuildtools" /> 10 <add key="wixbuildtools" value="https://ci.appveyor.com/nuget/wixbuildtools" />
11 <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> 11 <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
12 </packageSources> 12 </packageSources>
13</configuration> \ No newline at end of file 13</configuration>
diff --git a/src/wix/test/WixToolsetTest.Converters.Symbolizer/ConvertSymbolsFixture.cs b/src/wix/test/WixToolsetTest.Converters.Symbolizer/ConvertSymbolsFixture.cs
new file mode 100644
index 00000000..01213524
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters.Symbolizer/ConvertSymbolsFixture.cs
@@ -0,0 +1,606 @@
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
3namespace WixToolsetTest.Converters.Symbolizer
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixBuildTools.TestSupport;
10 using Wix3 = Microsoft.Tools.WindowsInstallerXml;
11 using WixToolset.Converters.Symbolizer;
12 using WixToolset.Data;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Data.Symbols;
15 using Xunit;
16
17 public class ConvertSymbolsFixture
18 {
19 [Fact]
20 public void CanLoadWixoutAndConvertToIntermediate()
21 {
22 var rootFolder = TestData.Get();
23 var dataFolder = TestData.Get(@"TestData\Integration");
24
25 using (var fs = new DisposableFileSystem())
26 {
27 var intermediateFolder = fs.GetFolder();
28
29 var path = Path.Combine(dataFolder, "test.wixout");
30
31 var intermediate = ConvertSymbols.ConvertFile(path);
32
33 Assert.NotNull(intermediate);
34 Assert.Single(intermediate.Sections);
35 Assert.Equal(String.Empty, intermediate.Id);
36
37 // Save and load to guarantee round-tripping support.
38 //
39 var wixiplFile = Path.Combine(intermediateFolder, "test.wixipl");
40 intermediate.Save(wixiplFile);
41
42 intermediate = Intermediate.Load(wixiplFile);
43
44 var output = Wix3.Output.Load(path, suppressVersionCheck: true, suppressSchema: true);
45 var wixMediaByDiskId = IndexWixMediaTableByDiskId(output);
46
47 // Dump to text for easy diffing, with some massaging to keep v3 and v4 diffable.
48 //
49 var tables = output.Tables.Cast<Wix3.Table>();
50 var wix3Dump = tables
51 .SelectMany(table => table.Rows.Cast<Wix3.Row>()
52 .SelectMany(row => RowToStrings(row, wixMediaByDiskId)))
53 .Where(s => !String.IsNullOrEmpty(s))
54 .OrderBy(s => s)
55 .ToArray();
56
57 var symbols = intermediate.Sections.SelectMany(s => s.Symbols);
58
59 var assemblySymbolsByFileId = symbols.OfType<AssemblySymbol>().ToDictionary(a => a.Id.Id);
60
61 var wix4Dump = symbols
62 .SelectMany(symbol => SymbolToStrings(symbol, assemblySymbolsByFileId))
63 .OrderBy(s => s)
64 .ToArray();
65
66#if false
67 Assert.Equal(wix3Dump, wix4Dump);
68#else // useful when you want to diff the outputs with another diff tool.
69 var wix3TextDump = String.Join(Environment.NewLine, wix3Dump);
70 var wix4TextDump = String.Join(Environment.NewLine, wix4Dump);
71
72 var path3 = Path.Combine(Path.GetTempPath(), "~3.txt");
73 var path4 = Path.Combine(Path.GetTempPath(), "~4.txt");
74
75 File.WriteAllText(path3, wix3TextDump);
76 File.WriteAllText(path4, wix4TextDump);
77
78 Assert.Equal(wix3TextDump, wix4TextDump);
79#endif
80 }
81 }
82
83 private static Dictionary<int, Wix3.WixMediaRow> IndexWixMediaTableByDiskId(Wix3.Output output)
84 {
85 var wixMediaByDiskId = new Dictionary<int, Wix3.WixMediaRow>();
86 var wixMediaTable = output.Tables["WixMedia"];
87
88 if (wixMediaTable != null)
89 {
90 foreach (Wix3.WixMediaRow row in wixMediaTable.Rows)
91 {
92 wixMediaByDiskId.Add((int)row[0], row);
93 }
94 }
95
96 return wixMediaByDiskId;
97 }
98
99 private static IEnumerable<string> RowToStrings(Wix3.Row row, Dictionary<int, Wix3.WixMediaRow> wixMediaByDiskId)
100 {
101 string fields = null;
102
103 // Massage output to match WiX v3 rows and v4 symbols.
104 //
105 switch (row.Table.Name)
106 {
107 case "Directory":
108 var dirs = SplitDefaultDir((string)row[2]);
109 fields = String.Join(",", row[0], row[1], dirs[0], dirs[1], dirs[2], dirs[3]);
110 break;
111 case "File":
112 {
113 var fieldValues = row.Fields.Take(7).Select(SafeConvertField).ToArray();
114 if (fieldValues[3] == null)
115 {
116 // "Somebody" sometimes writes out a null field even when the column definition says
117 // it's non-nullable. Not naming names or anything. (SWID tags.)
118 fieldValues[3] = "0";
119 }
120 fields = String.Join(",", fieldValues);
121 break;
122 }
123 case "Media":
124 var compression = wixMediaByDiskId.TryGetValue((int)row[0], out var wixMedia) ? (CompressionLevel?)Enum.Parse(typeof(CompressionLevel), SafeConvertField(wixMedia.Fields[1]), true) : null;
125
126 fields = String.Join(",", row.Fields.Select(SafeConvertField));
127 fields = String.Join(",", fields, (int?)compression, SafeConvertField(wixMedia?.Fields[2]));
128 break;
129 case "RegLocator":
130 var type = (int)row[4];
131 fields = String.Join(",", row[0], row[1], row[2], row[3], type & 0xF, (type & 0x10) == 0x10);
132 break;
133 case "RemoveFile":
134 var attributes = (int)row[4];
135 var onInstall = (attributes & 1) == 1 ? (bool?)true : null;
136 var onUninstall = (attributes & 2) == 2 ? (bool?)true : null;
137 fields = String.Join(",", row.Fields.Take(4).Select(SafeConvertField));
138 fields = String.Join(",", fields, onInstall, onUninstall);
139 break;
140 case "Shortcut":
141 var split = ((string)row[2]).Split('|');
142 var afterName = String.Join(",", row.Fields.Skip(3).Select(SafeConvertField));
143 fields = String.Join(",", row[0], row[1], split.Length > 1 ? split[1] : split[0], split.Length > 1 ? split[0] : String.Empty, afterName);
144 break;
145 case "WixAction":
146 var table = (int)SequenceStringToSequenceTable(row[0]);
147 fields = String.Join(",", table, row[1], row[2], row[3], row[4], row[5], row[6]);
148 break;
149 case "WixFile":
150 {
151 var fieldValues = row.Fields.Select(SafeConvertField).ToArray();
152 if (fieldValues[8] == null)
153 {
154 // "Somebody" sometimes writes out a null field even when the column definition says
155 // it's non-nullable. Not naming names or anything. (SWID tags.)
156 fieldValues[8] = "0";
157 }
158 if (fieldValues[10] == null)
159 {
160 // WixFile rows that come from merge modules will not have the attributes column set
161 // so initilaize with 0.
162 fieldValues[10] = "0";
163 }
164 fields = String.Join(",", fieldValues);
165 break;
166 }
167 case "WixMedia":
168 break;
169 default:
170 fields = String.Join(",", row.Fields.Select(SafeConvertField));
171 break;
172 }
173
174 if (fields != null)
175 {
176 yield return $"{row.Table.Name}:{fields}";
177 }
178 }
179
180 private static IEnumerable<string> SymbolToStrings(IntermediateSymbol symbol, Dictionary<string, AssemblySymbol> assemblySymbolsByFileId)
181 {
182 var name = symbol.Definition.Type == SymbolDefinitionType.SummaryInformation ? "_SummaryInformation" : symbol.Definition.Name;
183 var id = symbol.Id?.Id ?? String.Empty;
184
185 string fields;
186 switch (symbol.Definition.Name)
187 {
188 // Massage output to match WiX v3 rows and v4 symbols.
189 //
190 case "Component":
191 {
192 var componentSymbol = (ComponentSymbol)symbol;
193 var attributes = ComponentLocation.Either == componentSymbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0;
194 attributes |= ComponentLocation.SourceOnly == componentSymbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0;
195 attributes |= ComponentKeyPathType.Registry == componentSymbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath : 0;
196 attributes |= ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource : 0;
197 attributes |= componentSymbol.DisableRegistryReflection ? WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection : 0;
198 attributes |= componentSymbol.NeverOverwrite ? WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite : 0;
199 attributes |= componentSymbol.Permanent ? WindowsInstallerConstants.MsidbComponentAttributesPermanent : 0;
200 attributes |= componentSymbol.SharedDllRefCount ? WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount : 0;
201 attributes |= componentSymbol.Shared ? WindowsInstallerConstants.MsidbComponentAttributesShared : 0;
202 attributes |= componentSymbol.Transitive ? WindowsInstallerConstants.MsidbComponentAttributesTransitive : 0;
203 attributes |= componentSymbol.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0;
204 attributes |= componentSymbol.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0;
205
206 fields = String.Join(",",
207 componentSymbol.ComponentId,
208 componentSymbol.DirectoryRef,
209 attributes.ToString(),
210 componentSymbol.Condition,
211 componentSymbol.KeyPath
212 );
213 break;
214 }
215 case "CustomAction":
216 {
217 var customActionSymbol = (CustomActionSymbol)symbol;
218 var type = customActionSymbol.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0;
219 type |= customActionSymbol.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0;
220 type |= customActionSymbol.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate;
221 type |= customActionSymbol.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0;
222 type |= customActionSymbol.Hidden ? WindowsInstallerConstants.MsidbCustomActionTypeHideTarget : 0;
223 type |= customActionSymbol.Async ? WindowsInstallerConstants.MsidbCustomActionTypeAsync : 0;
224 type |= CustomActionExecutionType.FirstSequence == customActionSymbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence : 0;
225 type |= CustomActionExecutionType.OncePerProcess == customActionSymbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess : 0;
226 type |= CustomActionExecutionType.ClientRepeat == customActionSymbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat : 0;
227 type |= CustomActionExecutionType.Deferred == customActionSymbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript : 0;
228 type |= CustomActionExecutionType.Rollback == customActionSymbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback : 0;
229 type |= CustomActionExecutionType.Commit == customActionSymbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit : 0;
230 type |= CustomActionSourceType.File == customActionSymbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeSourceFile : 0;
231 type |= CustomActionSourceType.Directory == customActionSymbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeDirectory : 0;
232 type |= CustomActionSourceType.Property == customActionSymbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeProperty : 0;
233 type |= CustomActionTargetType.Dll == customActionSymbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeDll : 0;
234 type |= CustomActionTargetType.Exe == customActionSymbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeExe : 0;
235 type |= CustomActionTargetType.TextData == customActionSymbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeTextData : 0;
236 type |= CustomActionTargetType.JScript == customActionSymbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeJScript : 0;
237 type |= CustomActionTargetType.VBScript == customActionSymbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeVBScript : 0;
238
239 fields = String.Join(",",
240 type.ToString(),
241 customActionSymbol.Source,
242 customActionSymbol.Target,
243 customActionSymbol.PatchUninstall ? WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall.ToString() : null
244 );
245 break;
246 }
247 case "Directory":
248 {
249 var directorySymbol = (DirectorySymbol)symbol;
250
251 if (!String.IsNullOrEmpty(directorySymbol.ComponentGuidGenerationSeed))
252 {
253 yield return $"WixDirectory:{directorySymbol.Id.Id},{directorySymbol.ComponentGuidGenerationSeed}";
254 }
255
256 fields = String.Join(",", directorySymbol.ParentDirectoryRef, directorySymbol.Name, directorySymbol.ShortName, directorySymbol.SourceName, directorySymbol.SourceShortName);
257 break;
258 }
259 case "Feature":
260 {
261 var featureSymbol = (FeatureSymbol)symbol;
262 var attributes = featureSymbol.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0;
263 attributes |= featureSymbol.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0;
264 attributes |= FeatureInstallDefault.FollowParent == featureSymbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFollowParent : 0;
265 attributes |= FeatureInstallDefault.Source == featureSymbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0;
266 attributes |= FeatureTypicalDefault.Advertise == featureSymbol.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0;
267
268 fields = String.Join(",",
269 featureSymbol.ParentFeatureRef,
270 featureSymbol.Title,
271 featureSymbol.Description,
272 featureSymbol.Display.ToString(),
273 featureSymbol.Level.ToString(),
274 featureSymbol.DirectoryRef,
275 attributes.ToString());
276 break;
277 }
278 case "File":
279 {
280 var fileSymbol = (FileSymbol)symbol;
281
282 if (fileSymbol.BindPath != null)
283 {
284 yield return $"BindImage:{fileSymbol.Id.Id},{fileSymbol.BindPath}";
285 }
286
287 if (fileSymbol.FontTitle != null)
288 {
289 yield return $"Font:{fileSymbol.Id.Id},{fileSymbol.FontTitle}";
290 }
291
292 if (fileSymbol.SelfRegCost.HasValue)
293 {
294 yield return $"SelfReg:{fileSymbol.Id.Id},{fileSymbol.SelfRegCost}";
295 }
296
297 int? assemblyAttributes = null;
298 if (assemblySymbolsByFileId.TryGetValue(fileSymbol.Id.Id, out var assemblySymbol))
299 {
300 if (assemblySymbol.Type == AssemblyType.DotNetAssembly)
301 {
302 assemblyAttributes = 0;
303 }
304 else if (assemblySymbol.Type == AssemblyType.Win32Assembly)
305 {
306 assemblyAttributes = 1;
307 }
308 }
309
310 yield return "WixFile:" + String.Join(",",
311 fileSymbol.Id.Id,
312 assemblyAttributes,
313 assemblySymbol?.ManifestFileRef,
314 assemblySymbol?.ApplicationFileRef,
315 fileSymbol.DirectoryRef,
316 fileSymbol.DiskId,
317 fileSymbol.Source.Path,
318 null, // assembly processor arch
319 fileSymbol.PatchGroup,
320 0,
321 (int)fileSymbol.PatchAttributes,
322 fileSymbol.RetainLengths,
323 fileSymbol.IgnoreOffsets,
324 fileSymbol.IgnoreLengths,
325 fileSymbol.RetainOffsets
326 );
327
328 var fileAttributes = 0;
329 fileAttributes |= (fileSymbol.Attributes & FileSymbolAttributes.ReadOnly) != 0 ? WindowsInstallerConstants.MsidbFileAttributesReadOnly : 0;
330 fileAttributes |= (fileSymbol.Attributes & FileSymbolAttributes.Hidden) != 0 ? WindowsInstallerConstants.MsidbFileAttributesHidden : 0;
331 fileAttributes |= (fileSymbol.Attributes & FileSymbolAttributes.System) != 0 ? WindowsInstallerConstants.MsidbFileAttributesSystem : 0;
332 fileAttributes |= (fileSymbol.Attributes & FileSymbolAttributes.Vital) != 0 ? WindowsInstallerConstants.MsidbFileAttributesVital : 0;
333 fileAttributes |= (fileSymbol.Attributes & FileSymbolAttributes.Checksum) != 0 ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0;
334 fileAttributes |= (fileSymbol.Attributes & FileSymbolAttributes.Compressed) != 0 ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0;
335 fileAttributes |= (fileSymbol.Attributes & FileSymbolAttributes.Uncompressed) != 0 ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0;
336
337 fields = String.Join(",",
338 fileSymbol.ComponentRef,
339 fileSymbol.Name,
340 fileSymbol.FileSize.ToString(),
341 fileSymbol.Version,
342 fileSymbol.Language,
343 fileAttributes);
344 break;
345 }
346
347 case "Media":
348 fields = String.Join(",", symbol.Fields.Skip(1).Select(SafeConvertField));
349 break;
350
351 case "Assembly":
352 {
353 var assemblySymbol = (AssemblySymbol)symbol;
354
355 id = null;
356 name = "MsiAssembly";
357 fields = String.Join(",", assemblySymbol.ComponentRef, assemblySymbol.FeatureRef, assemblySymbol.ManifestFileRef, assemblySymbol.ApplicationFileRef, assemblySymbol.Type == AssemblyType.Win32Assembly ? 1 : 0);
358 break;
359 }
360 case "RegLocator":
361 {
362 var locatorSymbol = (RegLocatorSymbol)symbol;
363
364 fields = String.Join(",", (int)locatorSymbol.Root, locatorSymbol.Key, locatorSymbol.Name, (int)locatorSymbol.Type, locatorSymbol.Win64);
365 break;
366 }
367 case "Registry":
368 {
369 var registrySymbol = (RegistrySymbol)symbol;
370 var value = registrySymbol.Value;
371
372 switch (registrySymbol.ValueType)
373 {
374 case RegistryValueType.Binary:
375 value = String.Concat("#x", value);
376 break;
377 case RegistryValueType.Expandable:
378 value = String.Concat("#%", value);
379 break;
380 case RegistryValueType.Integer:
381 value = String.Concat("#", value);
382 break;
383 case RegistryValueType.MultiString:
384 switch (registrySymbol.ValueAction)
385 {
386 case RegistryValueActionType.Append:
387 value = String.Concat("[~]", value);
388 break;
389 case RegistryValueActionType.Prepend:
390 value = String.Concat(value, "[~]");
391 break;
392 case RegistryValueActionType.Write:
393 default:
394 if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal))
395 {
396 value = String.Concat("[~]", value, "[~]");
397 }
398 break;
399 }
400 break;
401 case RegistryValueType.String:
402 // escape the leading '#' character for string registry keys
403 if (null != value && value.StartsWith("#", StringComparison.Ordinal))
404 {
405 value = String.Concat("#", value);
406 }
407 break;
408 }
409
410 fields = String.Join(",",
411 ((int)registrySymbol.Root).ToString(),
412 registrySymbol.Key,
413 registrySymbol.Name,
414 value,
415 registrySymbol.ComponentRef
416 );
417 break;
418 }
419
420 case "RemoveRegistry":
421 {
422 var removeRegistrySymbol = (RemoveRegistrySymbol)symbol;
423 fields = String.Join(",",
424 ((int)removeRegistrySymbol.Root).ToString(),
425 removeRegistrySymbol.Key,
426 removeRegistrySymbol.Name,
427 removeRegistrySymbol.ComponentRef
428 );
429 break;
430 }
431
432 case "ServiceControl":
433 {
434 var serviceControlSymbol = (ServiceControlSymbol)symbol;
435
436 var events = serviceControlSymbol.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0;
437 events |= serviceControlSymbol.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0;
438 events |= serviceControlSymbol.InstallStart ? WindowsInstallerConstants.MsidbServiceControlEventStart : 0;
439 events |= serviceControlSymbol.UninstallStart ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStart : 0;
440 events |= serviceControlSymbol.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0;
441 events |= serviceControlSymbol.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0;
442
443 fields = String.Join(",",
444 serviceControlSymbol.Name,
445 events.ToString(),
446 serviceControlSymbol.Arguments,
447 serviceControlSymbol.Wait == true ? "1" : "0",
448 serviceControlSymbol.ComponentRef
449 );
450 break;
451 }
452
453 case "ServiceInstall":
454 {
455 var serviceInstallSymbol = (ServiceInstallSymbol)symbol;
456
457 var errorControl = (int)serviceInstallSymbol.ErrorControl;
458 errorControl |= serviceInstallSymbol.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0;
459
460 var serviceType = (int)serviceInstallSymbol.ServiceType;
461 serviceType |= serviceInstallSymbol.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0;
462
463 fields = String.Join(",",
464 serviceInstallSymbol.Name,
465 serviceInstallSymbol.DisplayName,
466 serviceType.ToString(),
467 ((int)serviceInstallSymbol.StartType).ToString(),
468 errorControl.ToString(),
469 serviceInstallSymbol.LoadOrderGroup,
470 serviceInstallSymbol.Dependencies,
471 serviceInstallSymbol.StartName,
472 serviceInstallSymbol.Password,
473 serviceInstallSymbol.Arguments,
474 serviceInstallSymbol.ComponentRef,
475 serviceInstallSymbol.Description
476 );
477 break;
478 }
479
480 case "Upgrade":
481 {
482 var upgradeSymbol = (UpgradeSymbol)symbol;
483
484 var attributes = upgradeSymbol.MigrateFeatures ? WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures : 0;
485 attributes |= upgradeSymbol.OnlyDetect ? WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect : 0;
486 attributes |= upgradeSymbol.IgnoreRemoveFailures ? WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure : 0;
487 attributes |= upgradeSymbol.VersionMinInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive : 0;
488 attributes |= upgradeSymbol.VersionMaxInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive : 0;
489 attributes |= upgradeSymbol.ExcludeLanguages ? WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive : 0;
490
491 fields = String.Join(",",
492 upgradeSymbol.VersionMin,
493 upgradeSymbol.VersionMax,
494 upgradeSymbol.Language,
495 attributes.ToString(),
496 upgradeSymbol.Remove,
497 upgradeSymbol.ActionProperty
498 );
499 break;
500 }
501
502 case "WixAction":
503 {
504 var wixActionSymbol = (WixActionSymbol)symbol;
505 var data = wixActionSymbol.Fields[(int)WixActionSymbolFields.SequenceTable].AsObject();
506 var sequenceTableAsInt = data is string ? (int)SequenceStringToSequenceTable(data) : (int)wixActionSymbol.SequenceTable;
507
508 fields = String.Join(",",
509 sequenceTableAsInt,
510 wixActionSymbol.Action,
511 wixActionSymbol.Condition,
512 wixActionSymbol.Sequence?.ToString() ?? String.Empty,
513 wixActionSymbol.Before,
514 wixActionSymbol.After,
515 wixActionSymbol.Overridable == true ? "1" : "0"
516 );
517 break;
518 }
519
520 case "WixComplexReference":
521 {
522 var wixComplexReferenceSymbol = (WixComplexReferenceSymbol)symbol;
523 fields = String.Join(",",
524 wixComplexReferenceSymbol.Parent,
525 (int)wixComplexReferenceSymbol.ParentType,
526 wixComplexReferenceSymbol.ParentLanguage,
527 wixComplexReferenceSymbol.Child,
528 (int)wixComplexReferenceSymbol.ChildType,
529 wixComplexReferenceSymbol.IsPrimary ? "1" : "0"
530 );
531 break;
532 }
533
534 case "WixProperty":
535 {
536 var wixPropertySymbol = (WixPropertySymbol)symbol;
537 var attributes = wixPropertySymbol.Admin ? 0x1 : 0;
538 attributes |= wixPropertySymbol.Hidden ? 0x2 : 0;
539 attributes |= wixPropertySymbol.Secure ? 0x4 : 0;
540
541 fields = String.Join(",",
542 wixPropertySymbol.PropertyRef,
543 attributes.ToString()
544 );
545 break;
546 }
547
548 default:
549 fields = String.Join(",", symbol.Fields.Select(SafeConvertField));
550 break;
551 }
552
553 fields = String.IsNullOrEmpty(id) ? fields : String.IsNullOrEmpty(fields) ? id : $"{id},{fields}";
554 yield return $"{name}:{fields}";
555 }
556
557 private static SequenceTable SequenceStringToSequenceTable(object sequenceString)
558 {
559 switch (sequenceString)
560 {
561 case "AdminExecuteSequence":
562 return SequenceTable.AdminExecuteSequence;
563 case "AdminUISequence":
564 return SequenceTable.AdminUISequence;
565 case "AdvtExecuteSequence":
566 return SequenceTable.AdvertiseExecuteSequence;
567 case "InstallExecuteSequence":
568 return SequenceTable.InstallExecuteSequence;
569 case "InstallUISequence":
570 return SequenceTable.InstallUISequence;
571 default:
572 throw new ArgumentException($"Unknown sequence: {sequenceString}");
573 }
574 }
575
576 private static string SafeConvertField(Wix3.Field field)
577 {
578 return field?.Data?.ToString();
579 }
580
581 private static string SafeConvertField(IntermediateField field)
582 {
583 var data = field.AsObject();
584 if (data is IntermediateFieldPathValue path)
585 {
586 return path.Path;
587 }
588
589 return data?.ToString();
590 }
591
592 private static string[] SplitDefaultDir(string defaultDir)
593 {
594 var split1 = defaultDir.Split(':');
595 var targetSplit = split1.Length > 1 ? split1[1].Split('|') : split1[0].Split('|');
596 var sourceSplit = split1.Length > 1 ? split1[0].Split('|') : new[] { String.Empty };
597 return new[]
598 {
599 targetSplit.Length > 1 ? targetSplit[1] : targetSplit[0],
600 targetSplit.Length > 1 ? targetSplit[0] : String.Empty,
601 sourceSplit.Length > 1 ? sourceSplit[1] : sourceSplit[0],
602 sourceSplit.Length > 1 ? sourceSplit[0] : String.Empty
603 };
604 }
605 }
606}
diff --git a/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wixout b/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wixout
new file mode 100644
index 00000000..da64b8af
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wixout
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wixproj b/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wixproj
new file mode 100644
index 00000000..d7c4e625
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wixproj
@@ -0,0 +1,47 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureWixToolsetInstalled" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <PropertyGroup>
4 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5 <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
6 <ProductVersion>3.10</ProductVersion>
7 <ProjectGuid>d59f1c1e-9238-49fa-bfa2-ec1d9c2dda1d</ProjectGuid>
8 <SchemaVersion>2.0</SchemaVersion>
9 <OutputName>SymbolizerWixout</OutputName>
10 <OutputType>Package</OutputType>
11 </PropertyGroup>
12 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
13 <OutputPath>bin\$(Configuration)\</OutputPath>
14 <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
15 <DefineConstants>Debug</DefineConstants>
16 </PropertyGroup>
17 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
18 <OutputPath>bin\$(Configuration)\</OutputPath>
19 <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
20 </PropertyGroup>
21 <ItemGroup>
22 <Compile Include="Product.wxs" />
23 </ItemGroup>
24 <ItemGroup>
25 <WixExtension Include="WixUtilExtension">
26 <HintPath>$(WixExtDir)\WixUtilExtension.dll</HintPath>
27 <Name>WixUtilExtension</Name>
28 </WixExtension>
29 <WixExtension Include="WixNetFxExtension">
30 <HintPath>$(WixExtDir)\WixNetFxExtension.dll</HintPath>
31 <Name>WixNetFxExtension</Name>
32 </WixExtension>
33 </ItemGroup>
34 <Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
35 <Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets') " />
36 <Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' ">
37 <Error Text="The WiX Toolset v3.11 (or newer) build tools must be installed to build this project. To download the WiX Toolset, see http://wixtoolset.org/releases/" />
38 </Target>
39 <!--
40 To modify your build process, add your task inside one of the targets below and uncomment it.
41 Other similar extension points exist, see Wix.targets.
42 <Target Name="BeforeBuild">
43 </Target>
44 <Target Name="AfterBuild">
45 </Target>
46 -->
47</Project> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wxs b/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wxs
new file mode 100644
index 00000000..46d4fb43
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters.Symbolizer/TestData/Integration/test.wxs
@@ -0,0 +1,36 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
3 <Product Id="*" Name="SymbolizerWixout" Language="1033" Version="1.0.0.0" Manufacturer="FireGiant" UpgradeCode="14a02d7f-9b32-4c92-b1f1-da518bf4e32a">
4 <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
5
6 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." AllowSameVersionUpgrades="yes" IgnoreRemoveFailure="yes" />
7 <MediaTemplate />
8
9 <Feature Id="ProductFeature" Title="SymbolizerWixout" Level="1">
10 <Feature Id="ChildFeature">
11 <ComponentGroupRef Id="ProductComponents" />
12 </Feature>
13 </Feature>
14
15 <PropertyRef Id="WIX_IS_NETFRAMEWORK_462_OR_LATER_INSTALLED" />
16 <CustomActionRef Id="WixFailWhenDeferred" />
17 <Property Id="WIXFAILWHENDEFERRED" Value="1" Secure="yes" />
18 </Product>
19
20 <Fragment>
21 <Directory Id="TARGETDIR" Name="SourceDir">
22 <Directory Id="ProgramFilesFolder">
23 <Directory Id="INSTALLFOLDER" Name="SymbolizerWixout" />
24 </Directory>
25 </Directory>
26 </Fragment>
27
28 <Fragment>
29 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
30 <Component Id="ProductComponent">
31 <File Checksum="yes" ReadOnly="yes" Source="$(env.WIX)\bin\candle.exe" Assembly=".net" AssemblyApplication="candle.exe" />
32 <RegistryValue Root="HKLM" Key="SOFTWARE\WiX Toolset" Name="[ProductName]Installed" Value="1" Type="integer" />
33 </Component>
34 </ComponentGroup>
35 </Fragment>
36</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.csproj b/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.csproj
new file mode 100644
index 00000000..995d9297
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.csproj
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5
6 <PropertyGroup>
7 <TargetFramework>net461</TargetFramework>
8 <IsPackable>false</IsPackable>
9 </PropertyGroup>
10
11 <ItemGroup>
12 <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
13 </ItemGroup>
14
15 <ItemGroup>
16 <ProjectReference Include="..\..\WixToolset.Converters.Symbolizer\WixToolset.Converters.Symbolizer.csproj" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <PackageReference Include="WixToolset.Data" Version="4.0.*" />
21 <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" />
22 </ItemGroup>
23
24 <ItemGroup>
25 <Reference Include="wix" HintPath="..\..\deps\wix.dll" />
26 </ItemGroup>
27
28 <ItemGroup>
29 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.0" />
30 <PackageReference Include="xunit" Version="2.4.1" />
31 <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="All" />
32 </ItemGroup>
33</Project>
diff --git a/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject b/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
new file mode 100644
index 00000000..18ab4f79
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
@@ -0,0 +1,9 @@
1<ProjectConfiguration>
2 <Settings>
3 <IgnoredTests>
4 <NamedTestSelector>
5 <TestName>WixToolsetTest.Converters.Symbolizer.ConvertSymbolsFixture.CanLoadWixoutAndConvertToIntermediate</TestName>
6 </NamedTestSelector>
7 </IgnoredTests>
8 </Settings>
9</ProjectConfiguration> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Converters/BaseConverterFixture.cs b/src/wix/test/WixToolsetTest.Converters/BaseConverterFixture.cs
new file mode 100644
index 00000000..2421d73b
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/BaseConverterFixture.cs
@@ -0,0 +1,33 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Xml;
9 using System.Xml.Linq;
10 using Xunit;
11
12 public abstract class BaseConverterFixture
13 {
14 protected static string UnformattedDocumentString(XDocument document, bool omitXmlDeclaration = true)
15 {
16 var sb = new StringBuilder();
17
18 using (var writer = new StringWriter(sb))
19 using (var xml = XmlWriter.Create(writer, new XmlWriterSettings { OmitXmlDeclaration = omitXmlDeclaration }))
20 {
21 document.Save(xml);
22 }
23
24 return sb.ToString().TrimStart();
25 }
26
27 protected static string[] UnformattedDocumentLines(XDocument document, bool omitXmlDeclaration = true)
28 {
29 var unformatted = UnformattedDocumentString(document, omitXmlDeclaration);
30 return unformatted.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
31 }
32 }
33}
diff --git a/src/wix/test/WixToolsetTest.Converters/BitnessFixture.cs b/src/wix/test/WixToolsetTest.Converters/BitnessFixture.cs
new file mode 100644
index 00000000..e45a9388
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/BitnessFixture.cs
@@ -0,0 +1,187 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class BitnessFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixComponentBitness()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
20 " <Fragment>",
21 " <Component>",
22 " <File Source='default.exe' />",
23 " </Component>",
24 " <Component Win64='no'>",
25 " <File Source='32bit.exe' />",
26 " </Component>",
27 " <Component Win64='yes'>",
28 " <File Source='64bit.exe' />",
29 " </Component>",
30 " <Component Win64='$(var.Win64)'>",
31 " <File Source='unconvert.exe' />",
32 " </Component>",
33 " </Fragment>",
34 "</Wix>");
35
36 var expected = new[]
37 {
38 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
39 " <Fragment>",
40 " <Component>",
41 " <File Id=\"default.exe\" Source=\"default.exe\" />",
42 " </Component>",
43 " <Component Bitness=\"always32\">",
44 " <File Id=\"_32bit.exe\" Source=\"32bit.exe\" />",
45 " </Component>",
46 " <Component Bitness=\"always64\">",
47 " <File Id=\"_64bit.exe\" Source=\"64bit.exe\" />",
48 " </Component>",
49 " <Component Bitness=\"$(var.Win64)\">",
50 " <File Id=\"unconvert.exe\" Source=\"unconvert.exe\" />",
51 " </Component>",
52 " </Fragment>",
53 "</Wix>"
54 };
55
56 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
57
58 var messaging = new MockMessaging();
59 var converter = new WixConverter(messaging, 2, null, null);
60
61 var errors = converter.ConvertDocument(document);
62 Assert.Equal(10, errors);
63
64 var actualLines = UnformattedDocumentLines(document);
65 WixAssert.CompareLineByLine(expected, actualLines);
66 }
67
68 [Fact]
69 public void FixRegistrySearchBitness()
70 {
71 var parse = String.Join(Environment.NewLine,
72 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
73 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
74 " <Fragment>",
75 " <Property Id='BITNESSDEFAULT'>",
76 " <RegistrySearch Id='SampleRegSearch' Root='HKLM' Key='SampleReg' Type='raw'></RegistrySearch>",
77 " </Property>",
78 " <Property Id='BITNESS32'>",
79 " <RegistrySearch Id='SampleRegSearch' Root='HKLM' Key='SampleReg' Type='raw' Win64='no'></RegistrySearch>",
80 " </Property>",
81 " <Property Id='BITNESS64'>",
82 " <RegistrySearch Id='SampleRegSearch' Root='HKLM' Key='SampleReg' Type='raw' Win64='yes'></RegistrySearch>",
83 " </Property>",
84 " </Fragment>",
85 "</Wix>");
86
87 var expected = new[]
88 {
89 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
90 " <Fragment>",
91 " <Property Id=\"BITNESSDEFAULT\">",
92 " <RegistrySearch Id=\"SampleRegSearch\" Root=\"HKLM\" Key=\"SampleReg\" Type=\"raw\"></RegistrySearch>",
93 " </Property>",
94 " <Property Id=\"BITNESS32\">",
95 " <RegistrySearch Id=\"SampleRegSearch\" Root=\"HKLM\" Key=\"SampleReg\" Type=\"raw\" Bitness=\"always32\"></RegistrySearch>",
96 " </Property>",
97 " <Property Id=\"BITNESS64\">",
98 " <RegistrySearch Id=\"SampleRegSearch\" Root=\"HKLM\" Key=\"SampleReg\" Type=\"raw\" Bitness=\"always64\"></RegistrySearch>",
99 " </Property>",
100 " </Fragment>",
101 "</Wix>"
102 };
103
104 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
105
106 var messaging = new MockMessaging();
107 var converter = new WixConverter(messaging, 2, null, null);
108
109 var errors = converter.ConvertDocument(document);
110 Assert.Equal(4, errors);
111
112 var actualLines = UnformattedDocumentLines(document);
113 WixAssert.CompareLineByLine(expected, actualLines);
114 }
115
116 [Fact]
117 public void FixUtilRegistrySearchBitness()
118 {
119 var parse = String.Join(Environment.NewLine,
120 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>",
121 " <Fragment>",
122 " <util:RegistrySearch Id='RegValue' Root='HKLM' Key='Converter' Variable='Test' />",
123 " <util:RegistrySearch Id='RegValue2' Root='HKLM' Key='Converter' Variable='Test' Result='value' Win64='no' />",
124 " <util:RegistrySearch Id='RegValue3' Root='HKLM' Key='Converter' Variable='Test' Result='exists' Win64='yes' />",
125 " </Fragment>",
126 "</Wix>");
127
128 var expected = new[]
129 {
130 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:util=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
131 " <Fragment>",
132 " <util:RegistrySearch Id=\"RegValue\" Root=\"HKLM\" Key=\"Converter\" Variable=\"Test\" />",
133 " <util:RegistrySearch Id=\"RegValue2\" Root=\"HKLM\" Key=\"Converter\" Variable=\"Test\" Result=\"value\" Bitness=\"always32\" />",
134 " <util:RegistrySearch Id=\"RegValue3\" Root=\"HKLM\" Key=\"Converter\" Variable=\"Test\" Result=\"exists\" Bitness=\"always64\" />",
135 " </Fragment>",
136 "</Wix>"
137 };
138
139 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
140
141 var messaging = new MockMessaging();
142 var converter = new WixConverter(messaging, 2, null, null);
143
144 var errors = converter.ConvertDocument(document);
145 Assert.Equal(6, errors);
146
147 var actualLines = UnformattedDocumentLines(document);
148 WixAssert.CompareLineByLine(expected, actualLines);
149 }
150
151 [Fact]
152 public void FixApprovedExeBitness()
153 {
154 var parse = String.Join(Environment.NewLine,
155 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
156 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
157 " <Bundle>",
158 " <ApprovedExeForElevation Id='Default' Key='WixToolset\\BurnTesting' Value='Test' />",
159 " <ApprovedExeForElevation Id='Bitness32' Key='WixToolset\\BurnTesting' Value='Test' Win64='no' />",
160 " <ApprovedExeForElevation Id='Bitness64' Key='WixToolset\\BurnTesting' Value='Test' Win64='yes' />",
161 " </Bundle>",
162 "</Wix>");
163
164 var expected = new[]
165 {
166 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
167 " <Bundle>",
168 " <ApprovedExeForElevation Id=\"Default\" Key=\"WixToolset\\BurnTesting\" Value=\"Test\" />",
169 " <ApprovedExeForElevation Id=\"Bitness32\" Key=\"WixToolset\\BurnTesting\" Value=\"Test\" Bitness=\"always32\" />",
170 " <ApprovedExeForElevation Id=\"Bitness64\" Key=\"WixToolset\\BurnTesting\" Value=\"Test\" Bitness=\"always64\" />",
171 " </Bundle>",
172 "</Wix>"
173 };
174
175 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
176
177 var messaging = new MockMessaging();
178 var converter = new WixConverter(messaging, 2, null, null);
179
180 var errors = converter.ConvertDocument(document);
181 Assert.Equal(4, errors);
182
183 var actualLines = UnformattedDocumentLines(document);
184 WixAssert.CompareLineByLine(expected, actualLines);
185 }
186 }
187}
diff --git a/src/wix/test/WixToolsetTest.Converters/BootstrapperApplicationFixture.cs b/src/wix/test/WixToolsetTest.Converters/BootstrapperApplicationFixture.cs
new file mode 100644
index 00000000..158ab3be
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/BootstrapperApplicationFixture.cs
@@ -0,0 +1,418 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class BootstrapperApplicationFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void CantCreateBootstrapperApplicationDllFromV3PayloadGroupRef()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
19 " <Fragment>",
20 " <BootstrapperApplication Id='ba'>",
21 " <PayloadGroupRef Id='baPayloads' />",
22 " </BootstrapperApplication>",
23 " </Fragment>",
24 "</Wix>");
25
26 var expected = new[]
27 {
28 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
29 " <Fragment>",
30 " <BootstrapperApplication Id=\"ba\">",
31 " <PayloadGroupRef Id=\"baPayloads\" />",
32 " </BootstrapperApplication>",
33 " </Fragment>",
34 "</Wix>"
35 };
36
37 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
38
39 var messaging = new MockMessaging();
40 var converter = new WixConverter(messaging, 2, null, null);
41
42 var errors = converter.ConvertDocument(document);
43 Assert.Equal(2, errors);
44
45 var actualLines = UnformattedDocumentLines(document);
46 WixAssert.CompareLineByLine(expected, actualLines);
47 }
48
49 [Fact]
50 public void ConvertDotNetCoreBootstrapperApplicationRefWithExistingElement()
51 {
52 var parse = String.Join(Environment.NewLine,
53 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs' xmlns:bal='http://wixtoolset.org/schemas/v4/wxs/bal'>",
54 " <Fragment>",
55 " <BootstrapperApplicationRef Id='DotNetCoreBootstrapperApplicationHost.Minimal'>",
56 " <bal:WixDotNetCoreBootstrapperApplication SelfContainedDeployment='yes' />",
57 " </BootstrapperApplicationRef>",
58 " </Fragment>",
59 "</Wix>");
60
61 var expected = new[]
62 {
63 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">",
64 " <Fragment>",
65 " <BootstrapperApplication>",
66 " <bal:WixDotNetCoreBootstrapperApplicationHost SelfContainedDeployment=\"yes\" Theme=\"none\" />",
67 " </BootstrapperApplication>",
68 " </Fragment>",
69 "</Wix>"
70 };
71
72 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
73
74 var messaging = new MockMessaging();
75 var converter = new WixConverter(messaging, 2, null, null);
76
77 var errors = converter.ConvertDocument(document);
78 Assert.Equal(1, errors);
79
80 var actualLines = UnformattedDocumentLines(document);
81 WixAssert.CompareLineByLine(expected, actualLines);
82 }
83
84 [Fact]
85 public void ConvertDotNetCoreBootstrapperApplicationRefWithoutExistingElement()
86 {
87 var parse = String.Join(Environment.NewLine,
88 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs' xmlns:bal='http://wixtoolset.org/schemas/v4/wxs/bal'>",
89 " <Fragment>",
90 " <BootstrapperApplicationRef Id='DotNetCoreBootstrapperApplicationHost' />",
91 " </Fragment>",
92 "</Wix>");
93
94 var expected = new[]
95 {
96 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">",
97 " <Fragment>",
98 " <BootstrapperApplication>",
99 "<bal:WixDotNetCoreBootstrapperApplicationHost />",
100 "</BootstrapperApplication>",
101 " </Fragment>",
102 "</Wix>"
103 };
104
105 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
106
107 var messaging = new MockMessaging();
108 var converter = new WixConverter(messaging, 2, null, null);
109
110 var errors = converter.ConvertDocument(document);
111 Assert.Equal(1, errors);
112
113 var actualLines = UnformattedDocumentLines(document);
114 WixAssert.CompareLineByLine(expected, actualLines);
115 }
116
117 [Fact]
118 public void ConvertFrameworkBootstrapperApplicationRefWithExistingElement()
119 {
120 var parse = String.Join(Environment.NewLine,
121 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:bal='http://schemas.microsoft.com/wix/BalExtension'>",
122 " <Fragment>",
123 " <BootstrapperApplicationRef Id='ManagedBootstrapperApplicationHost'>",
124 " <bal:WixManagedBootstrapperApplicationHost LogoFile='logo.png' />",
125 " </BootstrapperApplicationRef>",
126 " </Fragment>",
127 "</Wix>");
128
129 var expected = new[]
130 {
131 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">",
132 " <Fragment>",
133 " <BootstrapperApplication>",
134 " <bal:WixManagedBootstrapperApplicationHost LogoFile=\"logo.png\" />",
135 " </BootstrapperApplication>",
136 " </Fragment>",
137 "</Wix>"
138 };
139
140 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
141
142 var messaging = new MockMessaging();
143 var converter = new WixConverter(messaging, 2, null, null);
144
145 var errors = converter.ConvertDocument(document);
146 Assert.Equal(3, errors);
147
148 var actualLines = UnformattedDocumentLines(document);
149 WixAssert.CompareLineByLine(expected, actualLines);
150 }
151
152 [Fact]
153 public void ConvertFrameworkBootstrapperApplicationRefWithoutExistingElement()
154 {
155 var parse = String.Join(Environment.NewLine,
156 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:bal='http://schemas.microsoft.com/wix/BalExtension'>",
157 " <Fragment>",
158 " <BootstrapperApplicationRef Id='ManagedBootstrapperApplicationHost.RtfLicense.Minimal' />",
159 " </Fragment>",
160 "</Wix>");
161
162 var expected = new[]
163 {
164 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">",
165 " <Fragment>",
166 " <BootstrapperApplication>",
167 "<bal:WixManagedBootstrapperApplicationHost Theme=\"none\" />",
168 "</BootstrapperApplication>",
169 " </Fragment>",
170 "</Wix>"
171 };
172
173 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
174
175 var messaging = new MockMessaging();
176 var converter = new WixConverter(messaging, 2, null, null);
177
178 var errors = converter.ConvertDocument(document);
179 Assert.Equal(3, errors);
180
181 var actualLines = UnformattedDocumentLines(document);
182 WixAssert.CompareLineByLine(expected, actualLines);
183 }
184
185 [Fact]
186 public void ConvertStandardBootstrapperApplicationRefWithExistingElement()
187 {
188 var parse = String.Join(Environment.NewLine,
189 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:bal='http://schemas.microsoft.com/wix/BalExtension'>",
190 " <Fragment>",
191 " <BootstrapperApplicationRef Id='WixStandardBootstrapperApplication.Foundation'>",
192 " <bal:WixStandardBootstrapperApplication LaunchTarget='[InstallFolder]the.exe' />",
193 " </BootstrapperApplicationRef>",
194 " </Fragment>",
195 "</Wix>");
196
197 var expected = new[]
198 {
199 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">",
200 " <Fragment>",
201 " <BootstrapperApplication>",
202 " <bal:WixStandardBootstrapperApplication LaunchTarget=\"[InstallFolder]the.exe\" Theme=\"none\" />",
203 " </BootstrapperApplication>",
204 " </Fragment>",
205 "</Wix>"
206 };
207
208 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
209
210 var messaging = new MockMessaging();
211 var converter = new WixConverter(messaging, 2, null, null);
212
213 var errors = converter.ConvertDocument(document);
214 Assert.Equal(3, errors);
215
216 var actualLines = UnformattedDocumentLines(document);
217 WixAssert.CompareLineByLine(expected, actualLines);
218 }
219
220 [Fact]
221 public void ConvertStandardBootstrapperApplicationRefWithoutExistingElement()
222 {
223 var parse = String.Join(Environment.NewLine,
224 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:bal='http://schemas.microsoft.com/wix/BalExtension'>",
225 " <Fragment>",
226 " <BootstrapperApplicationRef Id='WixStandardBootstrapperApplication.RtfLicense' />",
227 " </Fragment>",
228 "</Wix>");
229
230 var expected = new[]
231 {
232 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">",
233 " <Fragment>",
234 " <BootstrapperApplication>",
235 "<bal:WixStandardBootstrapperApplication Theme=\"rtfLicense\" />",
236 "</BootstrapperApplication>",
237 " </Fragment>",
238 "</Wix>"
239 };
240
241 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
242
243 var messaging = new MockMessaging();
244 var converter = new WixConverter(messaging, 2, null, null);
245
246 var errors = converter.ConvertDocument(document);
247 Assert.Equal(3, errors);
248
249 var actualLines = UnformattedDocumentLines(document);
250 WixAssert.CompareLineByLine(expected, actualLines);
251 }
252
253 [Fact]
254 public void CreateBootstrapperApplicationDllFromV3()
255 {
256 var parse = String.Join(Environment.NewLine,
257 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
258 " <Fragment>",
259 " <BootstrapperApplication Id='ba' SourceFile='ba.dll' />",
260 " </Fragment>",
261 "</Wix>");
262
263 var expected = new[]
264 {
265 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
266 " <Fragment>",
267 " <BootstrapperApplication Id=\"ba\">",
268 "<BootstrapperApplicationDll SourceFile=\"ba.dll\" DpiAwareness=\"unaware\" />",
269 "</BootstrapperApplication>",
270 " </Fragment>",
271 "</Wix>"
272 };
273
274 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
275
276 var messaging = new MockMessaging();
277 var converter = new WixConverter(messaging, 2, null, null);
278
279 var errors = converter.ConvertDocument(document);
280 Assert.Equal(3, errors);
281
282 var actualLines = UnformattedDocumentLines(document);
283 WixAssert.CompareLineByLine(expected, actualLines);
284 }
285
286 [Fact]
287 public void CreateBootstrapperApplicationDllFromV3Payload()
288 {
289 var parse = String.Join(Environment.NewLine,
290 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
291 " <Fragment>",
292 " <BootstrapperApplication Id='ba'>",
293 " <Payload SourceFile='ba.dll' />",
294 " </BootstrapperApplication>",
295 " </Fragment>",
296 "</Wix>");
297
298 var expected = new[]
299 {
300 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
301 " <Fragment>",
302 " <BootstrapperApplication Id=\"ba\">",
303 " ",
304 " ",
305 "<BootstrapperApplicationDll SourceFile=\"ba.dll\" DpiAwareness=\"unaware\" />",
306 "</BootstrapperApplication>",
307 " </Fragment>",
308 "</Wix>"
309 };
310
311 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
312
313 var messaging = new MockMessaging();
314 var converter = new WixConverter(messaging, 2, null, null);
315
316 var errors = converter.ConvertDocument(document);
317 Assert.Equal(3, errors);
318
319 var actualLines = UnformattedDocumentLines(document);
320 WixAssert.CompareLineByLine(expected, actualLines);
321 }
322
323 [Fact]
324 public void DoesntSetDpiUnawareFromV4()
325 {
326 var parse = String.Join(Environment.NewLine,
327 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
328 " <Fragment>",
329 " <BootstrapperApplication Id='ba' />",
330 " </Fragment>",
331 "</Wix>");
332
333 var expected = new[]
334 {
335 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
336 " <Fragment>",
337 " <BootstrapperApplication Id=\"ba\" />",
338 " </Fragment>",
339 "</Wix>"
340 };
341
342 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
343
344 var messaging = new MockMessaging();
345 var converter = new WixConverter(messaging, 2, null, null);
346
347 var errors = converter.ConvertDocument(document);
348 Assert.Equal(0, errors);
349
350 var actualLines = UnformattedDocumentLines(document);
351 WixAssert.CompareLineByLine(expected, actualLines);
352 }
353
354 [Fact]
355 public void KeepsDpiAwarenessFromV4()
356 {
357 var parse = String.Join(Environment.NewLine,
358 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
359 " <Fragment>",
360 " <BootstrapperApplication Id='ba' SourceFile='ba.dll' DpiAwareness='system' />",
361 " </Fragment>",
362 "</Wix>");
363
364 var expected = new[]
365 {
366 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
367 " <Fragment>",
368 " <BootstrapperApplication Id=\"ba\">",
369 "<BootstrapperApplicationDll SourceFile=\"ba.dll\" DpiAwareness=\"system\" />",
370 "</BootstrapperApplication>",
371 " </Fragment>",
372 "</Wix>"
373 };
374
375 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
376
377 var messaging = new MockMessaging();
378 var converter = new WixConverter(messaging, 2, null, null);
379
380 var errors = converter.ConvertDocument(document);
381 Assert.Equal(1, errors);
382
383 var actualLines = UnformattedDocumentLines(document);
384 WixAssert.CompareLineByLine(expected, actualLines);
385 }
386
387 [Fact]
388 public void RemovesBalUseUILanguages()
389 {
390 var parse = String.Join(Environment.NewLine,
391 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:bal='http://schemas.microsoft.com/wix/BalExtension'>",
392 " <Fragment>",
393 " <BootstrapperApplication Id='ba' bal:UseUILanguages='true' />",
394 " </Fragment>",
395 "</Wix>");
396
397 var expected = new[]
398 {
399 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
400 " <Fragment>",
401 " <BootstrapperApplication Id=\"ba\" />",
402 " </Fragment>",
403 "</Wix>"
404 };
405
406 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
407
408 var messaging = new MockMessaging();
409 var converter = new WixConverter(messaging, 2, null, null);
410
411 var errors = converter.ConvertDocument(document);
412 Assert.Equal(5, errors);
413
414 var actualLines = UnformattedDocumentLines(document);
415 WixAssert.CompareLineByLine(expected, actualLines);
416 }
417 }
418}
diff --git a/src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs b/src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs
new file mode 100644
index 00000000..d3f65aeb
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs
@@ -0,0 +1,297 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class ConditionFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixControlCondition()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
20 " <Fragment>",
21 " <UI>",
22 " <Dialog Id='Dlg1'>",
23 " <Control Id='Control1'>",
24 " <Condition Action='disable'>x=y</Condition>",
25 " <Condition Action='hide'>a&lt;>b</Condition>",
26 " </Control>",
27 " </Dialog>",
28 " </UI>",
29 " </Fragment>",
30 "</Wix>");
31
32 var expected = new[]
33 {
34 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
35 " <Fragment>",
36 " <UI>",
37 " <Dialog Id=\"Dlg1\">",
38 " <Control Id=\"Control1\" DisableCondition=\"x=y\" HideCondition=\"a&lt;&gt;b\">",
39 " ",
40 " ",
41 " </Control>",
42 " </Dialog>",
43 " </UI>",
44 " </Fragment>",
45 "</Wix>"
46 };
47
48 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
49
50 var messaging = new MockMessaging();
51 var converter = new WixConverter(messaging, 2, null, null);
52
53 var errors = converter.ConvertDocument(document);
54 Assert.Equal(4, errors);
55
56 var actualLines = UnformattedDocumentLines(document);
57 WixAssert.CompareLineByLine(expected, actualLines);
58 }
59
60 [Fact]
61 public void FixPublishCondition()
62 {
63 var parse = String.Join(Environment.NewLine,
64 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
65 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
66 " <Fragment>",
67 " <UI>",
68 " <Dialog Id='Dlg1'>",
69 " <Control Id='Control1'>",
70 " <Publish Value='abc'>1&lt;2</Publish>",
71 " <Publish Value='gone'>1</Publish>",
72 " </Control>",
73 " </Dialog>",
74 " </UI>",
75 " </Fragment>",
76 "</Wix>");
77
78 var expected = new[]
79 {
80 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
81 " <Fragment>",
82 " <UI>",
83 " <Dialog Id=\"Dlg1\">",
84 " <Control Id=\"Control1\">",
85 " <Publish Value=\"abc\" Condition=\"1&lt;2\" />",
86 " <Publish Value=\"gone\" />",
87 " </Control>",
88 " </Dialog>",
89 " </UI>",
90 " </Fragment>",
91 "</Wix>"
92 };
93
94 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
95
96 var messaging = new MockMessaging();
97 var converter = new WixConverter(messaging, 2, null, null);
98
99 var errors = converter.ConvertDocument(document);
100 Assert.Equal(5, errors);
101
102 var actualLines = UnformattedDocumentLines(document);
103 WixAssert.CompareLineByLine(expected, actualLines);
104 }
105
106 [Fact]
107 public void FixComponentCondition()
108 {
109 var parse = String.Join(Environment.NewLine,
110 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
111 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
112 " <Fragment>",
113 " <Component Id='Comp1' Directory='ApplicationFolder'>",
114 " <Condition>1&lt;2</Condition>",
115 " </Component>",
116 " </Fragment>",
117 "</Wix>");
118
119 var expected = new[]
120 {
121 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
122 " <Fragment>",
123 " <Component Id=\"Comp1\" Directory=\"ApplicationFolder\" Condition=\"1&lt;2\">",
124 " ",
125 " </Component>",
126 " </Fragment>",
127 "</Wix>"
128 };
129
130 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
131
132 var messaging = new MockMessaging();
133 var converter = new WixConverter(messaging, 2, null, null);
134
135 var errors = converter.ConvertDocument(document);
136 Assert.Equal(3, errors);
137
138 var actualLines = UnformattedDocumentLines(document);
139 WixAssert.CompareLineByLine(expected, actualLines);
140 }
141
142 [Fact]
143 public void FixFeatureCondition()
144 {
145 var parse = String.Join(Environment.NewLine,
146 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
147 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
148 " <Fragment>",
149 " <Feature Id='Feature1'>",
150 " <Condition Level='0'>PROP = 1</Condition>",
151 " </Feature>",
152 " </Fragment>",
153 "</Wix>");
154
155 var expected = new[]
156 {
157 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
158 " <Fragment>",
159 " <Feature Id=\"Feature1\">",
160 " <Level Value=\"0\" Condition=\"PROP = 1\" />",
161 " </Feature>",
162 " </Fragment>",
163 "</Wix>"
164 };
165
166 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
167
168 var messaging = new MockMessaging();
169 var converter = new WixConverter(messaging, 2, null, null);
170
171 var errors = converter.ConvertDocument(document);
172 Assert.Equal(3, errors);
173
174 var actualLines = UnformattedDocumentLines(document);
175 WixAssert.CompareLineByLine(expected, actualLines);
176 }
177
178 [Fact]
179 public void FixLaunchCondition()
180 {
181 var parse = String.Join(Environment.NewLine,
182 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
183 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
184 " <Fragment>",
185 " <Condition Message='Stop the install'>",
186 " 1&lt;2",
187 " </Condition>",
188 " <Condition Message='Do not stop'>",
189 " 1=2",
190 " </Condition>",
191 " </Fragment>",
192 "</Wix>");
193
194 var expected = new[]
195 {
196 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
197 " <Fragment>",
198 " <Launch Condition=\"1&lt;2\" Message=\"Stop the install\" />",
199 " <Launch Condition=\"1=2\" Message=\"Do not stop\" />",
200 " </Fragment>",
201 "</Wix>"
202 };
203
204 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
205
206 var messaging = new MockMessaging();
207 var converter = new WixConverter(messaging, 2, null, null);
208
209 var errors = converter.ConvertDocument(document);
210 Assert.Equal(4, errors);
211
212 var actualLines = UnformattedDocumentLines(document);
213 WixAssert.CompareLineByLine(expected, actualLines);
214 }
215
216 [Fact]
217 public void FixLaunchConditionInProduct()
218 {
219 var parse = String.Join(Environment.NewLine,
220 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
221 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
222 " <Product>",
223 " <Package />",
224 " <Condition Message='Stop the install'>",
225 " 1&lt;2",
226 " </Condition>",
227 " <Condition Message='Do not stop'>",
228 " 1=2",
229 " </Condition>",
230 " </Product>",
231 "</Wix>");
232
233 var expected = new[]
234 {
235 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
236 " <Package Compressed=\"no\">",
237 " ",
238 " <Launch Condition=\"1&lt;2\" Message=\"Stop the install\" />",
239 " <Launch Condition=\"1=2\" Message=\"Do not stop\" />",
240 " </Package>",
241 "</Wix>"
242 };
243
244 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
245
246 var messaging = new MockMessaging();
247 var converter = new WixConverter(messaging, 2, null, null);
248
249 var errors = converter.ConvertDocument(document);
250 Assert.Equal(6, errors);
251
252 var actualLines = UnformattedDocumentLines(document);
253 WixAssert.CompareLineByLine(expected, actualLines);
254 }
255
256 [Fact]
257 public void FixPermissionExCondition()
258 {
259 var parse = String.Join(Environment.NewLine,
260 "<!-- comment -->",
261 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
262 " <Fragment>",
263 " <Component Id='Comp1' Guid='*' Directory='ApplicationFolder'>",
264 " <PermissionEx Sddl='sddl'>",
265 " <Condition>1&lt;2</Condition>",
266 " </PermissionEx>",
267 " </Component>",
268 " </Fragment>",
269 "</Wix>");
270
271 var expected = new[]
272 {
273 "<!-- comment -->",
274 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
275 " <Fragment>",
276 " <Component Id=\"Comp1\" Directory=\"ApplicationFolder\">",
277 " <PermissionEx Sddl=\"sddl\" Condition=\"1&lt;2\">",
278 " ",
279 " </PermissionEx>",
280 " </Component>",
281 " </Fragment>",
282 "</Wix>"
283 };
284
285 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
286
287 var messaging = new MockMessaging();
288 var converter = new WixConverter(messaging, 2, null, null);
289
290 var errors = converter.ConvertDocument(document);
291 Assert.Equal(3, errors);
292
293 var actualLines = UnformattedDocumentLines(document);
294 WixAssert.CompareLineByLine(expected, actualLines);
295 }
296 }
297}
diff --git a/src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs b/src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs
new file mode 100644
index 00000000..13df9da7
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs
@@ -0,0 +1,449 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixToolset.Converters;
8 using WixToolsetTest.Converters.Mocks;
9 using Xunit;
10
11 public class ConverterFixture : BaseConverterFixture
12 {
13 private static readonly XNamespace Wix4Namespace = "http://wixtoolset.org/schemas/v4/wxs";
14
15 [Fact]
16 public void EnsuresNoDeclaration()
17 {
18 var parse = String.Join(Environment.NewLine,
19 "<?xml version='1.0' encoding='utf-8'?>",
20 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
21 " <Fragment />",
22 "</Wix>");
23
24 var expected = String.Join(Environment.NewLine,
25 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
26 " <Fragment />",
27 "</Wix>");
28
29 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
30
31 var messaging = new MockMessaging();
32 var converter = new WixConverter(messaging, 2, null, null);
33
34 var errors = converter.ConvertDocument(document);
35
36 var actual = UnformattedDocumentString(document);
37
38 Assert.Equal(1, errors);
39 Assert.Equal(expected, actual);
40 }
41
42 [Fact]
43 public void EnsuresDeclarationWhenIgnored()
44 {
45 var parse = String.Join(Environment.NewLine,
46 "<?xml version='1.0' encoding='utf-16'?>",
47 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
48 " <Fragment />",
49 "</Wix>");
50
51 var expected = String.Join(Environment.NewLine,
52 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
53 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
54 " <Fragment />",
55 "</Wix>");
56
57 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
58
59 var messaging = new MockMessaging();
60 var converter = new WixConverter(messaging, 2, ignoreErrors: new[] { "DeclarationPresent" } );
61
62 var errors = converter.ConvertDocument(document);
63
64 var actual = UnformattedDocumentString(document, omitXmlDeclaration: false);
65
66 Assert.Equal(0, errors);
67 Assert.Equal(expected, actual);
68 }
69
70 [Fact]
71 public void CanConvertMainNamespace()
72 {
73 var parse = String.Join(Environment.NewLine,
74 "<?xml version='1.0' encoding='utf-8'?>",
75 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
76 " <Fragment />",
77 "</Wix>");
78
79 var expected = String.Join(Environment.NewLine,
80 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
81 " <Fragment />",
82 "</Wix>");
83
84 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
85
86 var messaging = new MockMessaging();
87 var converter = new WixConverter(messaging, 2, null, null);
88
89 var errors = converter.ConvertDocument(document);
90
91 var actual = UnformattedDocumentString(document);
92
93 Assert.Equal(2, errors);
94 //Assert.Equal(Wix4Namespace, document.Root.GetDefaultNamespace());
95 Assert.Equal(expected, actual);
96 }
97
98 [Fact]
99 public void CanConvertNamedMainNamespace()
100 {
101 var parse = String.Join(Environment.NewLine,
102 "<?xml version='1.0' encoding='utf-8'?>",
103 "<w:Wix xmlns:w='http://schemas.microsoft.com/wix/2006/wi'>",
104 " <w:Fragment />",
105 "</w:Wix>");
106
107 var expected = String.Join(Environment.NewLine,
108 "<w:Wix xmlns:w=\"http://wixtoolset.org/schemas/v4/wxs\">",
109 " <w:Fragment />",
110 "</w:Wix>");
111
112 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
113
114 var messaging = new MockMessaging();
115 var converter = new WixConverter(messaging, 2, null, null);
116
117 var errors = converter.ConvertDocument(document);
118
119 var actual = UnformattedDocumentString(document);
120
121 Assert.Equal(2, errors);
122 Assert.Equal(expected, actual);
123 Assert.Equal(Wix4Namespace, document.Root.GetNamespaceOfPrefix("w"));
124 }
125
126 [Fact]
127 public void CanConvertNonWixDefaultNamespace()
128 {
129 var parse = String.Join(Environment.NewLine,
130 "<?xml version='1.0' encoding='utf-8'?>",
131 "<w:Wix xmlns:w='http://schemas.microsoft.com/wix/2006/wi' xmlns='http://schemas.microsoft.com/wix/UtilExtension'>",
132 " <w:Fragment>",
133 " <Test />",
134 " </w:Fragment>",
135 "</w:Wix>");
136
137 var expected = String.Join(Environment.NewLine,
138 "<w:Wix xmlns:w=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
139 " <w:Fragment>",
140 " <Test />",
141 " </w:Fragment>",
142 "</w:Wix>");
143
144 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
145
146 var messaging = new MockMessaging();
147 var converter = new WixConverter(messaging, 2, null, null);
148
149 var errors = converter.ConvertDocument(document);
150
151 var actual = UnformattedDocumentString(document);
152
153 Assert.Equal(expected, actual);
154 Assert.Equal(3, errors);
155 Assert.Equal(Wix4Namespace, document.Root.GetNamespaceOfPrefix("w"));
156 Assert.Equal("http://wixtoolset.org/schemas/v4/wxs/util", document.Root.GetDefaultNamespace());
157 }
158
159 [Fact]
160 public void CanRemoveUnusedNamespaces()
161 {
162 var parse = String.Join(Environment.NewLine,
163 "<?xml version='1.0' encoding='utf-8'?>",
164 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>",
165 " <Fragment />",
166 "</Wix>");
167
168 var expected = String.Join(Environment.NewLine,
169 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
170 " <Fragment />",
171 "</Wix>");
172
173 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
174
175 var messaging = new MockMessaging();
176 var converter = new WixConverter(messaging, 2, null, null);
177
178 var errors = converter.ConvertDocument(document);
179
180 var actual = UnformattedDocumentString(document);
181
182 Assert.Equal(4, errors);
183 Assert.Equal(expected, actual);
184 Assert.Equal(Wix4Namespace, document.Root.GetDefaultNamespace());
185 }
186
187 [Fact]
188 public void CanConvertMissingWixNamespace()
189 {
190 var parse = String.Join(Environment.NewLine,
191 "<?xml version='1.0' encoding='utf-8'?>",
192 "<Wix>",
193 " <Fragment />",
194 "</Wix>");
195
196 var expected = String.Join(Environment.NewLine,
197 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
198 " <Fragment />",
199 "</Wix>");
200
201 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
202
203 var messaging = new MockMessaging();
204 var converter = new WixConverter(messaging, 2, null, null);
205
206 var errors = converter.ConvertDocument(document);
207
208 var actual = UnformattedDocumentString(document);
209
210 Assert.Equal(2, errors);
211 Assert.Equal(expected, actual);
212 Assert.Equal(Wix4Namespace, document.Root.GetDefaultNamespace());
213 }
214
215 [Fact]
216 public void CanConvertMissingIncludeNamespace()
217 {
218 var parse = String.Join(Environment.NewLine,
219 "<?xml version='1.0' encoding='utf-8'?>",
220 "<Include>",
221 " <?define Version = 1.2.3 ?>",
222 " <Fragment>",
223 " <DirectoryRef Id='TARGETDIR'>",
224 " <Directory Id='ANOTHERDIR' Name='Another' />",
225 " </DirectoryRef>",
226 " </Fragment>",
227 "</Include>");
228
229 var expected = String.Join(Environment.NewLine,
230 "<Include xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
231 " <?define Version = 1.2.3 ?>",
232 " <Fragment>",
233 " <DirectoryRef Id=\"TARGETDIR\">",
234 " <Directory Id=\"ANOTHERDIR\" Name=\"Another\" />",
235 " </DirectoryRef>",
236 " </Fragment>",
237 "</Include>");
238
239 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
240
241 var messaging = new MockMessaging();
242 var converter = new WixConverter(messaging, 2, null, null);
243
244 var errors = converter.ConvertDocument(document);
245
246 var actual = UnformattedDocumentString(document);
247
248 Assert.Equal(2, errors);
249 Assert.Equal(expected, actual);
250 Assert.Equal(Wix4Namespace, document.Root.GetDefaultNamespace());
251 }
252
253 [Fact]
254 public void CanConvertAnonymousFile()
255 {
256 var parse = String.Join(Environment.NewLine,
257 "<?xml version='1.0' encoding='utf-8'?>",
258 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
259 " <File Source='path\\to\\foo.txt' />",
260 "</Wix>");
261
262 var expected = String.Join(Environment.NewLine,
263 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
264 " <File Id=\"foo.txt\" Source=\"path\\to\\foo.txt\" />",
265 "</Wix>");
266
267 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
268
269 var messaging = new MockMessaging();
270 var converter = new WixConverter(messaging, 2, null, null);
271
272 var errors = converter.ConvertDocument(document);
273
274 var actual = UnformattedDocumentString(document);
275
276 Assert.Equal(3, errors);
277 Assert.Equal(expected, actual);
278 }
279
280 [Fact]
281 public void CanConvertShortNameDirectoryWithoutName()
282 {
283 var parse = String.Join(Environment.NewLine,
284 "<?xml version='1.0' encoding='utf-8'?>",
285 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
286 " <Directory ShortName='iamshort' />",
287 "</Wix>");
288
289 var expected = String.Join(Environment.NewLine,
290 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
291 " <Directory Name=\"iamshort\" />",
292 "</Wix>");
293
294 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
295
296 var messaging = new MockMessaging();
297 var converter = new WixConverter(messaging, 2, null, null);
298
299 var errors = converter.ConvertDocument(document);
300
301 var actual = UnformattedDocumentString(document);
302
303 Assert.Equal(2, errors);
304 Assert.Equal(expected, actual);
305 }
306
307 [Fact]
308 public void CanConvertCatalogElement()
309 {
310 var parse = String.Join(Environment.NewLine,
311 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
312 " <Catalog Id='idCatalog' SourceFile='path\\to\\catalog.cat' />",
313 "</Wix>");
314
315 var expected = String.Join(Environment.NewLine,
316 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
317 " ",
318 "</Wix>");
319
320 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
321
322 var messaging = new MockMessaging();
323 var converter = new WixConverter(messaging, 2, null, null);
324
325 var errors = converter.ConvertDocument(document);
326
327 var actual = UnformattedDocumentString(document);
328
329 Assert.Equal(1, errors);
330 Assert.Equal(expected, actual);
331 }
332
333 [Fact]
334 public void CanConvertSuppressSignatureValidationNo()
335 {
336 var parse = String.Join(Environment.NewLine,
337 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
338 " <MsiPackage SuppressSignatureValidation='no' />",
339 "</Wix>");
340
341 var expected = String.Join(Environment.NewLine,
342 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
343 " <MsiPackage />",
344 "</Wix>");
345
346 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
347
348 var messaging = new MockMessaging();
349 var converter = new WixConverter(messaging, 2, null, null);
350
351 var errors = converter.ConvertDocument(document);
352
353 var actual = UnformattedDocumentString(document);
354
355 Assert.Equal(1, errors);
356 Assert.Equal(expected, actual);
357 }
358
359 [Fact]
360 public void CanConvertSuppressSignatureValidationYes()
361 {
362 var parse = String.Join(Environment.NewLine,
363 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
364 " <Payload SuppressSignatureValidation='yes' />",
365 "</Wix>");
366
367 var expected = String.Join(Environment.NewLine,
368 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
369 " <Payload />",
370 "</Wix>");
371
372 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
373
374 var messaging = new MockMessaging();
375 var converter = new WixConverter(messaging, 2, null, null);
376
377 var errors = converter.ConvertDocument(document);
378
379 var actual = UnformattedDocumentString(document);
380
381 Assert.Equal(1, errors);
382 Assert.Equal(expected, actual);
383 }
384
385 [Fact]
386 public void CantConvertVerbTarget()
387 {
388 var parse = String.Join(Environment.NewLine,
389 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
390 " <Verb Target='anything' />",
391 "</Wix>");
392
393 var expected = String.Join(Environment.NewLine,
394 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
395 " <Verb Target=\"anything\" />",
396 "</Wix>");
397
398 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
399
400 var messaging = new MockMessaging();
401 var converter = new WixConverter(messaging, 2, null, null);
402
403 var errors = converter.ConvertDocument(document);
404
405 var actual = UnformattedDocumentString(document);
406
407 Assert.Equal(2, errors);
408 Assert.Equal(expected, actual);
409 }
410
411 [Fact]
412 public void CanConvertDeprecatedPrefix()
413 {
414 var parse = String.Join(Environment.NewLine,
415 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
416 "<Fragment>",
417 "<ComponentGroup Id=\"$(loc.Variable)\" />",
418 "<ComponentGroup Id=\"$$(loc.Variable)\" />",
419 "<ComponentGroup Id=\"$$$(loc.Variable)\" />",
420 "<ComponentGroup Id=\"$$$$(loc.Variable)\" />",
421 "<ComponentGroup Id=\"$$$$$(loc.Variable)\" />",
422 "</Fragment>",
423 "</Wix>");
424
425 var expected = String.Join(Environment.NewLine,
426 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
427 "<Fragment>",
428 "<ComponentGroup Id=\"!(loc.Variable)\" />",
429 "<ComponentGroup Id=\"$$(loc.Variable)\" />",
430 "<ComponentGroup Id=\"$$!(loc.Variable)\" />",
431 "<ComponentGroup Id=\"$$$$(loc.Variable)\" />",
432 "<ComponentGroup Id=\"$$$$!(loc.Variable)\" />",
433 "</Fragment>",
434 "</Wix>");
435
436 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
437
438 var messaging = new MockMessaging();
439 var converter = new WixConverter(messaging, 2, null, null);
440
441 var errors = converter.ConvertDocument(document);
442
443 var actual = UnformattedDocumentString(document);
444
445 Assert.Equal(3, errors);
446 Assert.Equal(expected, actual);
447 }
448 }
449}
diff --git a/src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs b/src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs
new file mode 100644
index 00000000..a39f6243
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs
@@ -0,0 +1,195 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Converters;
10 using WixToolset.Core;
11 using WixToolset.Core.TestPackage;
12 using WixToolsetTest.Converters.Mocks;
13 using Xunit;
14
15 public class ConverterIntegrationFixture
16 {
17 [Fact]
18 public void CanConvertPermissionExFile()
19 {
20 const string beforeFileName = "v3.wxs";
21 const string afterFileName = "v4_expected.wxs";
22 var folder = TestData.Get(@"TestData\PermissionEx");
23
24 using (var fs = new DisposableFileSystem())
25 {
26 var baseFolder = fs.GetFolder(true);
27 var targetFile = Path.Combine(baseFolder, beforeFileName);
28 File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName));
29
30 var messaging = new MockMessaging();
31 var converter = new WixConverter(messaging, 4);
32 var errors = converter.ConvertFile(targetFile, true);
33
34 Assert.Equal(8, errors);
35
36 var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n");
37 var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n");
38 Assert.Equal(expected, actual);
39
40 EnsureFixed(targetFile);
41 }
42 }
43
44 [Fact]
45 public void CanConvertSingleFile()
46 {
47 const string beforeFileName = "SingleFile.wxs";
48 const string afterFileName = "ConvertedSingleFile.wxs";
49 var folder = TestData.Get(@"TestData\SingleFile");
50
51 using (var fs = new DisposableFileSystem())
52 {
53 var baseFolder = fs.GetFolder(true);
54 var targetFile = Path.Combine(baseFolder, beforeFileName);
55 File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName));
56
57 var messaging = new MockMessaging();
58 var converter = new WixConverter(messaging, 4);
59 var errors = converter.ConvertFile(targetFile, true);
60
61 Assert.Equal(9, errors);
62
63 var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n");
64 var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n");
65 Assert.Equal(expected, actual);
66
67 EnsureFixed(targetFile);
68 }
69 }
70
71 [Fact]
72 public void CanDetectReadOnlyOutputFile()
73 {
74 const string beforeFileName = "SingleFile.wxs";
75 var folder = TestData.Get(@"TestData\SingleFile");
76
77 using (var fs = new DisposableFileSystem())
78 {
79 var baseFolder = fs.GetFolder(true);
80 var targetFile = Path.Combine(baseFolder, beforeFileName);
81 File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName));
82
83 var info = new FileInfo(targetFile);
84 info.IsReadOnly = true;
85
86 var messaging = new MockMessaging();
87 var converter = new WixConverter(messaging, 4);
88 var errors = converter.ConvertFile(targetFile, true);
89
90 Assert.Single(messaging.Messages.Where(m => m.Id == 5/*WixConverter.ConverterTestType.UnauthorizedAccessException*/));
91 }
92 }
93
94 [Fact]
95 public void RetainsPreprocessorInstructions()
96 {
97 const string beforeFileName = "Preprocessor.wxs";
98 const string afterFileName = "ConvertedPreprocessor.wxs";
99 var folder = TestData.Get(@"TestData\Preprocessor");
100
101 using (var fs = new DisposableFileSystem())
102 {
103 var baseFolder = fs.GetFolder(true);
104 var targetFile = Path.Combine(baseFolder, beforeFileName);
105 File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName));
106
107 var settingsFile = Path.Combine(folder, "wixcop.settings.xml");
108
109 var result = RunConversion(targetFile, settingsFile: settingsFile);
110 Assert.Equal(9, result.ExitCode);
111
112 var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n");
113 var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n");
114 Assert.Equal(expected, actual);
115
116 EnsureFixed(targetFile);
117 }
118 }
119
120 [Fact]
121 public void CanConvertQtExec()
122 {
123 const string beforeFileName = "v3.wxs";
124 const string afterFileName = "v4_expected.wxs";
125 var folder = TestData.Get(@"TestData\QtExec");
126
127 using (var fs = new DisposableFileSystem())
128 {
129 var baseFolder = fs.GetFolder(true);
130 var targetFile = Path.Combine(baseFolder, beforeFileName);
131 File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName));
132
133 var result = RunConversion(targetFile);
134 Assert.Equal(13, result.ExitCode);
135
136 var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n");
137 var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n");
138 Assert.Equal(expected, actual);
139
140 EnsureFixed(targetFile);
141 }
142 }
143
144 [Fact]
145 public void DetectUnconvertableQtExecCmdTimeout()
146 {
147 const string beforeFileName = "v3.wxs";
148 const string afterFileName = "v4_expected.wxs";
149 var folder = TestData.Get(@"TestData\QtExec.bad");
150
151 using (var fs = new DisposableFileSystem())
152 {
153 var baseFolder = fs.GetFolder(true);
154 var targetFile = Path.Combine(baseFolder, beforeFileName);
155 File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName));
156
157 var result = RunConversion(targetFile);
158
159 Assert.Equal(13, result.ExitCode);
160 Assert.Single(result.Messages.Where(message => message.ToString().EndsWith("(QtExecCmdTimeoutAmbiguous)")));
161
162 var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n");
163 var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n");
164 Assert.Equal(expected, actual);
165
166 // still fails because QtExecCmdTimeoutAmbiguous is unfixable
167 var result2 = RunConversion(targetFile);
168 Assert.Equal(1, result2.ExitCode);
169 }
170 }
171
172 private static WixRunnerResult RunConversion(string targetFile, bool fixErrors = true, string settingsFile = null)
173 {
174 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider().AddConverter();
175
176 var exitCode = WixRunner.Execute(new[]
177 {
178 "convert",
179 fixErrors ? null : "--dry-run",
180 String.IsNullOrEmpty(settingsFile) ? null : "-set1" + settingsFile,
181 targetFile
182 }, serviceProvider, out var messages);
183
184 return new WixRunnerResult { ExitCode = exitCode.Result, Messages = messages.ToArray() };
185 }
186
187 private static void EnsureFixed(string targetFile)
188 {
189 var messaging2 = new MockMessaging();
190 var converter2 = new WixConverter(messaging2, 4);
191 var errors2 = converter2.ConvertFile(targetFile, true);
192 Assert.Equal(0, errors2);
193 }
194 }
195}
diff --git a/src/wix/test/WixToolsetTest.Converters/CustomActionFixture.cs b/src/wix/test/WixToolsetTest.Converters/CustomActionFixture.cs
new file mode 100644
index 00000000..eafc171a
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/CustomActionFixture.cs
@@ -0,0 +1,88 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.IO;
7 using System.Xml.Linq;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class CustomActionFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void CanConvertCustomAction()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version='1.0' encoding='utf-8'?>",
19 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
20 " <CustomAction Id='Foo' BinaryKey='WixCA' DllEntry='CAQuietExec' />",
21 " <CustomAction Id='Foo' BinaryKey='WixCA_x64' DllEntry='CAQuietExec64' />",
22 " <CustomAction Id='Foo' BinaryKey='UtilCA' DllEntry='WixQuietExec' />",
23 " <CustomAction Id='Foo' BinaryKey='UtilCA_x64' DllEntry='WixQuietExec64' />",
24 "</Wix>");
25
26 var expected = String.Join(Environment.NewLine,
27 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
28 " <CustomAction Id=\"Foo\" DllEntry=\"WixQuietExec\" BinaryRef=\"Wix4UtilCA_X86\" />",
29 " <CustomAction Id=\"Foo\" DllEntry=\"WixQuietExec64\" BinaryRef=\"Wix4UtilCA_X64\" />",
30 " <CustomAction Id=\"Foo\" DllEntry=\"WixQuietExec\" BinaryRef=\"Wix4UtilCA_X86\" />",
31 " <CustomAction Id=\"Foo\" DllEntry=\"WixQuietExec64\" BinaryRef=\"Wix4UtilCA_X64\" />",
32 "</Wix>");
33
34 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
35
36 var messaging = new MockMessaging();
37 var converter = new WixConverter(messaging, 2, null, null);
38
39 var errors = converter.ConvertDocument(document);
40
41 var actual = UnformattedDocumentString(document);
42
43 Assert.Equal(11, errors);
44 Assert.Equal(expected, actual);
45 }
46
47 [Fact]
48 public void CanConvertCustomActionScript()
49 {
50 var parse = String.Join(Environment.NewLine,
51 "<?xml version='1.0' encoding='utf-8'?>",
52 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
53 " <CustomAction Id='Foo' Script='jscript'>",
54 " function() {",
55 " var x = 0;",
56 " return x;",
57 " }",
58 " </CustomAction>",
59 "</Wix>");
60
61 var expected = String.Join(Environment.NewLine,
62 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
63 " <CustomAction Id=\"Foo\" Script=\"jscript\" ScriptSourceFile=\"Foo.js\" />",
64 "</Wix>");
65
66 var expectedScript = String.Join("\n",
67 "function() {",
68 " var x = 0;",
69 " return x;",
70 " }");
71
72 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
73
74 var messaging = new MockMessaging();
75 var converter = new WixConverter(messaging, 2, null, null);
76
77 var errors = converter.ConvertDocument(document);
78
79 var actual = UnformattedDocumentString(document);
80
81 Assert.Equal(2, errors);
82 Assert.Equal(expected, actual);
83
84 var script = File.ReadAllText("Foo.js");
85 Assert.Equal(expectedScript, script);
86 }
87 }
88}
diff --git a/src/wix/test/WixToolsetTest.Converters/CustomTableFixture.cs b/src/wix/test/WixToolsetTest.Converters/CustomTableFixture.cs
new file mode 100644
index 00000000..2b81a863
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/CustomTableFixture.cs
@@ -0,0 +1,363 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class CustomTableFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixCustomTableCategoryAndModularization()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
20 " <Fragment>",
21 " <CustomTable Id='Custom1'>",
22 " <Column Id='Column1' Type='string' Category='Text' Modularize='Column' />",
23 " </CustomTable>",
24 " </Fragment>",
25 "</Wix>");
26
27 var expected = new[]
28 {
29 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
30 " <Fragment>",
31 " <CustomTable Id=\"Custom1\">",
32 " <Column Id=\"Column1\" Type=\"string\" Category=\"text\" Modularize=\"column\" />",
33 " </CustomTable>",
34 " </Fragment>",
35 "</Wix>"
36 };
37
38 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
39
40 var messaging = new MockMessaging();
41 var converter = new WixConverter(messaging, 2, null, null);
42
43 var errors = converter.ConvertDocument(document);
44 Assert.Equal(4, errors);
45
46 var actualLines = UnformattedDocumentLines(document);
47 WixAssert.CompareLineByLine(expected, actualLines);
48 }
49
50 [Fact]
51 public void FixCustomRowTextValue()
52 {
53 var parse = String.Join(Environment.NewLine,
54 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
55 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
56 " <Fragment>",
57 " <CustomTable Id='Custom1'>",
58 " <Row>",
59 " <Data Id='Column1'>",
60 " Some value",
61 " </Data>",
62 " </Row>",
63 " </CustomTable>",
64 " </Fragment>",
65 "</Wix>");
66
67 var expected = new[]
68 {
69 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
70 " <Fragment>",
71 " <CustomTable Id=\"Custom1\">",
72 " <Row>",
73 " <Data Id=\"Column1\" Value=\"Some value\" />",
74 " </Row>",
75 " </CustomTable>",
76 " </Fragment>",
77 "</Wix>"
78 };
79
80 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
81
82 var messaging = new MockMessaging();
83 var converter = new WixConverter(messaging, 2, null, null);
84
85 var errors = converter.ConvertDocument(document);
86 Assert.Equal(4, errors);
87
88 var actualLines = UnformattedDocumentLines(document);
89 WixAssert.CompareLineByLine(expected, actualLines);
90 }
91
92 [Fact]
93 public void FixCustomRowCdataValue()
94 {
95 var parse = String.Join(Environment.NewLine,
96 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
97 " <Fragment>",
98 " <CustomTable Id='Custom1'>",
99 " <Row>",
100 " <Data Id='Column1'>",
101 " <![CDATA[",
102 " Some value",
103 " ]]>",
104 " </Data>",
105 " </Row>",
106 " </CustomTable>",
107 " </Fragment>",
108 "</Wix>");
109
110 var expected = new[]
111 {
112 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
113 " <Fragment>",
114 " <CustomTable Id=\"Custom1\">",
115 " <Row>",
116 " <Data Id=\"Column1\" Value=\"Some value\" />",
117 " </Row>",
118 " </CustomTable>",
119 " </Fragment>",
120 "</Wix>"
121 };
122
123 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
124
125 var messaging = new MockMessaging();
126 var converter = new WixConverter(messaging, 2, null, null);
127
128 var errors = converter.ConvertDocument(document);
129 Assert.Equal(3, errors);
130
131 var actualLines = UnformattedDocumentLines(document);
132 WixAssert.CompareLineByLine(expected, actualLines);
133 }
134
135 [Fact]
136 public void FixCustomRowWithoutValue()
137 {
138 var parse = String.Join(Environment.NewLine,
139 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
140 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
141 " <Fragment>",
142 " <CustomTable Id='Custom1'>",
143 " <Row Id='Column1'></Row>",
144 " </CustomTable>",
145 " </Fragment>",
146 "</Wix>");
147
148 var expected = new[]
149 {
150 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
151 " <Fragment>",
152 " <CustomTable Id=\"Custom1\">",
153 " <Row Id=\"Column1\"></Row>",
154 " </CustomTable>",
155 " </Fragment>",
156 "</Wix>"
157 };
158
159 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
160
161 var messaging = new MockMessaging();
162 var converter = new WixConverter(messaging, 2, null, null);
163
164 var errors = converter.ConvertDocument(document);
165 Assert.Equal(3, errors);
166
167 var actualLines = UnformattedDocumentLines(document);
168 WixAssert.CompareLineByLine(expected, actualLines);
169 }
170
171 [Fact]
172 public void CanConvertBundleCustomTableBootstrapperApplicationData()
173 {
174 var parse = String.Join(Environment.NewLine,
175 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
176 " <CustomTable Id='FgAppx' BootstrapperApplicationData='yes'>",
177 " <Column Id='Column1' PrimaryKey='yes' Type='string' Width='0' Category='text' Description='The first custom column.' />",
178 " <Row>",
179 " <Data Column='Column1'>Row1</Data>",
180 " </Row>",
181 " </CustomTable>",
182 "</Wix>");
183
184 var expected = String.Join(Environment.NewLine,
185 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
186 " <BundleCustomData Id=\"FgAppx\">",
187 " <BundleAttributeDefinition Id=\"Column1\" />",
188 " <BundleElement>",
189 " <BundleAttribute Id=\"Column1\" Value=\"Row1\" />",
190 " </BundleElement>",
191 " </BundleCustomData>",
192 "</Wix>");
193
194 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
195
196 var messaging = new MockMessaging();
197 var converter = new WixConverter(messaging, 2, customTableTarget: CustomTableTarget.Bundle);
198
199 var errors = converter.ConvertDocument(document);
200
201 var actual = UnformattedDocumentString(document);
202
203 Assert.Equal(2, errors);
204 Assert.Equal(expected, actual);
205 }
206
207 [Fact]
208 public void CanConvertBundleCustomTableRef()
209 {
210 var parse = String.Join(Environment.NewLine,
211 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
212 " <CustomTable Id='FgAppx'>",
213 " <Row>",
214 " <Data Column='Column1'>Row1</Data>",
215 " </Row>",
216 " </CustomTable>",
217 "</Wix>");
218
219 var expected = String.Join(Environment.NewLine,
220 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
221 " <BundleCustomDataRef Id=\"FgAppx\">",
222 " <BundleElement>",
223 " <BundleAttribute Id=\"Column1\" Value=\"Row1\" />",
224 " </BundleElement>",
225 " </BundleCustomDataRef>",
226 "</Wix>");
227
228 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
229
230 var messaging = new MockMessaging();
231 var converter = new WixConverter(messaging, 2, customTableTarget: CustomTableTarget.Bundle);
232
233 var errors = converter.ConvertDocument(document);
234
235 var actual = UnformattedDocumentString(document);
236
237 Assert.Equal(2, errors);
238 Assert.Equal(expected, actual);
239 }
240
241 [Fact]
242 public void CanConvertMsiCustomTableBootstrapperApplicationData()
243 {
244 var parse = String.Join(Environment.NewLine,
245 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
246 " <CustomTable Id='FgAppx' BootstrapperApplicationData='yes'>",
247 " <Column Id='Column1' PrimaryKey='yes' Type='string' Width='0' Category='text' Description='The first custom column.' />",
248 " <Row>",
249 " <Data Column='Column1'>Row1</Data>",
250 " </Row>",
251 " </CustomTable>",
252 "</Wix>");
253
254 var expected = String.Join(Environment.NewLine,
255 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
256 " <CustomTable Id=\"FgAppx\" Unreal=\"yes\">",
257 " <Column Id=\"Column1\" PrimaryKey=\"yes\" Type=\"string\" Width=\"0\" Category=\"text\" Description=\"The first custom column.\" />",
258 " <Row>",
259 " <Data Column=\"Column1\" Value=\"Row1\" />",
260 " </Row>",
261 " </CustomTable>",
262 "</Wix>");
263
264 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
265
266 var messaging = new MockMessaging();
267 var converter = new WixConverter(messaging, 2, customTableTarget: CustomTableTarget.Msi);
268
269 var errors = converter.ConvertDocument(document);
270
271 var actual = UnformattedDocumentString(document);
272
273 Assert.Equal(2, errors);
274 Assert.Equal(expected, actual);
275 }
276
277 [Fact]
278 public void CanConvertMsiCustomTableRef()
279 {
280 var parse = String.Join(Environment.NewLine,
281 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
282 " <CustomTable Id='FgAppx'>",
283 " <Row>",
284 " <Data Column='Column1'>Row1</Data>",
285 " </Row>",
286 " </CustomTable>",
287 "</Wix>");
288
289 var expected = String.Join(Environment.NewLine,
290 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
291 " <CustomTableRef Id=\"FgAppx\">",
292 " <Row>",
293 " <Data Column=\"Column1\" Value=\"Row1\" />",
294 " </Row>",
295 " </CustomTableRef>",
296 "</Wix>");
297
298 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
299
300 var messaging = new MockMessaging();
301 var converter = new WixConverter(messaging, 2, customTableTarget: CustomTableTarget.Msi);
302
303 var errors = converter.ConvertDocument(document);
304
305 var actual = UnformattedDocumentString(document);
306
307 Assert.Equal(2, errors);
308 Assert.Equal(expected, actual);
309 }
310
311 [Fact]
312 public void CanDetectAmbiguousCustomTableBootstrapperApplicationData()
313 {
314 var parse = String.Join(Environment.NewLine,
315 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
316 " <CustomTable Id='FgAppx' BootstrapperApplicationData='yes' />",
317 "</Wix>");
318
319 var expected = String.Join(Environment.NewLine,
320 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
321 " <CustomTable Id=\"FgAppx\" BootstrapperApplicationData=\"yes\" />",
322 "</Wix>");
323
324 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
325
326 var messaging = new MockMessaging();
327 var converter = new WixConverter(messaging, 2);
328
329 var errors = converter.ConvertDocument(document);
330
331 var actual = UnformattedDocumentString(document);
332
333 Assert.Equal(1, errors);
334 Assert.Equal(expected, actual);
335 }
336
337 [Fact]
338 public void CanRemoveBootstrapperApplicationDataFromRealCustomTable()
339 {
340 var parse = String.Join(Environment.NewLine,
341 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
342 " <CustomTable Id='FgAppx' BootstrapperApplicationData='no' />",
343 "</Wix>");
344
345 var expected = String.Join(Environment.NewLine,
346 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
347 " <CustomTable Id=\"FgAppx\" />",
348 "</Wix>");
349
350 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
351
352 var messaging = new MockMessaging();
353 var converter = new WixConverter(messaging, 2);
354
355 var errors = converter.ConvertDocument(document);
356
357 var actual = UnformattedDocumentString(document);
358
359 Assert.Equal(1, errors);
360 Assert.Equal(expected, actual);
361 }
362 }
363}
diff --git a/src/wix/test/WixToolsetTest.Converters/DependencyFixture.cs b/src/wix/test/WixToolsetTest.Converters/DependencyFixture.cs
new file mode 100644
index 00000000..41ded927
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/DependencyFixture.cs
@@ -0,0 +1,178 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class DependencyFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixPackageDependencyProvides()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:dep='http://schemas.microsoft.com/wix/DependencyExtension'>",
20 " <Fragment>",
21 " <ComponentGroup Id='Group1' Directory='INSTALLFOLDER'>",
22 " <Component>",
23 " <dep:Provides Key='abc' />",
24 " </Component>",
25 " </ComponentGroup>",
26 " </Fragment>",
27 "</Wix>");
28
29 var expected = new[]
30 {
31 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:dep=\"http://wixtoolset.org/schemas/v4/wxs/dependency\">",
32 " <Fragment>",
33 " <ComponentGroup Id=\"Group1\" Directory=\"INSTALLFOLDER\">",
34 " <Component>",
35 " <Provides Key=\"abc\" dep:Check=\"yes\" />",
36 " </Component>",
37 " </ComponentGroup>",
38 " </Fragment>",
39 "</Wix>"
40 };
41
42 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
43
44 var messaging = new MockMessaging();
45 var converter = new WixConverter(messaging, 2, null, null);
46
47 var errors = converter.ConvertDocument(document);
48 Assert.Equal(5, errors);
49
50 var actualLines = UnformattedDocumentLines(document);
51 WixAssert.CompareLineByLine(expected, actualLines);
52 }
53
54 [Fact]
55 public void FixPackageDependencyRequires()
56 {
57 var parse = String.Join(Environment.NewLine,
58 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
59 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:dep='http://schemas.microsoft.com/wix/DependencyExtension'>",
60 " <Fragment>",
61 " <ComponentGroup Id='Group1' Directory='INSTALLFOLDER'>",
62 " <Component>",
63 " <dep:Provides Key='abc'>",
64 " <dep:Requires Key='xyz' />",
65 " </dep:Provides>",
66 " </Component>",
67 " </ComponentGroup>",
68 " </Fragment>",
69 "</Wix>");
70
71 var expected = new[]
72 {
73 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:dep=\"http://wixtoolset.org/schemas/v4/wxs/dependency\">",
74 " <Fragment>",
75 " <ComponentGroup Id=\"Group1\" Directory=\"INSTALLFOLDER\">",
76 " <Component>",
77 " <Provides Key=\"abc\" dep:Check=\"yes\">",
78 " <Requires Key=\"xyz\" dep:Enforce=\"yes\" />",
79 " </Provides>",
80 " </Component>",
81 " </ComponentGroup>",
82 " </Fragment>",
83 "</Wix>"
84 };
85
86 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
87
88 var messaging = new MockMessaging();
89 var converter = new WixConverter(messaging, 2, null, null);
90
91 var errors = converter.ConvertDocument(document);
92 Assert.Equal(7, errors);
93
94 var actualLines = UnformattedDocumentLines(document);
95 WixAssert.CompareLineByLine(expected, actualLines);
96 }
97
98 [Fact]
99 public void FixPackageDependencyRequiresRef()
100 {
101 var parse = String.Join(Environment.NewLine,
102 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
103 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:dep='http://schemas.microsoft.com/wix/DependencyExtension'>",
104 " <Fragment>",
105 " <ComponentGroup Id='Group1' Directory='INSTALLFOLDER'>",
106 " <Component>",
107 " <dep:Provides Key='abc'>",
108 " <dep:RequiresRef Id='OtherRequires' />",
109 " </dep:Provides>",
110 " </Component>",
111 " </ComponentGroup>",
112 " </Fragment>",
113 "</Wix>");
114
115 var expected = new[]
116 {
117 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:dep=\"http://wixtoolset.org/schemas/v4/wxs/dependency\">",
118 " <Fragment>",
119 " <ComponentGroup Id=\"Group1\" Directory=\"INSTALLFOLDER\">",
120 " <Component>",
121 " <Provides Key=\"abc\" dep:Check=\"yes\">",
122 " <RequiresRef Id=\"OtherRequires\" dep:Enforce=\"yes\" />",
123 " </Provides>",
124 " </Component>",
125 " </ComponentGroup>",
126 " </Fragment>",
127 "</Wix>"
128 };
129
130 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
131
132 var messaging = new MockMessaging();
133 var converter = new WixConverter(messaging, 2, null, null);
134
135 var errors = converter.ConvertDocument(document);
136 Assert.Equal(7, errors);
137
138 var actualLines = UnformattedDocumentLines(document);
139 WixAssert.CompareLineByLine(expected, actualLines);
140 }
141
142 [Fact]
143 public void FixBundleDependencyProvides()
144 {
145 var parse = String.Join(Environment.NewLine,
146 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
147 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:dep='http://schemas.microsoft.com/wix/DependencyExtension'>",
148 " <Fragment>",
149 " <MsiPackage Id='Package1'>",
150 " <dep:Provides Key='abc' />",
151 " </MsiPackage>",
152 " </Fragment>",
153 "</Wix>");
154
155 var expected = new[]
156 {
157 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
158 " <Fragment>",
159 " <MsiPackage Id=\"Package1\">",
160 " <Provides Key=\"abc\" />",
161 " </MsiPackage>",
162 " </Fragment>",
163 "</Wix>"
164 };
165
166 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
167
168 var messaging = new MockMessaging();
169 var converter = new WixConverter(messaging, 2, null, null);
170
171 var errors = converter.ConvertDocument(document);
172 Assert.Equal(5, errors);
173
174 var actualLines = UnformattedDocumentLines(document);
175 WixAssert.CompareLineByLine(expected, actualLines);
176 }
177 }
178}
diff --git a/src/wix/test/WixToolsetTest.Converters/DirectoryFixture.cs b/src/wix/test/WixToolsetTest.Converters/DirectoryFixture.cs
new file mode 100644
index 00000000..3c906320
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/DirectoryFixture.cs
@@ -0,0 +1,92 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class DirectoryFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void RemoveTargetDir()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
20 " <Fragment>",
21 " <Directory Id='TARGETDIR' Name='SourceDir'>",
22 " <!-- Comment -->",
23 " <Directory Id='RootFolder' Name='Root'>",
24 " <Directory Id='ChildFolder' Name='Child' />",
25 " </Directory>",
26 " </Directory>",
27 " </Fragment>",
28 "</Wix>");
29
30 var expected = new[]
31 {
32 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
33 " <Fragment>",
34 " <!-- Comment -->",
35 " <Directory Id=\"RootFolder\" Name=\"Root\">",
36 " <Directory Id=\"ChildFolder\" Name=\"Child\" />",
37 " </Directory>",
38 " </Fragment>",
39 "</Wix>"
40 };
41
42 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
43
44 var messaging = new MockMessaging();
45 var converter = new WixConverter(messaging, 2, null, null);
46
47 var errors = converter.ConvertDocument(document);
48 Assert.Equal(3, errors);
49
50 var actualLines = UnformattedDocumentLines(document);
51 WixAssert.CompareLineByLine(expected, actualLines);
52 }
53
54 [Fact]
55 public void FixStandardDirectory()
56 {
57 var parse = String.Join(Environment.NewLine,
58 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
59 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
60 " <Fragment>",
61 " <Directory Id='TARGETDIR' Name='SourceDir'>",
62 " <Directory Id='ProgramFilesFolder' Name='PFiles'>",
63 " <Directory Id='ChildFolder' Name='Child' />",
64 " </Directory>",
65 " </Directory>",
66 " </Fragment>",
67 "</Wix>");
68
69 var expected = new[]
70 {
71 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
72 " <Fragment>",
73 " <StandardDirectory Id=\"ProgramFilesFolder\">",
74 " <Directory Id=\"ChildFolder\" Name=\"Child\" />",
75 " </StandardDirectory>",
76 " </Fragment>",
77 "</Wix>"
78 };
79
80 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
81
82 var messaging = new MockMessaging();
83 var converter = new WixConverter(messaging, 2, null, null);
84
85 var errors = converter.ConvertDocument(document);
86 Assert.Equal(4, errors);
87
88 var actualLines = UnformattedDocumentLines(document);
89 WixAssert.CompareLineByLine(expected, actualLines);
90 }
91 }
92}
diff --git a/src/wix/test/WixToolsetTest.Converters/ExePackageFixture.cs b/src/wix/test/WixToolsetTest.Converters/ExePackageFixture.cs
new file mode 100644
index 00000000..0ee8d065
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/ExePackageFixture.cs
@@ -0,0 +1,49 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class ExePackageFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void CanConvertExePackageCommandToArguments()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
19 " <Fragment>",
20 " <PackageGroup Id='exe'>",
21 " <ExePackage InstallCommand='-install' RepairCommand='-repair' UninstallCommand='-uninstall' SourceFile='test.exe' />",
22 " </PackageGroup>",
23 " </Fragment>",
24 "</Wix>");
25
26 var expected = new[]
27 {
28 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
29 " <Fragment>",
30 " <PackageGroup Id=\"exe\">",
31 " <ExePackage SourceFile=\"test.exe\" InstallArguments=\"-install\" RepairArguments=\"-repair\" UninstallArguments=\"-uninstall\" />",
32 " </PackageGroup>",
33 " </Fragment>",
34 "</Wix>"
35 };
36
37 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
38
39 var messaging = new MockMessaging();
40 var converter = new WixConverter(messaging, 2, null, null);
41
42 var errors = converter.ConvertDocument(document);
43 Assert.Equal(3, errors);
44
45 var actualLines = UnformattedDocumentLines(document);
46 WixAssert.CompareLineByLine(expected, actualLines);
47 }
48 }
49}
diff --git a/src/wix/test/WixToolsetTest.Converters/FeatureFixture.cs b/src/wix/test/WixToolsetTest.Converters/FeatureFixture.cs
new file mode 100644
index 00000000..1df94a81
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/FeatureFixture.cs
@@ -0,0 +1,110 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class FeatureFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixAllowAttributes()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
20 " <Fragment>",
21 " <Feature Absent='allow' AllowAdvertise='system' />",
22 " </Fragment>",
23 "</Wix>");
24
25 var expected = new[]
26 {
27 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
28 " <Fragment>",
29 " <Feature AllowAdvertise=\"yes\" />",
30 " </Fragment>",
31 "</Wix>"
32 };
33
34 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
35
36 var messaging = new MockMessaging();
37 var converter = new WixConverter(messaging, 2, null, null);
38
39 var errors = converter.ConvertDocument(document);
40 Assert.Equal(4, errors);
41
42 var actualLines = UnformattedDocumentLines(document);
43 WixAssert.CompareLineByLine(expected, actualLines);
44 }
45
46 [Fact]
47 public void FixDisallowAttributes()
48 {
49 var parse = String.Join(Environment.NewLine,
50 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
51 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
52 " <Fragment>",
53 " <Feature Absent='disallow' AllowAdvertise='no' />",
54 " </Fragment>",
55 "</Wix>");
56
57 var expected = new[]
58 {
59 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
60 " <Fragment>",
61 " <Feature AllowAdvertise=\"no\" AllowAbsent=\"no\" />",
62 " </Fragment>",
63 "</Wix>"
64 };
65
66 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
67
68 var messaging = new MockMessaging();
69 var converter = new WixConverter(messaging, 2, null, null);
70
71 var errors = converter.ConvertDocument(document);
72 Assert.Equal(3, errors);
73
74 var actualLines = UnformattedDocumentLines(document);
75 WixAssert.CompareLineByLine(expected, actualLines);
76 }
77
78 [Fact]
79 public void RemoveDeprecatedAllowAdvertiseAttributes()
80 {
81 var parse = String.Join(Environment.NewLine,
82 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
83 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
84 " <Fragment>",
85 " <Feature AllowAdvertise='disallow' />",
86 " </Fragment>",
87 "</Wix>");
88
89 var expected = new[]
90 {
91 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
92 " <Fragment>",
93 " <Feature />",
94 " </Fragment>",
95 "</Wix>"
96 };
97
98 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
99
100 var messaging = new MockMessaging();
101 var converter = new WixConverter(messaging, 2, null, null);
102
103 var errors = converter.ConvertDocument(document);
104 Assert.Equal(3, errors);
105
106 var actualLines = UnformattedDocumentLines(document);
107 WixAssert.CompareLineByLine(expected, actualLines);
108 }
109 }
110}
diff --git a/src/wix/test/WixToolsetTest.Converters/FirewallExtensionFixture.cs b/src/wix/test/WixToolsetTest.Converters/FirewallExtensionFixture.cs
new file mode 100644
index 00000000..a101019b
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/FirewallExtensionFixture.cs
@@ -0,0 +1,47 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class FirewallExtensionFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixRemoteAddressValue()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:fw='http://schemas.microsoft.com/wix/FirewallExtension'>",
19 " <Fragment>",
20 " <fw:RemoteAddress>",
21 " 127.0.0.1",
22 " </fw:RemoteAddress>",
23 " </Fragment>",
24 "</Wix>");
25
26 var expected = new[]
27 {
28 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:fw=\"http://wixtoolset.org/schemas/v4/wxs/firewall\">",
29 " <Fragment>",
30 " <fw:RemoteAddress Value=\"127.0.0.1\" />",
31 " </Fragment>",
32 "</Wix>"
33 };
34
35 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
36
37 var messaging = new MockMessaging();
38 var converter = new WixConverter(messaging, 2, null, null);
39
40 var errors = converter.ConvertDocument(document);
41 Assert.Equal(3, errors);
42
43 var actualLines = UnformattedDocumentLines(document);
44 WixAssert.CompareLineByLine(expected, actualLines);
45 }
46 }
47}
diff --git a/src/wix/test/WixToolsetTest.Converters/FormatFixture.cs b/src/wix/test/WixToolsetTest.Converters/FormatFixture.cs
new file mode 100644
index 00000000..739fba66
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/FormatFixture.cs
@@ -0,0 +1,117 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixToolset.Converters;
8 using WixToolsetTest.Converters.Mocks;
9 using Xunit;
10
11 public class FormatFixture : BaseConverterFixture
12 {
13 [Fact]
14 public void CanFixWhitespace()
15 {
16 var parse = String.Join(Environment.NewLine,
17 "<?xml version='1.0' encoding='utf-8'?>",
18 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
19 " <Fragment>",
20 " <Property Id='Prop'",
21 " Value='Val'>",
22 " </Property>",
23 " </Fragment>",
24 "</Wix>");
25
26 var expected = String.Join(Environment.NewLine,
27 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
28 " <Fragment>",
29 " <Property Id=\"Prop\" Value=\"Val\" />",
30 " </Fragment>",
31 "</Wix>");
32
33 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
34
35 var messaging = new MockMessaging();
36 var converter = new WixConverter(messaging, 4, null, null);
37
38 var errors = converter.FormatDocument(document);
39
40 var actual = UnformattedDocumentString(document);
41
42 Assert.Equal(expected, actual);
43 Assert.Equal(5, errors);
44 }
45
46 [Fact]
47 public void CanPreserveNewLines()
48 {
49 var parse = String.Join(Environment.NewLine,
50 "<?xml version='1.0' encoding='utf-8'?>",
51 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
52 " <Fragment>",
53 "",
54 " <Property Id='Prop' Value='Val' />",
55 "",
56 " </Fragment>",
57 "</Wix>");
58
59 var expected = String.Join(Environment.NewLine,
60 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
61 " <Fragment>",
62 "",
63 " <Property Id=\"Prop\" Value=\"Val\" />",
64 "",
65 " </Fragment>",
66 "</Wix>");
67
68 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
69
70 var messaging = new MockMessaging();
71 var converter = new WixConverter(messaging, 4, null, null);
72
73 var conversions = converter.FormatDocument(document);
74
75 var actual = UnformattedDocumentString(document);
76
77 Assert.Equal(expected, actual);
78 Assert.Equal(4, conversions);
79 }
80
81 [Fact]
82 public void CanFormatWithNewLineAtEndOfFile()
83 {
84 var parse = String.Join(Environment.NewLine,
85 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
86 " <Fragment>",
87 "",
88 " <Property Id='Prop' Value='Val' />",
89 "",
90 " </Fragment>",
91 "</Wix>",
92 "");
93
94 var expected = String.Join(Environment.NewLine,
95 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
96 " <Fragment>",
97 "",
98 " <Property Id=\"Prop\" Value=\"Val\" />",
99 "",
100 " </Fragment>",
101 "</Wix>",
102 "");
103
104 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
105
106 var messaging = new MockMessaging();
107 var converter = new WixConverter(messaging, 4, null, null);
108
109 var conversions = converter.FormatDocument(document);
110
111 var actual = UnformattedDocumentString(document);
112
113 Assert.Equal(expected, actual);
114 Assert.Equal(3, conversions);
115 }
116 }
117}
diff --git a/src/wix/test/WixToolsetTest.Converters/IncludeFixture.cs b/src/wix/test/WixToolsetTest.Converters/IncludeFixture.cs
new file mode 100644
index 00000000..2fd8244f
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/IncludeFixture.cs
@@ -0,0 +1,68 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class IncludeFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void EnsureNamespace()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Include>",
19 " <Fragment />",
20 "</Include>");
21
22 var expected = new[]
23 {
24 "<Include xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
25 " <Fragment />",
26 "</Include>"
27 };
28
29 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
30
31 var messaging = new MockMessaging();
32 var converter = new WixConverter(messaging, 2, null, null);
33
34 var errors = converter.ConvertDocument(document);
35 Assert.Equal(1, errors);
36
37 var actualLines = UnformattedDocumentLines(document);
38 WixAssert.CompareLineByLine(expected, actualLines);
39 }
40
41 [Fact]
42 public void FixNamespace()
43 {
44 var parse = String.Join(Environment.NewLine,
45 "<Include xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
46 " <Fragment />",
47 "</Include>");
48
49 var expected = new[]
50 {
51 "<Include xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
52 " <Fragment />",
53 "</Include>"
54 };
55
56 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
57
58 var messaging = new MockMessaging();
59 var converter = new WixConverter(messaging, 2, null, null);
60
61 var errors = converter.ConvertDocument(document);
62 Assert.Equal(1, errors);
63
64 var actualLines = UnformattedDocumentLines(document);
65 WixAssert.CompareLineByLine(expected, actualLines);
66 }
67 }
68}
diff --git a/src/wix/test/WixToolsetTest.Converters/Mocks/MockCoreServiceProvider.cs b/src/wix/test/WixToolsetTest.Converters/Mocks/MockCoreServiceProvider.cs
new file mode 100644
index 00000000..b6bb8a40
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/Mocks/MockCoreServiceProvider.cs
@@ -0,0 +1,51 @@
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
3namespace WixToolsetTest.Converters.Mocks
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Extensibility.Services;
8
9 public class MockCoreServiceProvider : IWixToolsetCoreServiceProvider
10 {
11 public Dictionary<Type, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object>> CreationFunctions { get; } = new Dictionary<Type, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object>>();
12
13 public Dictionary<Type, object> Singletons { get; } = new Dictionary<Type, object>()
14 {
15 { typeof(IMessaging), new MockMessaging() }
16 };
17
18 public void AddService(Type serviceType, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object> creationFunction) => this.CreationFunctions.Add(serviceType, creationFunction);
19
20 public void AddService<T>(Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, T> creationFunction) where T : class => this.AddService(typeof(T), creationFunction);
21
22 public T GetService<T>() where T : class => this.TryGetService(typeof(T), out var obj) ? (T)obj : null;
23
24 public object GetService(Type serviceType) => this.TryGetService(serviceType, out var service) ? service : null;
25
26 public bool TryGetService(Type serviceType, out object service)
27 {
28 if (!this.Singletons.TryGetValue(serviceType, out service))
29 {
30 if (this.CreationFunctions.TryGetValue(serviceType, out var creationFunction))
31 {
32 service = creationFunction(this, this.Singletons);
33 }
34 }
35
36 return service != null;
37 }
38
39 public bool TryGetService<T>(out T service) where T : class
40 {
41 service = null;
42
43 if (this.TryGetService(typeof(T), out var obj))
44 {
45 service = (T)obj;
46 }
47
48 return service != null;
49 }
50 }
51} \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Converters/Mocks/MockMessaging.cs b/src/wix/test/WixToolsetTest.Converters/Mocks/MockMessaging.cs
new file mode 100644
index 00000000..77821a1c
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/Mocks/MockMessaging.cs
@@ -0,0 +1,39 @@
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
3namespace WixToolsetTest.Converters.Mocks
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Services;
10
11 public class MockMessaging : IMessaging
12 {
13 public List<Message> Messages { get; } = new List<Message>();
14
15 public bool EncounteredError { get; private set; }
16
17 public int LastErrorNumber { get; }
18
19 public bool ShowVerboseMessages { get; set; }
20
21 public bool SuppressAllWarnings { get; set; }
22
23 public bool WarningsAsError { get; set; }
24
25 public void ElevateWarningMessage(int warningNumber) => throw new NotImplementedException();
26
27 public void SetListener(IMessageListener listener) => throw new NotImplementedException();
28
29 public void SuppressWarningMessage(int warningNumber) => throw new NotImplementedException();
30
31 public void Write(Message message)
32 {
33 this.Messages.Add(message);
34 this.EncounteredError |= message.Level == MessageLevel.Error;
35 }
36
37 public void Write(string message, bool verbose = false) => throw new NotImplementedException();
38 }
39}
diff --git a/src/wix/test/WixToolsetTest.Converters/ProductPackageFixture.cs b/src/wix/test/WixToolsetTest.Converters/ProductPackageFixture.cs
new file mode 100644
index 00000000..e01b9789
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/ProductPackageFixture.cs
@@ -0,0 +1,278 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.IO;
7 using System.Xml.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Converters;
10 using WixToolset.Core.TestPackage;
11 using WixToolsetTest.Converters.Mocks;
12 using Xunit;
13
14 public class ProductPackageFixture : BaseConverterFixture
15 {
16 [Fact]
17 public void FixesCompressedWhenYes()
18 {
19 var parse = String.Join(Environment.NewLine,
20 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
21 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
22 " <Product>",
23 " <Package Compressed='yes' />",
24 " </Product>",
25 "</Wix>");
26
27 var expected = new[]
28 {
29 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
30 " <Package>",
31 " ",
32 " </Package>",
33 "</Wix>"
34 };
35
36 AssertSuccess(parse, 4, expected);
37 }
38
39 [Fact]
40 public void FixesCompressedWhenNo()
41 {
42 var parse = String.Join(Environment.NewLine,
43 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
44 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
45 " <Product>",
46 " <Package Compressed='no' />",
47 " </Product>",
48 "</Wix>");
49
50 var expected = new[]
51 {
52 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
53 " <Package Compressed=\"no\">",
54 " ",
55 " </Package>",
56 "</Wix>"
57 };
58
59 AssertSuccess(parse, 4, expected);
60 }
61
62 [Fact]
63 public void FixesCompressedWhenOmitted()
64 {
65 var parse = String.Join(Environment.NewLine,
66 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
67 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
68 " <Product>",
69 " <Package />",
70 " </Product>",
71 "</Wix>");
72
73 var expected = new[]
74 {
75 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
76 " <Package Compressed=\"no\">",
77 " ",
78 " </Package>",
79 "</Wix>"
80 };
81
82 AssertSuccess(parse, 4, expected);
83 }
84
85 private static void AssertSuccess(string input, int expectedErrorCount, string[] expected)
86 {
87 var document = XDocument.Parse(input, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
88
89 var messaging = new MockMessaging();
90 var converter = new WixConverter(messaging, 2, null, null);
91
92 var errors = converter.ConvertDocument(document);
93 Assert.Equal(expectedErrorCount, errors);
94
95 var actualLines = UnformattedDocumentLines(document);
96 WixAssert.CompareLineByLine(expected, actualLines);
97 }
98
99 [Fact]
100 public void FixesInstallerVersion()
101 {
102 var parse = String.Join(Environment.NewLine,
103 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
104 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
105 " <Product>",
106 " <Package InstallerVersion='666' />",
107 " </Product>",
108 "</Wix>");
109
110 var expected = new[]
111 {
112 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
113 " <Package Compressed=\"no\" InstallerVersion=\"666\">",
114 " ",
115 " </Package>",
116 "</Wix>"
117 };
118
119 AssertSuccess(parse, 3, expected);
120 }
121
122 [Fact]
123 public void FixesDefaultInstallerVersion()
124 {
125 var parse = String.Join(Environment.NewLine,
126 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
127 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
128 " <Product>",
129 " <Package InstallerVersion='500' />",
130 " </Product>",
131 "</Wix>");
132
133 var expected = new[]
134 {
135 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
136 " <Package Compressed=\"no\">",
137 " ",
138 " </Package>",
139 "</Wix>"
140 };
141
142 AssertSuccess(parse, 3, expected);
143 }
144
145 [Fact]
146 public void FixesImplicitInstallerVersion()
147 {
148 var parse = String.Join(Environment.NewLine,
149 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
150 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
151 " <Product>",
152 " <Package />",
153 " </Product>",
154 "</Wix>");
155
156 var expected = new[]
157 {
158 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
159 " <Package Compressed=\"no\">",
160 " ",
161 " </Package>",
162 "</Wix>"
163 };
164
165 AssertSuccess(parse, 4, expected);
166 }
167
168 [Fact]
169 public void FixesNonDefaultInstallerVersion()
170 {
171 var parse = String.Join(Environment.NewLine,
172 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
173 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
174 " <Product>",
175 " <Package InstallerVersion='200' />",
176 " </Product>",
177 "</Wix>");
178
179 var expected = new[]
180 {
181 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
182 " <Package Compressed=\"no\" InstallerVersion=\"200\">",
183 " ",
184 " </Package>",
185 "</Wix>"
186 };
187
188 AssertSuccess(parse, 3, expected);
189 }
190
191 [Fact]
192 public void FixesLimitedInstallerPrivileges()
193 {
194 var parse = String.Join(Environment.NewLine,
195 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
196 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
197 " <Product>",
198 " <Package InstallPrivileges='limited' />",
199 " </Product>",
200 "</Wix>");
201
202 var expected = new[]
203 {
204 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
205 " <Package Compressed=\"no\" Scope=\"perUser\">",
206 " ",
207 " </Package>",
208 "</Wix>"
209 };
210
211 AssertSuccess(parse, 4, expected);
212 }
213
214 [Fact]
215 public void FixesElevatedInstallerPrivileges()
216 {
217 var parse = String.Join(Environment.NewLine,
218 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
219 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
220 " <Product>",
221 " <Package InstallPrivileges='elevated' />",
222 " <Property Id='ALLUSERS' Value='1' />",
223 " </Product>",
224 "</Wix>");
225
226 var expected = new[]
227 {
228 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
229 " <Package Compressed=\"no\">",
230 " ",
231 " ",
232 " </Package>",
233 "</Wix>"
234 };
235
236 AssertSuccess(parse, 4, expected);
237 }
238
239 [Fact]
240 public void CanDecompileAndRecompile()
241 {
242 using (var fs = new DisposableFileSystem())
243 {
244 var baseFolder = fs.GetFolder();
245 var intermediateFolder = Path.Combine(baseFolder, "obj");
246 var decompiledWxsPath = Path.Combine(baseFolder, "TypicalV3.wxs");
247
248 var folder = TestData.Get(@"TestData\PackageSummaryInformation");
249 var v3msiPath = Path.Combine(folder, "TypicalV3.msi");
250 var result = WixRunner.Execute(new[]
251 {
252 "decompile", v3msiPath,
253 "-intermediateFolder", intermediateFolder,
254 "-o", decompiledWxsPath
255 });
256
257 result.AssertSuccess();
258
259 var v4msiPath = Path.Combine(intermediateFolder, "TypicalV4.msi");
260 result = WixRunner.Execute(new[]
261 {
262 "build", decompiledWxsPath,
263 "-arch", "x64",
264 "-intermediateFolder", intermediateFolder,
265 "-o", v4msiPath
266 });
267
268 result.AssertSuccess();
269
270 Assert.True(File.Exists(v4msiPath));
271
272 var v3results = Query.QueryDatabase(v3msiPath, new[] { "_SummaryInformation", "Property" });
273 var v4results = Query.QueryDatabase(v4msiPath, new[] { "_SummaryInformation", "Property" });
274 WixAssert.CompareLineByLine(v3results, v4results);
275 }
276 }
277 }
278}
diff --git a/src/wix/test/WixToolsetTest.Converters/PropertyFixture.cs b/src/wix/test/WixToolsetTest.Converters/PropertyFixture.cs
new file mode 100644
index 00000000..e50a6518
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/PropertyFixture.cs
@@ -0,0 +1,108 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixToolset.Converters;
8 using WixToolsetTest.Converters.Mocks;
9 using Xunit;
10
11 public class PropertyFixture : BaseConverterFixture
12 {
13 [Fact]
14 public void CanFixCdataWhitespace()
15 {
16 var parse = String.Join(Environment.NewLine,
17 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
18 " <Fragment>",
19 " <Property Id='Prop'>",
20 " <![CDATA[1<2]]>",
21 " </Property>",
22 " </Fragment>",
23 "</Wix>");
24
25 var expected = String.Join(Environment.NewLine,
26 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
27 " <Fragment>",
28 " <Property Id=\"Prop\" Value=\"1&lt;2\" />",
29 " </Fragment>",
30 "</Wix>");
31
32 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
33
34 var messaging = new MockMessaging();
35 var converter = new WixConverter(messaging, 2, null, null);
36
37 var errors = converter.ConvertDocument(document);
38
39 var actual = UnformattedDocumentString(document);
40
41 Assert.Equal(expected, actual);
42 Assert.Equal(1, errors);
43 }
44
45 [Fact]
46 public void CanFixCdataWithWhitespace()
47 {
48 var parse = String.Join(Environment.NewLine,
49 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
50 " <Fragment>",
51 " <Property Id='Prop'>",
52 " <![CDATA[",
53 " 1<2",
54 " ]]>",
55 " </Property>",
56 " </Fragment>",
57 "</Wix>");
58
59 var expected = String.Join(Environment.NewLine,
60 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
61 " <Fragment>",
62 " <Property Id=\"Prop\" Value=\"1&lt;2\" />",
63 " </Fragment>",
64 "</Wix>");
65
66 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
67
68 var messaging = new MockMessaging();
69 var converter = new WixConverter(messaging, 2, null, null);
70
71 var errors = converter.ConvertDocument(document);
72
73 var actual = UnformattedDocumentString(document);
74
75 Assert.Equal(expected, actual);
76 Assert.Equal(1, errors);
77 }
78
79 [Fact]
80 public void CanKeepCdataWithOnlyWhitespace()
81 {
82 var parse = String.Join(Environment.NewLine,
83 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
84 " <Fragment>",
85 " <Property Id='Prop'><![CDATA[ ]]></Property>",
86 " </Fragment>",
87 "</Wix>");
88
89 var expected = String.Join(Environment.NewLine,
90 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
91 " <Fragment>",
92 " <Property Id=\"Prop\" Value=\" \" />",
93 " </Fragment>",
94 "</Wix>");
95
96 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
97
98 var messaging = new MockMessaging();
99 var converter = new WixConverter(messaging, 2, null, null);
100 var errors = converter.ConvertDocument(document);
101
102 var actual = UnformattedDocumentString(document);
103
104 Assert.Equal(expected, actual);
105 Assert.Equal(1, errors);
106 }
107 }
108}
diff --git a/src/wix/test/WixToolsetTest.Converters/RegistryFixture.cs b/src/wix/test/WixToolsetTest.Converters/RegistryFixture.cs
new file mode 100644
index 00000000..405f5416
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/RegistryFixture.cs
@@ -0,0 +1,54 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class RegistryFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixRegistryKeyAction()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
20 " <Fragment>",
21 " <Component>",
22 " <RegistryKey Id='ExampleRegistryKey1' Action='create' Root='HKLM' Key='TestRegistryKey1' />",
23 " <RegistryKey Id='ExampleRegistryKey2' Action='createAndRemoveOnUninstall' Root='HKLM' Key='TestRegistryKey2' />",
24 " <RegistryKey Id='ExampleRegistryKey3' Action='none' Root='HKLM' Key='TestRegistryKey3' />",
25 " </Component>",
26 " </Fragment>",
27 "</Wix>");
28
29 var expected = new[]
30 {
31 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
32 " <Fragment>",
33 " <Component>",
34 " <RegistryKey Id=\"ExampleRegistryKey1\" Root=\"HKLM\" Key=\"TestRegistryKey1\" ForceCreateOnInstall=\"yes\" />",
35 " <RegistryKey Id=\"ExampleRegistryKey2\" Root=\"HKLM\" Key=\"TestRegistryKey2\" ForceCreateOnInstall=\"yes\" ForceDeleteOnUninstall=\"yes\" />",
36 " <RegistryKey Id=\"ExampleRegistryKey3\" Root=\"HKLM\" Key=\"TestRegistryKey3\" />",
37 " </Component>",
38 " </Fragment>",
39 "</Wix>"
40 };
41
42 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
43
44 var messaging = new MockMessaging();
45 var converter = new WixConverter(messaging, 2, null, null);
46
47 var errors = converter.ConvertDocument(document);
48 Assert.Equal(5, errors);
49
50 var actualLines = UnformattedDocumentLines(document);
51 WixAssert.CompareLineByLine(expected, actualLines);
52 }
53 }
54}
diff --git a/src/wix/test/WixToolsetTest.Converters/RemotePayloadFixture.cs b/src/wix/test/WixToolsetTest.Converters/RemotePayloadFixture.cs
new file mode 100644
index 00000000..b2640e13
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/RemotePayloadFixture.cs
@@ -0,0 +1,104 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class RemotePayloadFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void CanConvertExePackageRemotePayload()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
19 " <Fragment>",
20 " <PackageGroup Id='exe'>",
21 " <ExePackage Name='example.exe' DownloadUrl='example.com'>",
22 " <RemotePayload",
23 " Description='Microsoft ASP.NET Core 3.1.8 - Shared Framework'",
24 " Hash='61DC9EAA0C8968E48E13C5913ED202A2F8F94DBA'",
25 " CertificatePublicKey='3756E9BBF4461DCD0AA68E0D1FCFFA9CEA47AC18'",
26 " CertificateThumbprint='2485A7AFA98E178CB8F30C9838346B514AEA4769'",
27 " ProductName='Microsoft ASP.NET Core 3.1.8 - Shared Framework'",
28 " Size='7841880'",
29 " Version='3.1.8.20421' />",
30 " </ExePackage>",
31 " </PackageGroup>",
32 " </Fragment>",
33 "</Wix>");
34
35 var expected = new[]
36 {
37 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
38 " <Fragment>",
39 " <PackageGroup Id=\"exe\">",
40 " <ExePackage>",
41 " <ExePackagePayload Description=\"Microsoft ASP.NET Core 3.1.8 - Shared Framework\" Hash=\"61DC9EAA0C8968E48E13C5913ED202A2F8F94DBA\" ProductName=\"Microsoft ASP.NET Core 3.1.8 - Shared Framework\" Size=\"7841880\" Version=\"3.1.8.20421\" Name=\"example.exe\" DownloadUrl=\"example.com\" />",
42 " </ExePackage>",
43 " </PackageGroup>",
44 " </Fragment>",
45 "</Wix>"
46 };
47
48 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
49
50 var messaging = new MockMessaging();
51 var converter = new WixConverter(messaging, 2, null, null);
52
53 var errors = converter.ConvertDocument(document);
54 Assert.Equal(6, errors);
55
56 var actualLines = UnformattedDocumentLines(document);
57 WixAssert.CompareLineByLine(expected, actualLines);
58 }
59
60 [Fact]
61 public void CanConvertMsuPackageRemotePayload()
62 {
63 var parse = String.Join(Environment.NewLine,
64 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
65 " <Fragment>",
66 " <PackageGroup Id='msu'>",
67 " <MsuPackage Name='example.msu' DownloadUrl='example.com' Compressed='no'>",
68 " <RemotePayload",
69 " Description='msu description'",
70 " Hash='71DC9EAA0C8968E48E13C5913ED202A2F8F94DBB'",
71 " ProductName='msu product name'",
72 " Size='500'",
73 " Version='0.0.0.0' />",
74 " </MsuPackage>",
75 " </PackageGroup>",
76 " </Fragment>",
77 "</Wix>");
78
79 var expected = new[]
80 {
81 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
82 " <Fragment>",
83 " <PackageGroup Id=\"msu\">",
84 " <MsuPackage>",
85 " <MsuPackagePayload Description=\"msu description\" Hash=\"71DC9EAA0C8968E48E13C5913ED202A2F8F94DBB\" ProductName=\"msu product name\" Size=\"500\" Version=\"0.0.0.0\" Name=\"example.msu\" DownloadUrl=\"example.com\" />",
86 " </MsuPackage>",
87 " </PackageGroup>",
88 " </Fragment>",
89 "</Wix>"
90 };
91
92 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
93
94 var messaging = new MockMessaging();
95 var converter = new WixConverter(messaging, 2, null, null);
96
97 var errors = converter.ConvertDocument(document);
98 Assert.Equal(5, errors);
99
100 var actualLines = UnformattedDocumentLines(document);
101 WixAssert.CompareLineByLine(expected, actualLines);
102 }
103 }
104}
diff --git a/src/wix/test/WixToolsetTest.Converters/SequenceFixture.cs b/src/wix/test/WixToolsetTest.Converters/SequenceFixture.cs
new file mode 100644
index 00000000..ec6c709b
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/SequenceFixture.cs
@@ -0,0 +1,49 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class SequenceFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixCondition()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
19 " <Fragment>",
20 " <InstallUISequence>",
21 " <Custom Action='ExampleCA' After='InstallFiles'>NOT Installed</Custom>",
22 " </InstallUISequence>",
23 " </Fragment>",
24 "</Wix>");
25
26 var expected = new[]
27 {
28 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
29 " <Fragment>",
30 " <InstallUISequence>",
31 " <Custom Action=\"ExampleCA\" After=\"InstallFiles\" Condition=\"NOT Installed\" />",
32 " </InstallUISequence>",
33 " </Fragment>",
34 "</Wix>"
35 };
36
37 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
38
39 var messaging = new MockMessaging();
40 var converter = new WixConverter(messaging, 2, null, null);
41
42 var errors = converter.ConvertDocument(document);
43 Assert.Equal(2, errors);
44
45 var actualLines = UnformattedDocumentLines(document);
46 WixAssert.CompareLineByLine(expected, actualLines);
47 }
48 }
49}
diff --git a/src/wix/test/WixToolsetTest.Converters/TagFixture.cs b/src/wix/test/WixToolsetTest.Converters/TagFixture.cs
new file mode 100644
index 00000000..5e07c83b
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TagFixture.cs
@@ -0,0 +1,111 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class TagFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixTagExtension()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:tag='http://schemas.microsoft.com/wix/TagExtension'>",
19 " <Product>",
20 " <tag:Tag Regid='wixtoolset.org' InstallDirectory='InstallFolder' />",
21 " </Product>",
22 "</Wix>");
23
24 var expected = new[]
25 {
26 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
27 " <Package>",
28 " <SoftwareTag Regid=\"wixtoolset.org\" InstallDirectory=\"InstallFolder\" />",
29 " </Package>",
30 "</Wix>"
31 };
32
33 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
34
35 var messaging = new MockMessaging();
36 var converter = new WixConverter(messaging, 2, null, null);
37
38 var errors = converter.ConvertDocument(document);
39 Assert.Equal(4, errors);
40
41 var actualLines = UnformattedDocumentLines(document);
42 WixAssert.CompareLineByLine(expected, actualLines);
43 }
44
45 [Fact]
46 public void FixTagExtensionDeprecations()
47 {
48 var parse = String.Join(Environment.NewLine,
49 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:tag='http://schemas.microsoft.com/wix/TagExtension'>",
50 " <Product>",
51 " <tag:Tag Regid='wixtoolset.org' InstallDirectory='InstallFolder' Licensed='true' Type='component' Win64='yes' />",
52 " </Product>",
53 "</Wix>");
54
55 var expected = new[]
56 {
57 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
58 " <Package>",
59 " <SoftwareTag Regid=\"wixtoolset.org\" InstallDirectory=\"InstallFolder\" Bitness=\"always64\" />",
60 " </Package>",
61 "</Wix>"
62 };
63
64 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
65
66 var messaging = new MockMessaging();
67 var converter = new WixConverter(messaging, 2, null, null);
68
69 var errors = converter.ConvertDocument(document);
70 Assert.Equal(7, errors);
71
72 var actualLines = UnformattedDocumentLines(document);
73 WixAssert.CompareLineByLine(expected, actualLines);
74 }
75
76 [Fact]
77 public void FixTagExtensionTagRef()
78 {
79 var parse = String.Join(Environment.NewLine,
80 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:tag='http://schemas.microsoft.com/wix/TagExtension'>",
81 " <Fragment>",
82 " <PatchFamily>",
83 " <tag:TagRef Regid='wixtoolset.org' />",
84 " </PatchFamily>",
85 " </Fragment>",
86 "</Wix>");
87
88 var expected = new[]
89 {
90 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
91 " <Fragment>",
92 " <PatchFamily>",
93 " <SoftwareTagRef Regid=\"wixtoolset.org\" />",
94 " </PatchFamily>",
95 " </Fragment>",
96 "</Wix>"
97 };
98
99 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
100
101 var messaging = new MockMessaging();
102 var converter = new WixConverter(messaging, 2, null, null);
103
104 var errors = converter.ConvertDocument(document);
105 Assert.Equal(3, errors);
106
107 var actualLines = UnformattedDocumentLines(document);
108 WixAssert.CompareLineByLine(expected, actualLines);
109 }
110 }
111}
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/PackageSummaryInformation/TypicalV3.msi b/src/wix/test/WixToolsetTest.Converters/TestData/PackageSummaryInformation/TypicalV3.msi
new file mode 100644
index 00000000..0d7e1b21
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/PackageSummaryInformation/TypicalV3.msi
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/PackageSummaryInformation/TypicalV3.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/PackageSummaryInformation/TypicalV3.wxs
new file mode 100644
index 00000000..8c5027b4
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/PackageSummaryInformation/TypicalV3.wxs
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
3 <?define Name = "Package and summary information testing" ?>
4
5 <Product Id="*"
6 Name="$(var.Name)"
7 Language="1033"
8 Version="1.0"
9 Manufacturer="Example Corporation"
10 UpgradeCode="D86CAC27-51EA-46BD-8105-C465109AFA74">
11
12 <Package InstallerVersion="500"
13 Compressed="yes"
14 InstallScope="perMachine"
15 Platform="x64"
16 InstallPrivileges="elevated" />
17
18 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
19 <MediaTemplate EmbedCab="yes" />
20
21 <Feature Id="Feature" Title="Installer" Level="1">
22 <ComponentGroupRef Id="ProductComponents" />
23 </Feature>
24
25 <Directory Id="TARGETDIR" Name="SourceDir">
26 <Directory Id="ProgramFiles64Folder">
27 <Directory Id="INSTALLFOLDER" Name="$(var.Name)" />
28 </Directory>
29 </Directory>
30
31 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
32 <Component>
33 <RegistryValue Root="HKLM" Key="SOFTWARE\Example\$(var.Name)" Name="Installed" Value="1" Type="integer" />
34 </Component>
35 </ComponentGroup>
36 </Product>
37</Wix> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/PermissionEx/v3.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/PermissionEx/v3.wxs
new file mode 100644
index 00000000..9a739052
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/PermissionEx/v3.wxs
@@ -0,0 +1,26 @@
1<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
4 <Component>
5 <File Source="example.txt">
6 <util:PermissionEx User="Everyone" GenericAll="yes" />
7 </File>
8 <CreateFolder>
9 <util:PermissionEx User="Everyone" GenericAll="yes" />
10 </CreateFolder>
11 <ServiceInstall Name="testsvc" Type="ownProcess" Start="disabled" ErrorControl="normal">
12 <util:PermissionEx User="Everyone" GenericAll="yes" />
13 </ServiceInstall>
14 <Registry Action="createKey" Root="HKLM" Key="TestKey">
15 <util:PermissionEx User="Everyone" GenericAll="yes" />
16 </Registry>
17 <RegistryKey Id="ExampleRegistryKey" ForceCreateOnInstall="yes" Root="HKLM" Key="TestRegistryKey">
18 <util:PermissionEx User="Everyone" GenericAll="yes" />
19 </RegistryKey>
20 <RegistryValue Root="HKLM" Key="TestRegistryValueKey" Value="abc">
21 <util:PermissionEx User="Everyone" GenericAll="yes" />
22 </RegistryValue>
23 </Component>
24 </ComponentGroup>
25 </Fragment>
26</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/PermissionEx/v4_expected.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/PermissionEx/v4_expected.wxs
new file mode 100644
index 00000000..6bf3c1ea
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/PermissionEx/v4_expected.wxs
@@ -0,0 +1,26 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
4 <Component>
5 <File Id="example.txt" Source="example.txt">
6 <util:PermissionEx User="Everyone" GenericAll="yes" Inheritable="no" />
7 </File>
8 <CreateFolder>
9 <util:PermissionEx User="Everyone" GenericAll="yes" />
10 </CreateFolder>
11 <ServiceInstall Name="testsvc" Type="ownProcess" Start="disabled" ErrorControl="normal">
12 <util:PermissionEx User="Everyone" GenericAll="yes" Inheritable="no" />
13 </ServiceInstall>
14 <Registry Action="createKey" Root="HKLM" Key="TestKey">
15 <util:PermissionEx User="Everyone" GenericAll="yes" Inheritable="no" />
16 </Registry>
17 <RegistryKey Id="ExampleRegistryKey" ForceCreateOnInstall="yes" Root="HKLM" Key="TestRegistryKey">
18 <util:PermissionEx User="Everyone" GenericAll="yes" Inheritable="no" />
19 </RegistryKey>
20 <RegistryValue Root="HKLM" Key="TestRegistryValueKey" Value="abc">
21 <util:PermissionEx User="Everyone" GenericAll="yes" Inheritable="no" />
22 </RegistryValue>
23 </Component>
24 </ComponentGroup>
25 </Fragment>
26</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/ConvertedPreprocessor.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/ConvertedPreprocessor.wxs
new file mode 100644
index 00000000..8188d900
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/ConvertedPreprocessor.wxs
@@ -0,0 +1,61 @@
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
4
5<?include WixVer.wxi ?>
6
7<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
8 <Package Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)" Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D" InstallerVersion="200">
9
10 <SoftwareTag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
11
12 <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
13
14 <MediaTemplate CabinetTemplate="core{0}.cab" />
15
16 <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
17 <Component Id="Licensing" Directory="INSTALLFOLDER">
18 <File Id="LICENSE.TXT" Source="LICENSE.TXT" />
19 </Component>
20
21 <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
22 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
23 <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
24 </RegistryKey>
25 </Component>
26
27 <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
28 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
29 <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
30 </RegistryKey>
31 </Component>
32
33 <Component Id="ProductInformation" Directory="BinFolder">
34 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
35 <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string" />
36 <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
37 </RegistryKey>
38
39 <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
40 </Component>
41
42 <Component Directory="BinFolder">
43 <File Id="wixtoolset.org.ico" Source="common\wixtoolset.org.ico">
44 <?include ComRegistration.wxi ?>
45 </File>
46 <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
47 </Component>
48
49 <ComponentGroupRef Id="ToolsetComponents" />
50 <ComponentGroupRef Id="ExtensionComponents" />
51 <ComponentGroupRef Id="LuxComponents" />
52 <ComponentGroupRef Id="DocComponents" />
53 </Feature>
54
55 <FeatureRef Id="Feature_MSBuild" />
56 <FeatureRef Id="Feature_Intellisense2010" />
57 <FeatureRef Id="Feature_Intellisense2012" />
58 <FeatureRef Id="Feature_Intellisense2013" />
59 <FeatureRef Id="Feature_Intellisense2015" />
60 </Package>
61</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/Preprocessor.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/Preprocessor.wxs
new file mode 100644
index 00000000..2eb908c2
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/Preprocessor.wxs
@@ -0,0 +1,63 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!-- 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. -->
3
4
5
6<?include WixVer.wxi ?>
7
8<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:swid="http://schemas.microsoft.com/wix/TagExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
9 <Product Id="*" Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)"
10 Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D">
11 <Package Compressed="yes" InstallerVersion="200" SummaryCodepage="1252" InstallScope="perMachine" />
12 <swid:Tag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
13
14 <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
15
16 <MediaTemplate CabinetTemplate="core{0}.cab" />
17
18 <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
19 <Component Id="Licensing" Directory="INSTALLFOLDER">
20 <File Source="LICENSE.TXT" />
21 </Component>
22
23 <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
24 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
25 <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
26 </RegistryKey>
27 </Component>
28
29 <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
30 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
31 <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
32 </RegistryKey>
33 </Component>
34
35 <Component Id="ProductInformation" Directory="BinFolder">
36 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
37 <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string"/>
38 <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
39 </RegistryKey>
40
41 <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
42 </Component>
43
44 <Component Directory="BinFolder">
45 <File Source="common\wixtoolset.org.ico">
46 <?include ComRegistration.wxi ?>
47 </File>
48 <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
49 </Component>
50
51 <ComponentGroupRef Id="ToolsetComponents" />
52 <ComponentGroupRef Id="ExtensionComponents" />
53 <ComponentGroupRef Id="LuxComponents" />
54 <ComponentGroupRef Id="DocComponents" />
55 </Feature>
56
57 <FeatureRef Id="Feature_MSBuild" />
58 <FeatureRef Id="Feature_Intellisense2010" />
59 <FeatureRef Id="Feature_Intellisense2012" />
60 <FeatureRef Id="Feature_Intellisense2013" />
61 <FeatureRef Id="Feature_Intellisense2015" />
62 </Product>
63</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/wixcop.settings.xml b/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/wixcop.settings.xml
new file mode 100644
index 00000000..9d3ad496
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/Preprocessor/wixcop.settings.xml
@@ -0,0 +1,9 @@
1<?xml version="1.0"?>
2<Settings>
3 <IgnoreErrors>
4 <Test Id="WhitespacePrecedingNodeWrong"/>
5 <Test Id="WhitespacePrecedingEndElementWrong"/>
6 </IgnoreErrors>
7 <ErrorsAsWarnings/>
8 <ExemptFiles/>
9</Settings> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/QtExec.bad/v3.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/QtExec.bad/v3.wxs
new file mode 100644
index 00000000..b0fcf9c9
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/QtExec.bad/v3.wxs
@@ -0,0 +1,64 @@
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
4
5<?include WixVer.wxi ?>
6
7<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:swid="http://schemas.microsoft.com/wix/TagExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
8 <Product Id="*" Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)"
9 Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D">
10 <Package Compressed="yes" InstallerVersion="200" SummaryCodepage="1252" InstallScope="perMachine" />
11 <swid:Tag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
12
13 <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
14
15 <MediaTemplate CabinetTemplate="core{0}.cab" />
16
17 <Property Id="QtExecCmdTimeout" Value="600000" />
18 <CustomAction Id="InstallVSTemplateCommand" Property="QtExecCmdLine" Value="&quot;[VSENVPRODUCT80]\devenv.exe&quot; /setup" />
19 <CustomAction Id="InstallVSTemplate" BinaryKey="WixCA" DllEntry="CAQuietExec" Return="asyncWait" />
20
21 <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
22 <Component Id="Licensing" Directory="INSTALLFOLDER">
23 <File Source="LICENSE.TXT" />
24 </Component>
25
26 <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
27 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
28 <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
29 </RegistryKey>
30 </Component>
31
32 <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
33 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
34 <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
35 </RegistryKey>
36 </Component>
37
38 <Component Id="ProductInformation" Directory="BinFolder">
39 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
40 <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string"/>
41 <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
42 </RegistryKey>
43
44 <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
45 </Component>
46
47 <Component Directory="BinFolder">
48 <File Source="common\wixtoolset.org.ico" />
49 <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
50 </Component>
51
52 <ComponentGroupRef Id="ToolsetComponents" />
53 <ComponentGroupRef Id="ExtensionComponents" />
54 <ComponentGroupRef Id="LuxComponents" />
55 <ComponentGroupRef Id="DocComponents" />
56 </Feature>
57
58 <FeatureRef Id="Feature_MSBuild" />
59 <FeatureRef Id="Feature_Intellisense2010" />
60 <FeatureRef Id="Feature_Intellisense2012" />
61 <FeatureRef Id="Feature_Intellisense2013" />
62 <FeatureRef Id="Feature_Intellisense2015" />
63 </Product>
64</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/QtExec.bad/v4_expected.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/QtExec.bad/v4_expected.wxs
new file mode 100644
index 00000000..95d2f618
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/QtExec.bad/v4_expected.wxs
@@ -0,0 +1,63 @@
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
4
5<?include WixVer.wxi ?>
6
7<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
8 <Package Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)" Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D" InstallerVersion="200">
9
10 <SoftwareTag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
11
12 <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
13
14 <MediaTemplate CabinetTemplate="core{0}.cab" />
15
16 <Property Id="QtExecCmdTimeout" Value="600000" />
17 <CustomAction Id="InstallVSTemplateCommand" Property="WixQuietExecCmdLine" Value="&quot;[VSENVPRODUCT80]\devenv.exe&quot; /setup" />
18 <CustomAction Id="InstallVSTemplate" DllEntry="WixQuietExec" Return="asyncWait" BinaryRef="Wix4UtilCA_X86" />
19
20 <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
21 <Component Id="Licensing" Directory="INSTALLFOLDER">
22 <File Id="LICENSE.TXT" Source="LICENSE.TXT" />
23 </Component>
24
25 <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
26 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
27 <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
28 </RegistryKey>
29 </Component>
30
31 <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
32 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
33 <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
34 </RegistryKey>
35 </Component>
36
37 <Component Id="ProductInformation" Directory="BinFolder">
38 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
39 <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string" />
40 <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
41 </RegistryKey>
42
43 <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
44 </Component>
45
46 <Component Directory="BinFolder">
47 <File Id="wixtoolset.org.ico" Source="common\wixtoolset.org.ico" />
48 <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
49 </Component>
50
51 <ComponentGroupRef Id="ToolsetComponents" />
52 <ComponentGroupRef Id="ExtensionComponents" />
53 <ComponentGroupRef Id="LuxComponents" />
54 <ComponentGroupRef Id="DocComponents" />
55 </Feature>
56
57 <FeatureRef Id="Feature_MSBuild" />
58 <FeatureRef Id="Feature_Intellisense2010" />
59 <FeatureRef Id="Feature_Intellisense2012" />
60 <FeatureRef Id="Feature_Intellisense2013" />
61 <FeatureRef Id="Feature_Intellisense2015" />
62 </Package>
63</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/QtExec/v3.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/QtExec/v3.wxs
new file mode 100644
index 00000000..8d81a758
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/QtExec/v3.wxs
@@ -0,0 +1,64 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!-- 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. -->
3
4
5
6<?include WixVer.wxi ?>
7
8<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:swid="http://schemas.microsoft.com/wix/TagExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
9 <Product Id="*" Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)"
10 Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D">
11 <Package Compressed="yes" InstallerVersion="200" SummaryCodepage="1252" InstallScope="perMachine" />
12 <swid:Tag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
13
14 <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
15
16 <MediaTemplate CabinetTemplate="core{0}.cab" />
17
18 <CustomAction Id="InstallVSTemplateCommand" Property="QtExecCmdLine" Value="&quot;[VSENVPRODUCT80]\devenv.exe&quot; /setup" />
19 <CustomAction Id="InstallVSTemplate" BinaryKey="WixCA" DllEntry="CAQuietExec" Return="asyncWait" />
20
21 <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
22 <Component Id="Licensing" Directory="INSTALLFOLDER">
23 <File Source="LICENSE.TXT" />
24 </Component>
25
26 <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
27 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
28 <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
29 </RegistryKey>
30 </Component>
31
32 <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
33 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
34 <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
35 </RegistryKey>
36 </Component>
37
38 <Component Id="ProductInformation" Directory="BinFolder">
39 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
40 <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string"/>
41 <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
42 </RegistryKey>
43
44 <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
45 </Component>
46
47 <Component Directory="BinFolder">
48 <File Source="common\wixtoolset.org.ico" />
49 <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
50 </Component>
51
52 <ComponentGroupRef Id="ToolsetComponents" />
53 <ComponentGroupRef Id="ExtensionComponents" />
54 <ComponentGroupRef Id="LuxComponents" />
55 <ComponentGroupRef Id="DocComponents" />
56 </Feature>
57
58 <FeatureRef Id="Feature_MSBuild" />
59 <FeatureRef Id="Feature_Intellisense2010" />
60 <FeatureRef Id="Feature_Intellisense2012" />
61 <FeatureRef Id="Feature_Intellisense2013" />
62 <FeatureRef Id="Feature_Intellisense2015" />
63 </Product>
64</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/QtExec/v4_expected.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/QtExec/v4_expected.wxs
new file mode 100644
index 00000000..f24d3f8f
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/QtExec/v4_expected.wxs
@@ -0,0 +1,62 @@
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
4
5<?include WixVer.wxi ?>
6
7<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
8 <Package Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)" Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D" InstallerVersion="200">
9
10 <SoftwareTag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
11
12 <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
13
14 <MediaTemplate CabinetTemplate="core{0}.cab" />
15
16 <CustomAction Id="InstallVSTemplateCommand" Property="WixQuietExecCmdLine" Value="&quot;[VSENVPRODUCT80]\devenv.exe&quot; /setup" />
17 <CustomAction Id="InstallVSTemplate" DllEntry="WixQuietExec" Return="asyncWait" BinaryRef="Wix4UtilCA_X86" />
18
19 <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
20 <Component Id="Licensing" Directory="INSTALLFOLDER">
21 <File Id="LICENSE.TXT" Source="LICENSE.TXT" />
22 </Component>
23
24 <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
25 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
26 <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
27 </RegistryKey>
28 </Component>
29
30 <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
31 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
32 <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
33 </RegistryKey>
34 </Component>
35
36 <Component Id="ProductInformation" Directory="BinFolder">
37 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
38 <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string" />
39 <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
40 </RegistryKey>
41
42 <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
43 </Component>
44
45 <Component Directory="BinFolder">
46 <File Id="wixtoolset.org.ico" Source="common\wixtoolset.org.ico" />
47 <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
48 </Component>
49
50 <ComponentGroupRef Id="ToolsetComponents" />
51 <ComponentGroupRef Id="ExtensionComponents" />
52 <ComponentGroupRef Id="LuxComponents" />
53 <ComponentGroupRef Id="DocComponents" />
54 </Feature>
55
56 <FeatureRef Id="Feature_MSBuild" />
57 <FeatureRef Id="Feature_Intellisense2010" />
58 <FeatureRef Id="Feature_Intellisense2012" />
59 <FeatureRef Id="Feature_Intellisense2013" />
60 <FeatureRef Id="Feature_Intellisense2015" />
61 </Package>
62</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/SingleFile/ConvertedSingleFile.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/SingleFile/ConvertedSingleFile.wxs
new file mode 100644
index 00000000..5bcdaf59
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/SingleFile/ConvertedSingleFile.wxs
@@ -0,0 +1,59 @@
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
4
5<?include WixVer.wxi ?>
6
7<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
8 <Package Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)" Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D" InstallerVersion="200">
9
10 <SoftwareTag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
11
12 <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
13
14 <MediaTemplate CabinetTemplate="core{0}.cab" />
15
16 <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
17 <Component Id="Licensing" Directory="INSTALLFOLDER">
18 <File Id="LICENSE.TXT" Source="LICENSE.TXT" />
19 </Component>
20
21 <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
22 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
23 <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
24 </RegistryKey>
25 </Component>
26
27 <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
28 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
29 <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
30 </RegistryKey>
31 </Component>
32
33 <Component Id="ProductInformation" Directory="BinFolder">
34 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
35 <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string" />
36 <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
37 </RegistryKey>
38
39 <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
40 </Component>
41
42 <Component Directory="BinFolder">
43 <File Id="wixtoolset.org.ico" Source="common\wixtoolset.org.ico" />
44 <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
45 </Component>
46
47 <ComponentGroupRef Id="ToolsetComponents" />
48 <ComponentGroupRef Id="ExtensionComponents" />
49 <ComponentGroupRef Id="LuxComponents" />
50 <ComponentGroupRef Id="DocComponents" />
51 </Feature>
52
53 <FeatureRef Id="Feature_MSBuild" />
54 <FeatureRef Id="Feature_Intellisense2010" />
55 <FeatureRef Id="Feature_Intellisense2012" />
56 <FeatureRef Id="Feature_Intellisense2013" />
57 <FeatureRef Id="Feature_Intellisense2015" />
58 </Package>
59</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/SingleFile/SingleFile.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/SingleFile/SingleFile.wxs
new file mode 100644
index 00000000..310ae811
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/SingleFile/SingleFile.wxs
@@ -0,0 +1,61 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!-- 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. -->
3
4
5
6<?include WixVer.wxi ?>
7
8<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:swid="http://schemas.microsoft.com/wix/TagExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
9 <Product Id="*" Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)"
10 Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D">
11 <Package Compressed="yes" InstallerVersion="200" SummaryCodepage="1252" InstallScope="perMachine" />
12 <swid:Tag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
13
14 <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
15
16 <MediaTemplate CabinetTemplate="core{0}.cab" />
17
18 <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
19 <Component Id="Licensing" Directory="INSTALLFOLDER">
20 <File Source="LICENSE.TXT" />
21 </Component>
22
23 <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
24 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
25 <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
26 </RegistryKey>
27 </Component>
28
29 <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
30 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
31 <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
32 </RegistryKey>
33 </Component>
34
35 <Component Id="ProductInformation" Directory="BinFolder">
36 <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
37 <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string"/>
38 <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
39 </RegistryKey>
40
41 <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
42 </Component>
43
44 <Component Directory="BinFolder">
45 <File Source="common\wixtoolset.org.ico" />
46 <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
47 </Component>
48
49 <ComponentGroupRef Id="ToolsetComponents" />
50 <ComponentGroupRef Id="ExtensionComponents" />
51 <ComponentGroupRef Id="LuxComponents" />
52 <ComponentGroupRef Id="DocComponents" />
53 </Feature>
54
55 <FeatureRef Id="Feature_MSBuild" />
56 <FeatureRef Id="Feature_Intellisense2010" />
57 <FeatureRef Id="Feature_Intellisense2012" />
58 <FeatureRef Id="Feature_Intellisense2013" />
59 <FeatureRef Id="Feature_Intellisense2015" />
60 </Product>
61</Wix>
diff --git a/src/wix/test/WixToolsetTest.Converters/UtilExtensionFixture.cs b/src/wix/test/WixToolsetTest.Converters/UtilExtensionFixture.cs
new file mode 100644
index 00000000..10450c68
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/UtilExtensionFixture.cs
@@ -0,0 +1,190 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class UtilExtensionFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixCloseAppsCondition()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>",
19 " <Fragment>",
20 " <util:CloseApplication Id='EndApp' Target='example.exe'>",
21 " a&lt;>b",
22 " </util:CloseApplication>",
23 " </Fragment>",
24 "</Wix>");
25
26 var expected = new[]
27 {
28 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:util=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
29 " <Fragment>",
30 " <util:CloseApplication Id=\"EndApp\" Target=\"example.exe\" Condition=\"a&lt;&gt;b\" />",
31 " </Fragment>",
32 "</Wix>"
33 };
34
35 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
36
37 var messaging = new MockMessaging();
38 var converter = new WixConverter(messaging, 2, null, null);
39
40 var errors = converter.ConvertDocument(document);
41 Assert.Equal(3, errors);
42
43 var actualLines = UnformattedDocumentLines(document);
44 WixAssert.CompareLineByLine(expected, actualLines);
45 }
46
47 [Fact]
48 public void FixXmlConfigValue()
49 {
50 var parse = String.Join(Environment.NewLine,
51 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>",
52 " <Fragment>",
53 " <util:XmlConfig Id='Change' ElementPath='book'>",
54 " a&lt;>b",
55 " </util:XmlConfig>",
56 " </Fragment>",
57 "</Wix>");
58
59 var expected = new[]
60 {
61 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:util=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
62 " <Fragment>",
63 " <util:XmlConfig Id=\"Change\" ElementPath=\"book\" Value=\"a&lt;&gt;b\" />",
64 " </Fragment>",
65 "</Wix>"
66 };
67
68 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
69
70 var messaging = new MockMessaging();
71 var converter = new WixConverter(messaging, 2, null, null);
72
73 var errors = converter.ConvertDocument(document);
74 Assert.Equal(3, errors);
75
76 var actualLines = UnformattedDocumentLines(document);
77 WixAssert.CompareLineByLine(expected, actualLines);
78 }
79
80 [Fact]
81 public void WarnsOnAllRegistryValueSearches()
82 {
83 var parse = String.Join(Environment.NewLine,
84 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>",
85 " <Fragment>",
86 " <util:RegistrySearch Id='RegValue' Root='HKLM' Key='Converter' Variable='Test' />",
87 " <util:RegistrySearch Id='RegValue2' Root='HKLM' Key='Converter' Variable='Test' Result='value' />",
88 " <util:RegistrySearch Id='RegValue3' Root='HKLM' Key='Converter' Variable='Test' Result='exists' />",
89 " </Fragment>",
90 "</Wix>");
91
92 var expected = new[]
93 {
94 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:util=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
95 " <Fragment>",
96 " <util:RegistrySearch Id=\"RegValue\" Root=\"HKLM\" Key=\"Converter\" Variable=\"Test\" />",
97 " <util:RegistrySearch Id=\"RegValue2\" Root=\"HKLM\" Key=\"Converter\" Variable=\"Test\" Result=\"value\" />",
98 " <util:RegistrySearch Id=\"RegValue3\" Root=\"HKLM\" Key=\"Converter\" Variable=\"Test\" Result=\"exists\" />",
99 " </Fragment>",
100 "</Wix>"
101 };
102
103 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
104
105 var messaging = new MockMessaging();
106 var converter = new WixConverter(messaging, 2, null, null);
107
108 var errors = converter.ConvertDocument(document);
109 Assert.Equal(4, errors);
110
111 var actualLines = UnformattedDocumentLines(document);
112 WixAssert.CompareLineByLine(expected, actualLines);
113 }
114
115
116 [Fact]
117 public void FixXmlConfigValueCData()
118 {
119 var parse = String.Join(Environment.NewLine,
120 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>",
121 " <Fragment>",
122 " <util:XmlConfig Id='Change' ElementPath='book'>",
123 " <![CDATA[a<>b]]>",
124 " </util:XmlConfig>",
125 " </Fragment>",
126 "</Wix>");
127
128 var expected = new[]
129 {
130 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:util=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
131 " <Fragment>",
132 " <util:XmlConfig Id=\"Change\" ElementPath=\"book\" Value=\"a&lt;&gt;b\" />",
133 " </Fragment>",
134 "</Wix>"
135 };
136
137 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
138
139 var messaging = new MockMessaging();
140 var converter = new WixConverter(messaging, 2, null, null);
141
142 var errors = converter.ConvertDocument(document);
143 Assert.Equal(3, errors);
144
145 var actualLines = UnformattedDocumentLines(document);
146 WixAssert.CompareLineByLine(expected, actualLines);
147 }
148
149 [Fact]
150 public void FixQueryOsPropertyRefs()
151 {
152 var parse = String.Join(Environment.NewLine,
153 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>",
154 " <Fragment>",
155 " <PropertyRef Id=\"WIX_SUITE_ENTERPRISE\" />",
156 " <PropertyRef Id=\"WIX_DIR_COMMON_DOCUMENTS\" />",
157 " <CustomActionRef Id=\"WixFailWhenDeferred\" />",
158 " <UI>",
159 " <PropertyRef Id=\"WIX_ACCOUNT_LOCALSERVICE\" />",
160 " </UI>",
161 " </Fragment>",
162 "</Wix>");
163
164 var expected = new[]
165 {
166 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:util=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
167 " <Fragment>",
168 " <util:QueryWindowsSuiteInfo />",
169 " <util:QueryWindowsDirectories />",
170 " <util:FailWhenDeferred />",
171 " <UI>",
172 " <util:QueryWindowsWellKnownSIDs />",
173 " </UI>",
174 " </Fragment>",
175 "</Wix>"
176 };
177
178 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
179
180 var messaging = new MockMessaging();
181 var converter = new WixConverter(messaging, 2, null, null);
182
183 var errors = converter.ConvertDocument(document);
184 Assert.Equal(6, errors);
185
186 var actualLines = UnformattedDocumentLines(document);
187 WixAssert.CompareLineByLine(expected, actualLines);
188 }
189 }
190}
diff --git a/src/wix/test/WixToolsetTest.Converters/VariableFixture.cs b/src/wix/test/WixToolsetTest.Converters/VariableFixture.cs
new file mode 100644
index 00000000..b7b7388f
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/VariableFixture.cs
@@ -0,0 +1,86 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class VariableFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void FixFormattedType()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
19 " <Fragment>",
20 " <Variable Name='ExplicitString' Type='string' Value='explicit' />",
21 " <Variable Name='ImplicitNumber' Value='42' />",
22 " <Variable Name='ImplicitString' Value='implicit' />",
23 " <Variable Name='ImplicitVersion' Value='v2' />",
24 " <Variable Name='NoTypeOrValue' />",
25 " </Fragment>",
26 "</Wix>");
27
28 var expected = new[]
29 {
30 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
31 " <Fragment>",
32 " <Variable Name=\"ExplicitString\" Type=\"formatted\" Value=\"explicit\" />",
33 " <Variable Name=\"ImplicitNumber\" Value=\"42\" />",
34 " <Variable Name=\"ImplicitString\" Value=\"implicit\" Type=\"formatted\" />",
35 " <Variable Name=\"ImplicitVersion\" Value=\"v2\" />",
36 " <Variable Name=\"NoTypeOrValue\" />",
37 " </Fragment>",
38 "</Wix>"
39 };
40
41 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
42
43 var messaging = new MockMessaging();
44 var converter = new WixConverter(messaging, 2, null, null);
45
46 var errors = converter.ConvertDocument(document);
47 Assert.Equal(3, errors);
48
49 var actualLines = UnformattedDocumentLines(document);
50 WixAssert.CompareLineByLine(expected, actualLines);
51 }
52
53 [Fact]
54 public void DoesntFixFormattedTypeFromV4()
55 {
56 var parse = String.Join(Environment.NewLine,
57 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
58 " <Fragment>",
59 " <Variable Name='ImplicitString' Value='implicit' />",
60 " <Variable Name='ExplicitString' Type='string' Value='explicit' />",
61 " </Fragment>",
62 "</Wix>");
63
64 var expected = new[]
65 {
66 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
67 " <Fragment>",
68 " <Variable Name=\"ImplicitString\" Value=\"implicit\" />",
69 " <Variable Name=\"ExplicitString\" Type=\"string\" Value=\"explicit\" />",
70 " </Fragment>",
71 "</Wix>"
72 };
73
74 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
75
76 var messaging = new MockMessaging();
77 var converter = new WixConverter(messaging, 2, null, null);
78
79 var errors = converter.ConvertDocument(document);
80 Assert.Equal(0, errors);
81
82 var actualLines = UnformattedDocumentLines(document);
83 WixAssert.CompareLineByLine(expected, actualLines);
84 }
85 }
86}
diff --git a/src/wix/test/WixToolsetTest.Converters/Wix4ConversionFixture.cs b/src/wix/test/WixToolsetTest.Converters/Wix4ConversionFixture.cs
new file mode 100644
index 00000000..16a68895
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/Wix4ConversionFixture.cs
@@ -0,0 +1,54 @@
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
3namespace WixToolsetTest.Converters
4{
5 using System;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Converters;
9 using WixToolsetTest.Converters.Mocks;
10 using Xunit;
11
12 public class Wix4ConversionFixture : BaseConverterFixture
13 {
14 [Fact]
15 public void DoesNotAddFileId()
16 {
17 var parse = String.Join(Environment.NewLine,
18 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
20 " <Fragment>",
21 " <ComponentGroup Id='ProductComponents' Directory='INSTALLFOLDER'>",
22 " <Component>",
23 " <File Source='example.txt' />",
24 " </Component>",
25 " </ComponentGroup>",
26 " </Fragment>",
27 "</Wix>");
28
29 var expected = new[]
30 {
31 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
32 " <Fragment>",
33 " <ComponentGroup Id=\"ProductComponents\" Directory=\"INSTALLFOLDER\">",
34 " <Component>",
35 " <File Source=\"example.txt\" />",
36 " </Component>",
37 " </ComponentGroup>",
38 " </Fragment>",
39 "</Wix>"
40 };
41
42 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
43
44 var messaging = new MockMessaging();
45 var converter = new WixConverter(messaging, 2, null, null);
46
47 var errors = converter.ConvertDocument(document);
48 Assert.Equal(1, errors);
49
50 var actualLines = UnformattedDocumentLines(document);
51 WixAssert.CompareLineByLine(expected, actualLines);
52 }
53 }
54}
diff --git a/src/wix/test/WixToolsetTest.Converters/WixToolsetTest.Converters.csproj b/src/wix/test/WixToolsetTest.Converters/WixToolsetTest.Converters.csproj
new file mode 100644
index 00000000..29b02b95
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/WixToolsetTest.Converters.csproj
@@ -0,0 +1,31 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>netcoreapp3.1</TargetFramework>
7 <IsPackable>false</IsPackable>
8 </PropertyGroup>
9
10 <ItemGroup>
11 <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
12 </ItemGroup>
13
14 <ItemGroup>
15 <ProjectReference Include="..\..\WixToolset.Converters\WixToolset.Converters.csproj" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" />
20 <PackageReference Include="WixToolset.Core" Version="4.0.*" />
21 <PackageReference Include="WixToolset.Core.Burn" Version="4.0.*" />
22 <PackageReference Include="WixToolset.Core.TestPackage" Version="4.0.*" />
23 <PackageReference Include="WixToolset.Core.WindowsInstaller" Version="4.0.*" />
24 </ItemGroup>
25
26 <ItemGroup>
27 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
28 <PackageReference Include="xunit" Version="2.4.1" />
29 <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="All" />
30 </ItemGroup>
31</Project>