aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2026-01-07 15:10:07 -0800
committerRob Mensching <rob@firegiant.com>2026-01-07 16:15:16 -0800
commit164ea64ea05c1298979cadda1842feaf86a1bda9 (patch)
treec5b5ba6e3bc0fbbc1bd9e84af5858a24f41917f4
parent130bbb7be53435479203209ec713f6cd9c9d1b45 (diff)
downloadwix-164ea64ea05c1298979cadda1842feaf86a1bda9.tar.gz
wix-164ea64ea05c1298979cadda1842feaf86a1bda9.tar.bz2
wix-164ea64ea05c1298979cadda1842feaf86a1bda9.zip
Fix anonymous Directory Id generation to be consistent
Fixes 7861
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs67
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs51
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/AnonymousDirectories.wxs22
5 files changed, 120 insertions, 40 deletions
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs
index f2d70f2b..1665ced3 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs
@@ -62,7 +62,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
62 62
63 private void AddSectionToData() 63 private void AddSectionToData()
64 { 64 {
65 var cellsByTableAndRowId = new Dictionary<string, List<WixCustomTableCellSymbol>>(); 65 var directoryRowsById = new Dictionary<string, Row>(StringComparer.Ordinal);
66 var cellsByTableAndRowId = new Dictionary<string, List<WixCustomTableCellSymbol>>(StringComparer.Ordinal);
66 67
67 foreach (var symbol in this.Section.Symbols) 68 foreach (var symbol in this.Section.Symbols)
68 { 69 {
@@ -107,7 +108,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
107 break; 108 break;
108 109
109 case SymbolDefinitionType.Directory: 110 case SymbolDefinitionType.Directory:
110 this.AddDirectorySymbol((DirectorySymbol)symbol); 111 this.AddDirectorySymbol((DirectorySymbol)symbol, directoryRowsById);
111 break; 112 break;
112 113
113 case SymbolDefinitionType.DuplicateFile: 114 case SymbolDefinitionType.DuplicateFile:
@@ -497,9 +498,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
497 this.Data.EnsureTable(this.TableDefinitions["ListBox"]); 498 this.Data.EnsureTable(this.TableDefinitions["ListBox"]);
498 } 499 }
499 500
500 private void AddDirectorySymbol(DirectorySymbol symbol) 501 private void AddDirectorySymbol(DirectorySymbol symbol, Dictionary<string, Row> directoryRowsById)
501 { 502 {
502 (var name, var parentDir) = this.AddDirectorySubdirectories(symbol); 503 (var name, var parentDir) = this.AddDirectorySubdirectories(symbol, directoryRowsById);
503 504
504 var shortName = symbol.ShortName; 505 var shortName = symbol.ShortName;
505 var sourceShortname = symbol.SourceShortName; 506 var sourceShortname = symbol.SourceShortName;
@@ -524,10 +525,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
524 525
525 var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName; 526 var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName;
526 527
527 var row = this.CreateRow(symbol, "Directory"); 528 this.CreateOrAddDirectoryRow(directoryRowsById, symbol, symbol.Id.Id, parentDir, defaultDir);
528 row[0] = symbol.Id.Id;
529 row[1] = parentDir;
530 row[2] = defaultDir;
531 529
532 if (OutputType.Module == this.Data.Type) 530 if (OutputType.Module == this.Data.Type)
533 { 531 {
@@ -1146,7 +1144,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1146 } 1144 }
1147 else 1145 else
1148 { 1146 {
1149 var after = (null == symbol.Before); 1147 var after = null == symbol.Before;
1150 row[2] = after ? symbol.After : symbol.Before; 1148 row[2] = after ? symbol.After : symbol.Before;
1151 row[3] = after ? 1 : 0; 1149 row[3] = after ? 1 : 0;
1152 } 1150 }
@@ -1271,7 +1269,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1271 } 1269 }
1272 } 1270 }
1273 1271
1274 private void AddWixPackageSymbol(WixPackageSymbol symbol) 1272 private void AddWixPackageSymbol(WixPackageSymbol _)
1275 { 1273 {
1276 // TODO: Remove the following from the compiler and do it here instead. 1274 // TODO: Remove the following from the compiler and do it here instead.
1277 //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); 1275 //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true);
@@ -1319,11 +1317,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1319 } 1317 }
1320 } 1318 }
1321 1319
1322 private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol) 1320 private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol, Dictionary<string, Row> directoryRowsById)
1323 { 1321 {
1324 var directory = symbol.Name.Trim(PathSeparatorChars); 1322 var directory = symbol.Name.Trim(PathSeparatorChars);
1325 var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR"); 1323 var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR");
1326 var directoryRows = this.Data.TryGetTable("Directory", out var table) ? table.Rows.ToDictionary(row => row.FieldAsString(0)) : new Dictionary<string, Row>();
1327 1324
1328 var start = 0; 1325 var start = 0;
1329 var end = directory.IndexOfAny(PathSeparatorChars); 1326 var end = directory.IndexOfAny(PathSeparatorChars);
@@ -1337,18 +1334,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1337 { 1334 {
1338 path = Path.Combine(path, subdirectoryName); 1335 path = Path.Combine(path, subdirectoryName);
1339 1336
1340 var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path); 1337 var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path, /*shortName:*/ null, /*sourceName:*/ null, /*shortSourceName:*/ null);
1341 var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef); 1338 var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef);
1339 var defaultDir = CreateMsiFilename(shortnameSubdirectory, subdirectoryName);
1342 1340
1343 if (!directoryRows.ContainsKey(id)) 1341 this.CreateOrAddDirectoryRow(directoryRowsById, symbol, id, parentDir, defaultDir);
1344 {
1345 var subdirectoryRow = this.CreateRow(symbol, "Directory");
1346 subdirectoryRow[0] = id;
1347 subdirectoryRow[1] = parentDir;
1348 subdirectoryRow[2] = CreateMsiFilename(shortnameSubdirectory, subdirectoryName);
1349
1350 directoryRows.Add(id, subdirectoryRow);
1351 }
1352 1342
1353 parentDir = id; 1343 parentDir = id;
1354 } 1344 }
@@ -1362,6 +1352,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1362 return (name, parentDir); 1352 return (name, parentDir);
1363 } 1353 }
1364 1354
1355 private Row CreateOrAddDirectoryRow(Dictionary<string, Row> directoryRowsById, DirectorySymbol symbol, string id, string parentDir, string defaultDir)
1356 {
1357 if (!directoryRowsById.TryGetValue(id, out var directoryRow))
1358 {
1359 directoryRow = this.CreateRow(symbol, "Directory");
1360 directoryRow[0] = id;
1361 directoryRow[1] = parentDir;
1362 directoryRow[2] = defaultDir;
1363
1364 directoryRowsById.Add(id, directoryRow);
1365 }
1366 else if (directoryRow.FieldAsString(1) != parentDir || directoryRow.FieldAsString(2) != defaultDir)
1367 {
1368 throw new WixException(WindowsInstallerBackendErrors.UnexpectedAnonymousDirectoryCollision(symbol.SourceLineNumbers, symbol.Id.Id, parentDir, defaultDir, directoryRow.SourceLineNumbers, directoryRow.FieldAsString(1), directoryRow.FieldAsString(2)));
1369 }
1370
1371 return directoryRow;
1372 }
1373
1365 private void EnsureRequiredTables() 1374 private void EnsureRequiredTables()
1366 { 1375 {
1367 // check for missing table and add them or display an error as appropriate 1376 // check for missing table and add them or display an error as appropriate
@@ -1412,7 +1421,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1412 "Upgrade" == table.Name || 1421 "Upgrade" == table.Name ||
1413 "WixMerge" == table.Name) 1422 "WixMerge" == table.Name)
1414 { 1423 {
1415 foreach (Row row in table.Rows) 1424 foreach (var row in table.Rows)
1416 { 1425 {
1417 this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); 1426 this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name));
1418 } 1427 }
@@ -1609,8 +1618,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1609 longName = longName.ToLowerInvariant(); 1618 longName = longName.ToLowerInvariant();
1610 1619
1611 // collect all the data 1620 // collect all the data
1612 var strings = new List<string>(1 + args.Length); 1621 var strings = new List<string>(1 + args.Length)
1613 strings.Add(longName); 1622 {
1623 longName
1624 };
1614 strings.AddRange(args); 1625 strings.AddRange(args);
1615 1626
1616 // prepare for hashing 1627 // prepare for hashing
@@ -1625,8 +1636,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1625 } 1636 }
1626 1637
1627 // generate the short file/directory name without an extension 1638 // generate the short file/directory name without an extension
1628 var shortName = new StringBuilder(Convert.ToBase64String(hash)); 1639 var shortName = new StringBuilder(Convert.ToBase64String(hash))
1629 shortName.Length = 8; 1640 {
1641 Length = 8
1642 };
1630 shortName.Replace('+', '-').Replace('/', '_'); 1643 shortName.Replace('+', '-').Replace('/', '_');
1631 1644
1632 if (keepExtension) 1645 if (keepExtension)
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs
index 756bb5e4..0935d964 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs
@@ -39,6 +39,11 @@ namespace WixToolset.Core.WindowsInstaller
39 return Message(null, Ids.InvalidWindowsInstallerWixpdbForValidation, "The validation .wixpdb file: {0} was not from a Windows Installer database build (.msi or .msm). Verify that the output type was actually an MSI Package or Merge Module.", wixpdbPath); 39 return Message(null, Ids.InvalidWindowsInstallerWixpdbForValidation, "The validation .wixpdb file: {0} was not from a Windows Installer database build (.msi or .msm). Verify that the output type was actually an MSI Package or Merge Module.", wixpdbPath);
40 } 40 }
41 41
42 public static Message UnexpectedAnonymousDirectoryCollision(SourceLineNumber sourceLineNumbers, string id, string parentDir, string defaultDir, SourceLineNumber existingSourceLineNumbers, string existingParentDir, string existingDefaultDir)
43 {
44 return Message(sourceLineNumbers, Ids.UnexpectedAnonymousDirectoryCollision, "This should not happen. The first directory id '{0}' uses parent directory '{1}' with DefaultDir '{2}'. The colliding directory uses parent directory '{3}' with DefaultDir '{4}' from line: {5}", id, parentDir, defaultDir, existingParentDir, existingDefaultDir, existingSourceLineNumbers.ToString());
45 }
46
42 public static Message UnknownDecompileType(string decompileType, string filePath) 47 public static Message UnknownDecompileType(string decompileType, string filePath)
43 { 48 {
44 return Message(null, Ids.UnknownDecompileType, "Unknown decompile type '{0}' from input: {1}", decompileType, filePath); 49 return Message(null, Ids.UnknownDecompileType, "Unknown decompile type '{0}' from input: {1}", decompileType, filePath);
@@ -65,6 +70,7 @@ namespace WixToolset.Core.WindowsInstaller
65 UnknownDecompileType = 7504, 70 UnknownDecompileType = 7504,
66 UnknownValidationTargetFileExtension = 7505, 71 UnknownValidationTargetFileExtension = 7505,
67 InvalidWindowsInstallerWixpdbForValidation = 7506, 72 InvalidWindowsInstallerWixpdbForValidation = 7506,
73 UnexpectedAnonymousDirectoryCollision = 7507,
68 } // last available is 7999. 8000 is BurnBackendErrors. 74 } // last available is 7999. 8000 is BurnBackendErrors.
69 } 75 }
70} 76}
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs
index c132eb80..ca5f869d 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs
@@ -163,6 +163,45 @@ namespace WixToolsetTest.CoreIntegration
163 } 163 }
164 164
165 [Fact] 165 [Fact]
166 public void CanGetAnonymousDirectories()
167 {
168 var folder = TestData.Get("TestData");
169
170 using (var fs = new DisposableFileSystem())
171 {
172 var baseFolder = fs.GetFolder();
173 var intermediateFolder = Path.Combine(baseFolder, "obj");
174 var wixlibPath = Path.Combine(baseFolder, "bin", "test.msi");
175
176 var result = WixRunner.Execute(
177 [
178 "build",
179 "-arch", "x64",
180 Path.Combine(folder, "Directory", "AnonymousDirectories.wxs"),
181 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
182 "-intermediateFolder", intermediateFolder,
183 "-o", wixlibPath
184 ]);
185
186 result.AssertSuccess();
187
188 var pdb = WindowsInstallerData.Load(Path.Combine(baseFolder, "bin", "test.wixpdb"));
189 var directoryRows = pdb.Tables["Directory"].Rows;
190
191 var dirs = directoryRows.Select(d => d.ToString()).OrderBy(s => s).ToArray();
192 WixAssert.CompareLineByLine(
193 [
194 @"DesktopFolder/TARGETDIR/Desktop",
195 @"dHKac23vLoBC5fFrqxqAIybMFOj0/DesktopFolder/a",
196 @"dKom2ks4onBH9RsYLDhjge71s7s8/dlvpu0ovv8DWWVrdCVSYzDfsibzg/c",
197 @"dlvpu0ovv8DWWVrdCVSYzDfsibzg/dHKac23vLoBC5fFrqxqAIybMFOj0/b",
198 @"SomeFolder/TARGETDIR/fef2brvc|Some Folder",
199 @"TARGETDIR//SourceDir"
200 ], dirs);
201 }
202 }
203
204 [Fact]
166 public void CanGetDefaultName() 205 public void CanGetDefaultName()
167 { 206 {
168 var folder = TestData.Get(@"TestData"); 207 var folder = TestData.Get(@"TestData");
@@ -293,9 +332,9 @@ namespace WixToolsetTest.CoreIntegration
293 var directoryRows = data.Tables["Directory"].Rows; 332 var directoryRows = data.Tables["Directory"].Rows;
294 WixAssert.CompareLineByLine(new[] 333 WixAssert.CompareLineByLine(new[]
295 { 334 {
296 "d4EceYatXTyy8HXPt5B6DT9Rj.wE:ProgramFilesFolder:u7-b4gch|Example Corporation", 335 "dwGveZhe5wcMbbRyRAkRwm2sqnE4:ProgramFilesFolder:u7-b4gch|Example Corporation",
297 "dSJ1pgiASlW7kJTu0wqsGBklJsS0:d4EceYatXTyy8HXPt5B6DT9Rj.wE:vjj-gxay|Test Product", 336 "d8kPFuRMPxdOxfpYS0O8azlhLUpY:dwGveZhe5wcMbbRyRAkRwm2sqnE4:vjj-gxay|Test Product",
298 "BinFolder:dSJ1pgiASlW7kJTu0wqsGBklJsS0:bin", 337 "BinFolder:d8kPFuRMPxdOxfpYS0O8azlhLUpY:bin",
299 "ProgramFilesFolder:TARGETDIR:PFiles", 338 "ProgramFilesFolder:TARGETDIR:PFiles",
300 "TARGETDIR::SourceDir" 339 "TARGETDIR::SourceDir"
301 }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray()); 340 }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray());
@@ -386,9 +425,9 @@ namespace WixToolsetTest.CoreIntegration
386 var directoryRows = data.Tables["Directory"].Rows; 425 var directoryRows = data.Tables["Directory"].Rows;
387 WixAssert.CompareLineByLine(new[] 426 WixAssert.CompareLineByLine(new[]
388 { 427 {
389 @"d1nVb5_zcCwRCz7i2YXNAofGRmfc:ProgramFilesFolder:a", 428 @"dKO7wPCF.XLmq6KnqybHHgcBBqtU:dvFwapipzsdDBjHbM5DUi_2llN.k:c",
390 @"dijlG.bNicFgvj1_DujiGg9EBGrQ:d1nVb5_zcCwRCz7i2YXNAofGRmfc:b", 429 @"dvFwapipzsdDBjHbM5DUi_2llN.k:dyJuT.V6E4sWuHVv3CZiBNvrX2Lo:b",
391 @"dKO7wPCF.XLmq6KnqybHHgcBBqtU:dijlG.bNicFgvj1_DujiGg9EBGrQ:c", 430 @"dyJuT.V6E4sWuHVv3CZiBNvrX2Lo:ProgramFilesFolder:a",
392 "ProgramFilesFolder:TARGETDIR:PFiles", 431 "ProgramFilesFolder:TARGETDIR:PFiles",
393 "TARGETDIR::SourceDir" 432 "TARGETDIR::SourceDir"
394 }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).OrderBy(s => s).ToArray()); 433 }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).OrderBy(s => s).ToArray());
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs
index 8dc135eb..3c7342ce 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs
@@ -56,13 +56,13 @@ namespace WixToolsetTest.CoreIntegration
56 56
57 var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); 57 var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
58 var directoryRows = data.Tables["Directory"].Rows; 58 var directoryRows = data.Tables["Directory"].Rows;
59 WixAssert.CompareLineByLine(new[] 59 WixAssert.CompareLineByLine(
60 { 60 [
61 "d4EceYatXTyy8HXPt5B6DT9Rj.wE:u7-b4gch|Example Corporation", 61 "dwGveZhe5wcMbbRyRAkRwm2sqnE4:ProgramFilesFolder:u7-b4gch|Example Corporation",
62 "INSTALLFOLDER:oekcr5lq|MsiPackage", 62 "INSTALLFOLDER:dwGveZhe5wcMbbRyRAkRwm2sqnE4:oekcr5lq|MsiPackage",
63 "ProgramFilesFolder:PFiles", 63 "ProgramFilesFolder:TARGETDIR:PFiles",
64 "TARGETDIR:SourceDir" 64 "TARGETDIR::SourceDir"
65 }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(2)).ToArray()); 65 ], [.. directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2))]);
66 } 66 }
67 } 67 }
68 68
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/AnonymousDirectories.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/AnonymousDirectories.wxs
new file mode 100644
index 00000000..4c25fed0
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/AnonymousDirectories.wxs
@@ -0,0 +1,22 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Package Id="WixToolsetTest.TestPackage" Name="~RedundantSubdirectories" Version="1.0.0.0" Manufacturer="Example Corporation" Compressed="no">
4 <ComponentGroupRef Id="Files" />
5
6 <Directory Id="SomeFolder" Name="Some Folder" />
7 </Package>
8
9 <Fragment>
10 <ComponentGroup Id="Files">
11 <Component Directory="SomeFolder">
12 <File Source="test.txt" KeyPath="yes">
13 <Shortcut Id="Shortcut1" Name="Test Shortcut" Directory="DesktopFolder" Subdirectory="a\b\c" Advertise="yes" />
14 </File>
15
16 <RemoveFolder Id="RemoveAFolder" Directory="DesktopFolder" Subdirectory="a" On="uninstall" />
17 <RemoveFolder Id="RemoveBFolder" Directory="DesktopFolder" Subdirectory="a\b" On="uninstall" />
18 <RemoveFolder Id="RemoveCFolder" Directory="DesktopFolder" Subdirectory="a\b\c" On="uninstall" />
19 </Component>
20 </ComponentGroup>
21 </Fragment>
22</Wix>