aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2025-11-02 17:06:45 -0800
committerRob Mensching <rob@firegiant.com>2025-11-03 15:15:41 -0800
commitea519e45b11587c43579823a19f792a609843f83 (patch)
tree6a9a1e59120fc762adcfdf5ec8034c764b57e4fe
parentd2ba0da55725f2908b67e1470afc7cfd71cb3d1f (diff)
downloadwix-robmen/maxpath.tar.gz
wix-robmen/maxpath.tar.bz2
wix-robmen/maxpath.zip
Better handling of long pathsrobmen/maxpath
By integrating the use of long path prefix (\?\\) and careful use of short paths we can workaround most of the MSI API long path limitations. It's not perfect as short paths that still exceed MAX_PATH will fail in most MSI APIs. But accessing files placed in cabinets and copied around should now be fully long path supported. Fixes 3065 9115
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.Native/LongPathUtil.cs78
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/Database.cs120
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/Installer.cs22
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs2
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs4
-rw-r--r--src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs4
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs4
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs9
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs3
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs2
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/LongPathFixture.cs89
-rw-r--r--src/wix/wixnative/wixnative.manifest9
-rw-r--r--src/wix/wixnative/wixnative.vcxproj3
21 files changed, 324 insertions, 41 deletions
diff --git a/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs
index 5231e0be..70b98222 100644
--- a/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs
@@ -69,7 +69,7 @@ namespace WixToolset.Core.Burn.Bind
69 { 69 {
70 var payload = payloadSymbolsById[msiPackage.PayloadRef]; 70 var payload = payloadSymbolsById[msiPackage.PayloadRef];
71 71
72 using (var db = new Database(payload.SourceFile.Path, OpenDatabase.ReadOnly)) 72 using (var db = Database.OpenAsReadOnly(payload.SourceFile.Path))
73 { 73 {
74 if (db.TableExists("SoftwareIdentificationTag")) 74 if (db.TableExists("SoftwareIdentificationTag"))
75 { 75 {
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs
index d6cf1cfd..0b78c545 100644
--- a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs
@@ -164,7 +164,7 @@ namespace WixToolset.Core.Burn.Bundles
164 164
165 this.CheckIfWindowsInstallerFileTooLarge(this.PackagePayload.SourceLineNumbers, sourcePath, "MSI"); 165 this.CheckIfWindowsInstallerFileTooLarge(this.PackagePayload.SourceLineNumbers, sourcePath, "MSI");
166 166
167 using (var db = new Database(sourcePath, OpenDatabase.ReadOnly)) 167 using (var db = Database.OpenAsReadOnly(sourcePath))
168 { 168 {
169 // Read data out of the msi database... 169 // Read data out of the msi database...
170 using (var sumInfo = new SummaryInformation(db)) 170 using (var sumInfo = new SummaryInformation(db))
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs
index b889c2ce..8d1e9c39 100644
--- a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs
@@ -98,7 +98,7 @@ namespace WixToolset.Core.Burn.Bundles
98 98
99 try 99 try
100 { 100 {
101 using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) 101 using (var db = Database.OpenAsReadOnly(sourcePath, asPatch: true))
102 { 102 {
103 // Read data out of the msp database... 103 // Read data out of the msp database...
104 using (var sumInfo = new SummaryInformation(db)) 104 using (var sumInfo = new SummaryInformation(db))
diff --git a/src/wix/WixToolset.Core.Native/LongPathUtil.cs b/src/wix/WixToolset.Core.Native/LongPathUtil.cs
new file mode 100644
index 00000000..c24f1736
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/LongPathUtil.cs
@@ -0,0 +1,78 @@
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.Core.Native
4{
5 using System.IO;
6 using System.Runtime.InteropServices;
7 using System.Text;
8
9 internal static class PathUtil
10 {
11 private const int MaxPath = 260;
12 private const string LongPathPrefix = @"\\?\";
13
14 public static bool CreateOrGetShortPath(string path, out string shortPath)
15 {
16 var fileCreated = false;
17
18 // The file must exist so we can get its short path.
19 if (!File.Exists(path))
20 {
21 using (File.Create(path))
22 {
23 }
24
25 fileCreated = true;
26 }
27
28 // Use the short path to avoid issues with long paths in the MSI API.
29 shortPath = GetShortPath(path);
30
31 return fileCreated;
32 }
33
34 public static string GetPrefixedLongPath(string path)
35 {
36 if (path.Length > MaxPath && !path.StartsWith(LongPathPrefix))
37 {
38 path = LongPathPrefix + path;
39 }
40
41 return path;
42 }
43
44 public static string GetShortPath(string longPath)
45 {
46 var path = GetPrefixedLongPath(longPath);
47
48 var buffer = new StringBuilder(MaxPath); // start with MAX_PATH.
49
50 var result = GetShortPathName(path, buffer, (uint)buffer.Capacity);
51
52 // If result > buffer.Capacity, reallocate and call again (even though we're usually using short names to avoid long path)
53 // so the short path result is still going to end up too long for APIs requiring a short path.
54 if (result > buffer.Capacity)
55 {
56 buffer = new StringBuilder((int)result);
57
58 result = GetShortPathName(path, buffer, (uint)buffer.Capacity);
59 }
60
61 // If we succeeded, return the short path without the prefix.
62 if (result > 0)
63 {
64 path = buffer.ToString();
65
66 if (path.StartsWith(LongPathPrefix))
67 {
68 path = path.Substring(LongPathPrefix.Length);
69 }
70 }
71
72 return path;
73 }
74
75 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
76 private static extern uint GetShortPathName(string lpszLongPath, StringBuilder lpszShortPath, uint cchBuffer);
77 }
78}
diff --git a/src/wix/WixToolset.Core.Native/Msi/Database.cs b/src/wix/WixToolset.Core.Native/Msi/Database.cs
index 18e5066d..33eb8de6 100644
--- a/src/wix/WixToolset.Core.Native/Msi/Database.cs
+++ b/src/wix/WixToolset.Core.Native/Msi/Database.cs
@@ -18,13 +18,14 @@ namespace WixToolset.Core.Native.Msi
18 /// </summary> 18 /// </summary>
19 /// <param name="path">Path to the database to be opened.</param> 19 /// <param name="path">Path to the database to be opened.</param>
20 /// <param name="type">Persist mode to use when opening the database.</param> 20 /// <param name="type">Persist mode to use when opening the database.</param>
21 public Database(string path, OpenDatabase type) 21 private Database(string path, OpenDatabase type)
22 { 22 {
23 var error = MsiInterop.MsiOpenDatabase(path, (IntPtr)type, out var handle); 23 var error = MsiInterop.MsiOpenDatabase(path, (IntPtr)type, out var handle);
24 if (0 != error) 24 if (0 != error)
25 { 25 {
26 throw new MsiException(error); 26 throw new MsiException(error);
27 } 27 }
28
28 this.Handle = handle; 29 this.Handle = handle;
29 } 30 }
30 31
@@ -34,14 +35,89 @@ namespace WixToolset.Core.Native.Msi
34 public static int MsiMaxStreamNameLength => MsiInterop.MsiMaxStreamNameLength; 35 public static int MsiMaxStreamNameLength => MsiInterop.MsiMaxStreamNameLength;
35 36
36 /// <summary> 37 /// <summary>
38 /// Creates a new <see cref="Database"/> with the specified path.
39 /// </summary>
40 /// <param name="path">Path of database to be created.</param>
41 /// <param name="asPatch">Indicates whether the database should be opened as a patch file.</param>
42 public static Database Create(string path, bool asPatch = false)
43 {
44 var fileCreated = false;
45 var mode = OpenDatabase.CreateDirect;
46
47 if (asPatch)
48 {
49 mode |= OpenDatabase.OpenPatchFile;
50 }
51
52 try
53 {
54 fileCreated = PathUtil.CreateOrGetShortPath(path, out var shortPath);
55
56 return new Database(shortPath, mode);
57 }
58 catch // cleanup on error if we created the short path file.
59 {
60 if (fileCreated)
61 {
62 File.Delete(path);
63 }
64
65 throw;
66 }
67 }
68
69 /// <summary>
70 /// Opens an existing <see cref="Database"/> with the specified path.
71 /// </summary>
72 /// <param name="path">Path of database to open.</param>
73 /// <param name="transact">Indicates whether to open the database in transaction mode.</param>
74 /// <param name="asPatch">Indicates whether the database should be opened as a patch file.</param>
75 public static Database Open(string path, bool transact = false, bool asPatch = false)
76 {
77 var mode = transact ? OpenDatabase.Transact : OpenDatabase.Direct;
78
79 if (asPatch)
80 {
81 mode |= OpenDatabase.OpenPatchFile;
82 }
83
84 // Use the short path to avoid issues with long paths in the MSI API.
85 var shortPath = PathUtil.GetShortPath(path);
86
87 return new Database(shortPath, mode);
88 }
89
90 /// <summary>
91 /// Opens an existing <see cref="Database"/> with the specified path.
92 /// </summary>
93 /// <param name="path">Path of database to open.</param>
94 /// <param name="asPatch">Indicates whether the database should be opened as a patch file.</param>
95 public static Database OpenAsReadOnly(string path, bool asPatch = false)
96 {
97 var mode = OpenDatabase.ReadOnly;
98
99 if (asPatch)
100 {
101 mode |= OpenDatabase.OpenPatchFile;
102 }
103
104 // Use the short path to avoid issues with long paths in the MSI API.
105 var shortPath = PathUtil.GetShortPath(path);
106
107 return new Database(shortPath, mode);
108 }
109
110 /// <summary>
37 /// Apply a transform to the MSI. 111 /// Apply a transform to the MSI.
38 /// </summary> 112 /// </summary>
39 /// <param name="transformFile">Path to transform to apply.</param> 113 /// <param name="transformFile">Path to transform to apply.</param>
40 public void ApplyTransform(string transformFile) 114 public void ApplyTransform(string transformFile)
41 { 115 {
116 var shortTransformFile = PathUtil.GetShortPath(transformFile);
117
42 // get the curret validation bits 118 // get the curret validation bits
43 var conditions = TransformErrorConditions.None; 119 var conditions = TransformErrorConditions.None;
44 using (var summaryInfo = new SummaryInformation(transformFile)) 120 using (var summaryInfo = new SummaryInformation(shortTransformFile))
45 { 121 {
46 try 122 try
47 { 123 {
@@ -64,7 +140,9 @@ namespace WixToolset.Core.Native.Msi
64 /// <param name="errorConditions">Specifies the error conditions that are to be suppressed.</param> 140 /// <param name="errorConditions">Specifies the error conditions that are to be suppressed.</param>
65 public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions) 141 public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions)
66 { 142 {
67 var error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions); 143 var shortTransformFile = PathUtil.GetShortPath(transformFile);
144
145 var error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, shortTransformFile, errorConditions);
68 if (0 != error) 146 if (0 != error)
69 { 147 {
70 throw new MsiException(error); 148 throw new MsiException(error);
@@ -118,7 +196,9 @@ namespace WixToolset.Core.Native.Msi
118 /// shows which properties should be validated to verify that this transform can be applied to the database.</param> 196 /// shows which properties should be validated to verify that this transform can be applied to the database.</param>
119 public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations) 197 public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations)
120 { 198 {
121 var error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations); 199 var shortTransformFile = PathUtil.GetShortPath(transformFile);
200
201 var error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, shortTransformFile, errorConditions, validations);
122 if (0 != error) 202 if (0 != error)
123 { 203 {
124 throw new MsiException(error); 204 throw new MsiException(error);
@@ -136,7 +216,9 @@ namespace WixToolset.Core.Native.Msi
136 var folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath)); 216 var folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath));
137 var fileName = Path.GetFileName(idtPath); 217 var fileName = Path.GetFileName(idtPath);
138 218
139 var error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName); 219 var shortFolderPath = PathUtil.GetShortPath(folderPath);
220
221 var error = MsiInterop.MsiDatabaseImport(this.Handle, shortFolderPath, fileName);
140 if (1627 == error) // ERROR_FUNCTION_FAILED 222 if (1627 == error) // ERROR_FUNCTION_FAILED
141 { 223 {
142 throw new WixInvalidIdtException(idtPath); 224 throw new WixInvalidIdtException(idtPath);
@@ -160,7 +242,9 @@ namespace WixToolset.Core.Native.Msi
160 folderPath = Environment.CurrentDirectory; 242 folderPath = Environment.CurrentDirectory;
161 } 243 }
162 244
163 var error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName); 245 var shortFolderPath = PathUtil.GetShortPath(folderPath);
246
247 var error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, shortFolderPath, fileName);
164 if (0 != error) 248 if (0 != error)
165 { 249 {
166 throw new MsiException(error); 250 throw new MsiException(error);
@@ -176,13 +260,29 @@ namespace WixToolset.Core.Native.Msi
176 /// there are no differences between the two databases.</returns> 260 /// there are no differences between the two databases.</returns>
177 public bool GenerateTransform(Database referenceDatabase, string transformFile) 261 public bool GenerateTransform(Database referenceDatabase, string transformFile)
178 { 262 {
179 var error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0); 263 var fileCreated = false;
180 if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found 264
265 try
181 { 266 {
182 throw new MsiException(error); 267 fileCreated = PathUtil.CreateOrGetShortPath(transformFile, out var shortTransformFile);
268
269 var error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, shortTransformFile, 0, 0);
270 if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found
271 {
272 throw new MsiException(error);
273 }
274
275 return (0xE8 != error);
183 } 276 }
277 catch // Cleanup on error
278 {
279 if (fileCreated)
280 {
281 File.Delete(transformFile);
282 }
184 283
185 return (0xE8 != error); 284 throw;
285 }
186 } 286 }
187 287
188 /// <summary> 288 /// <summary>
diff --git a/src/wix/WixToolset.Core.Native/Msi/Installer.cs b/src/wix/WixToolset.Core.Native/Msi/Installer.cs
index b2c2c630..14745469 100644
--- a/src/wix/WixToolset.Core.Native/Msi/Installer.cs
+++ b/src/wix/WixToolset.Core.Native/Msi/Installer.cs
@@ -34,11 +34,13 @@ namespace WixToolset.Core.Native.Msi
34 var buffer = new StringBuilder(65535); 34 var buffer = new StringBuilder(65535);
35 var size = buffer.Capacity; 35 var size = buffer.Capacity;
36 36
37 var error = MsiInterop.MsiExtractPatchXMLData(path, 0, buffer, ref size); 37 var shortPath = PathUtil.GetShortPath(path);
38
39 var error = MsiInterop.MsiExtractPatchXMLData(shortPath, 0, buffer, ref size);
38 if (234 == error) 40 if (234 == error)
39 { 41 {
40 buffer.EnsureCapacity(++size); 42 buffer.EnsureCapacity(++size);
41 error = MsiInterop.MsiExtractPatchXMLData(path, 0, buffer, ref size); 43 error = MsiInterop.MsiExtractPatchXMLData(shortPath, 0, buffer, ref size);
42 } 44 }
43 45
44 if (error != 0) 46 if (error != 0)
@@ -57,8 +59,12 @@ namespace WixToolset.Core.Native.Msi
57 /// <param name="hash">Int array that receives the returned file hash information.</param> 59 /// <param name="hash">Int array that receives the returned file hash information.</param>
58 public static void GetFileHash(string filePath, int options, out int[] hash) 60 public static void GetFileHash(string filePath, int options, out int[] hash)
59 { 61 {
60 var hashInterop = new MSIFILEHASHINFO(); 62 var hashInterop = new MSIFILEHASHINFO
61 hashInterop.FileHashInfoSize = 20; 63 {
64 FileHashInfoSize = 20
65 };
66
67 filePath = PathUtil.GetPrefixedLongPath(filePath);
62 68
63 var error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop); 69 var error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop);
64 if (0 != error) 70 if (0 != error)
@@ -76,9 +82,9 @@ namespace WixToolset.Core.Native.Msi
76 } 82 }
77 83
78 /// <summary> 84 /// <summary>
79 /// Returns the version string and language string in the format that the installer 85 /// Returns the version string and language string in the format that the installer
80 /// expects to find them in the database. If you just want version information, set 86 /// expects to find them in the database. If you just want version information, set
81 /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set 87 /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set
82 /// lpVersionBuf and pcchVersionBuf to zero. 88 /// lpVersionBuf and pcchVersionBuf to zero.
83 /// </summary> 89 /// </summary>
84 /// <param name="filePath">Specifies the path to the file.</param> 90 /// <param name="filePath">Specifies the path to the file.</param>
@@ -91,6 +97,8 @@ namespace WixToolset.Core.Native.Msi
91 var versionBuffer = new StringBuilder(versionLength); 97 var versionBuffer = new StringBuilder(versionLength);
92 var languageBuffer = new StringBuilder(languageLength); 98 var languageBuffer = new StringBuilder(languageLength);
93 99
100 filePath = PathUtil.GetPrefixedLongPath(filePath);
101
94 var error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength); 102 var error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength);
95 if (234 == error) 103 if (234 == error)
96 { 104 {
diff --git a/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs b/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs
index 18a78f77..6d0226d2 100644
--- a/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs
+++ b/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs
@@ -1,4 +1,4 @@
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. 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 2
3namespace WixToolset.Core.Native.Msi 3namespace WixToolset.Core.Native.Msi
4{ 4{
diff --git a/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs b/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs
index 3b3dea0f..3eed1274 100644
--- a/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs
+++ b/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs
@@ -220,8 +220,10 @@ namespace WixToolset.Core.Native.Msi
220 throw new ArgumentNullException(nameof(databaseFile)); 220 throw new ArgumentNullException(nameof(databaseFile));
221 } 221 }
222 222
223 var shortDatabaseFile = PathUtil.GetShortPath(databaseFile);
224
223 var handle = IntPtr.Zero; 225 var handle = IntPtr.Zero;
224 var error = MsiInterop.MsiGetSummaryInformation(IntPtr.Zero, databaseFile, 0, ref handle); 226 var error = MsiInterop.MsiGetSummaryInformation(IntPtr.Zero, shortDatabaseFile, 0, ref handle);
225 if (0 != error) 227 if (0 != error)
226 { 228 {
227 throw new MsiException(error); 229 throw new MsiException(error);
diff --git a/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs b/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs
index 7978304a..434b0887 100644
--- a/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs
+++ b/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs
@@ -93,7 +93,7 @@ namespace WixToolset.Core.Native
93 93
94 try 94 try
95 { 95 {
96 using (var database = new Database(this.DatabasePath, OpenDatabase.Direct)) 96 using (var database = Database.Open(this.DatabasePath))
97 { 97 {
98 var propertyTableExists = database.TableExists("Property"); 98 var propertyTableExists = database.TableExists("Property");
99 string productCode = null; 99 string productCode = null;
@@ -130,7 +130,7 @@ namespace WixToolset.Core.Native
130 130
131 try 131 try
132 { 132 {
133 using (var cubeDatabase = new Database(findCubeFile.Path, OpenDatabase.ReadOnly)) 133 using (var cubeDatabase = Database.OpenAsReadOnly(findCubeFile.Path))
134 { 134 {
135 try 135 try
136 { 136 {
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
index b37c7b95..71dc4881 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
@@ -413,8 +413,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
413 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); 413 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
414 414
415 // create the transform file 415 // create the transform file
416 using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) 416 using (var targetDatabase = Database.OpenAsReadOnly(targetDatabaseFile))
417 using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) 417 using (var updatedDatabase = Database.OpenAsReadOnly(updatedDatabaseFile))
418 { 418 {
419 if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) 419 if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath))
420 { 420 {
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
index 94ed0afc..117923e5 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
@@ -90,7 +90,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
90 try 90 try
91 { 91 {
92 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. 92 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module.
93 using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) 93 using (var db = Database.OpenAsReadOnly(wixMergeRow.SourceFile))
94 { 94 {
95 if (db.TableExists("File") && db.TableExists("Component")) 95 if (db.TableExists("File") && db.TableExists("Component"))
96 { 96 {
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
index 361f797d..a248dbd5 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
@@ -81,20 +81,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
81 81
82 var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder); 82 var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder);
83 83
84 var type = OpenDatabase.CreateDirect;
85
86 if (OutputType.Patch == this.Data.Type)
87 {
88 type |= OpenDatabase.OpenPatchFile;
89 }
90
91 try 84 try
92 { 85 {
93 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); 86 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
94 87
95 Directory.CreateDirectory(idtFolder); 88 Directory.CreateDirectory(idtFolder);
96 89
97 using (var db = new Database(this.OutputPath, type)) 90 using (var db = Database.Create(this.OutputPath, asPatch: OutputType.Patch == this.Data.Type))
98 { 91 {
99 // If we're not using the default codepage, import a new one into our 92 // If we're not using the default codepage, import a new one into our
100 // database before we add any tables (or the tables would be added 93 // database before we add any tables (or the tables would be added
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
index 5f21f496..71a2e367 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
@@ -223,7 +223,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
223 return; 223 return;
224 } 224 }
225 225
226 using (var db = new Database(this.OutputPath, OpenDatabase.Direct)) 226 using (var db = Database.Open(this.OutputPath))
227 { 227 {
228 // Suppress individual actions. 228 // Suppress individual actions.
229 foreach (var suppressAction in this.Section.Symbols.OfType<WixSuppressActionSymbol>()) 229 foreach (var suppressAction in this.Section.Symbols.OfType<WixSuppressActionSymbol>())
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
index c5f198f8..ff7c6579 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
@@ -64,7 +64,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
64 64
65 var mediaRows = this.Section.Symbols.OfType<MediaSymbol>().ToDictionary(t => t.DiskId); 65 var mediaRows = this.Section.Symbols.OfType<MediaSymbol>().ToDictionary(t => t.DiskId);
66 66
67 using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) 67 using (var db = Database.OpenAsReadOnly(this.DatabasePath))
68 { 68 {
69 using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) 69 using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
70 { 70 {
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
index 52d2146c..75e536c2 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
@@ -76,7 +76,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
76 var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id); 76 var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
77 77
78 Parallel.ForEach(facades, 78 Parallel.ForEach(facades,
79 new ParallelOptions{ 79 new ParallelOptions
80 {
80 CancellationToken = this.CancellationToken, 81 CancellationToken = this.CancellationToken,
81 MaxDegreeOfParallelism = this.ThreadCount 82 MaxDegreeOfParallelism = this.ThreadCount
82 }, 83 },
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
index 455057f6..344cccb8 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
@@ -61,7 +61,7 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
61 return shouldCommit; 61 return shouldCommit;
62 } 62 }
63 63
64 using (var database = new Database(databasePath, OpenDatabase.Transact)) 64 using (var database = Database.Open(databasePath, transact: true))
65 { 65 {
66 // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content 66 // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content
67 var codepage = 1252; 67 var codepage = 1252;
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
index cfa53269..dce0d488 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
@@ -86,7 +86,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
86 { 86 {
87 if (this.Database == null) 87 if (this.Database == null)
88 { 88 {
89 database = new Database(this.DatabasePath, OpenDatabase.ReadOnly); 89 database = Database.OpenAsReadOnly(this.DatabasePath);
90 this.Database = database; 90 this.Database = database;
91 } 91 }
92 92
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs
index 8846739a..cc83af72 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs
@@ -263,7 +263,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
263 263
264 private Database ApplyTransformToSchemaDatabase(string schemaDatabasePath, TransformErrorConditions transformConditions) 264 private Database ApplyTransformToSchemaDatabase(string schemaDatabasePath, TransformErrorConditions transformConditions)
265 { 265 {
266 var msiDatabase = new Database(schemaDatabasePath, OpenDatabase.Transact); 266 var msiDatabase = Database.Open(schemaDatabasePath, transact: true);
267 267
268 try 268 try
269 { 269 {
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LongPathFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LongPathFixture.cs
new file mode 100644
index 00000000..2cada64d
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/LongPathFixture.cs
@@ -0,0 +1,89 @@
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.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixInternal.Core.TestPackage;
8 using WixInternal.TestSupport;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using Xunit;
12
13 public class LongPathFixture
14 {
15 [Fact]
16 public void TestLongPathSupport()
17 {
18 var testDataFolder = TestData.Get(@"TestData", "SingleFile");
19
20 using (var fs = new DisposableFileSystem())
21 {
22 var folder = fs.GetFolder();
23
24 while (folder.Length < 500)
25 {
26 folder = Path.Combine(folder, new string('z', 100));
27 }
28
29 CopyDirectory(testDataFolder, folder);
30
31 var baseFolder = fs.GetFolder();
32
33 while (baseFolder.Length < 500)
34 {
35 baseFolder = Path.Combine(baseFolder, new string('a', 100));
36 }
37
38 var intermediateFolder = Path.Combine(baseFolder, "obj");
39
40 var result = WixRunner.Execute(
41 [
42 "build",
43 Path.Combine(folder, "Package.wxs"),
44 Path.Combine(folder, "PackageComponents.wxs"),
45 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
46 "-bindpath", Path.Combine(folder, "data"),
47 "-intermediateFolder", intermediateFolder,
48 "-o", Path.Combine(baseFolder, "bin", "test.msi")
49 ]);
50
51 result.AssertSuccess();
52
53 Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.msi")));
54 Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.wixpdb")));
55 Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "PFiles", "Example Corporation MsiPackage", "test.txt")));
56
57 var intermediate = Intermediate.Load(Path.Combine(baseFolder, "bin", "test.wixpdb"));
58
59 var section = intermediate.Sections.Single();
60
61 var fileSymbol = section.Symbols.OfType<FileSymbol>().First();
62 WixAssert.StringEqual(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
63 WixAssert.StringEqual(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
64 }
65 }
66
67 private static void CopyDirectory(string sourceFolder, string targetFolder)
68 {
69 // Ensure the target directory exists
70 Directory.CreateDirectory(targetFolder);
71
72 // Copy all files
73 foreach (var file in Directory.GetFiles(sourceFolder))
74 {
75 var targetFile = Path.Combine(targetFolder, Path.GetFileName(file));
76
77 File.Copy(file, targetFile);
78 }
79
80 // Recursively copy subdirectories
81 foreach (var subFolder in Directory.GetDirectories(sourceFolder))
82 {
83 var targetSubFolder = Path.Combine(targetFolder, Path.GetFileName(subFolder));
84
85 CopyDirectory(subFolder, targetSubFolder);
86 }
87 }
88 }
89}
diff --git a/src/wix/wixnative/wixnative.manifest b/src/wix/wixnative/wixnative.manifest
new file mode 100644
index 00000000..ce288e89
--- /dev/null
+++ b/src/wix/wixnative/wixnative.manifest
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3 <assemblyIdentity name="WixToolset.Tools.WixNative" version="4.0.0.0" processorArchitecture="x86" type="win32"/>
4 <description>WiX Toolset Native</description>
5 <application xmlns="urn:schemas-microsoft-com:asm.v3"><windowsSettings>
6 <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
7 </windowsSettings></application>
8 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo>
9</assembly>
diff --git a/src/wix/wixnative/wixnative.vcxproj b/src/wix/wixnative/wixnative.vcxproj
index e4474bb1..c4e05a62 100644
--- a/src/wix/wixnative/wixnative.vcxproj
+++ b/src/wix/wixnative/wixnative.vcxproj
@@ -55,6 +55,9 @@
55 <ClCompile Include="extractcab.cpp" /> 55 <ClCompile Include="extractcab.cpp" />
56 <ClCompile Include="smartcab.cpp" /> 56 <ClCompile Include="smartcab.cpp" />
57 </ItemGroup> 57 </ItemGroup>
58 <ItemGroup>
59 <Manifest Include="wixnative.manifest" />
60 </ItemGroup>
58 61
59 <ItemGroup> 62 <ItemGroup>
60 <ClInclude Include="precomp.h" /> 63 <ClInclude Include="precomp.h" />