From ea519e45b11587c43579823a19f792a609843f83 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 2 Nov 2025 17:06:45 -0800 Subject: [PATCH] Better handling of long paths 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 --- .../Bind/ProcessBundleSoftwareTagsCommand.cs | 2 +- .../Bundles/ProcessMsiPackageCommand.cs | 2 +- .../Bundles/ProcessMspPackageCommand.cs | 2 +- .../WixToolset.Core.Native/LongPathUtil.cs | 78 ++++++++++++ .../WixToolset.Core.Native/Msi/Database.cs | 120 ++++++++++++++++-- .../WixToolset.Core.Native/Msi/Installer.cs | 22 +++- .../Msi/OpenDatabase.cs | 2 +- .../Msi/SummaryInformation.cs | 4 +- .../WindowsInstallerValidator.cs | 4 +- .../Bind/BindTransformCommand.cs | 4 +- .../Bind/ExtractMergeModuleFilesCommand.cs | 2 +- .../Bind/GenerateDatabaseCommand.cs | 9 +- .../Bind/MergeModulesCommand.cs | 2 +- .../Bind/ProcessUncompressedFilesCommand.cs | 2 +- .../Bind/UpdateFileFacadesCommand.cs | 3 +- .../Inscribe/InscribeMsiPackageCommand.cs | 2 +- .../Unbind/UnbindDatabaseCommand.cs | 2 +- .../Unbind/UnbindTransformCommand.cs | 2 +- .../LongPathFixture.cs | 89 +++++++++++++ src/wix/wixnative/wixnative.manifest | 9 ++ src/wix/wixnative/wixnative.vcxproj | 3 + 21 files changed, 324 insertions(+), 41 deletions(-) create mode 100644 src/wix/WixToolset.Core.Native/LongPathUtil.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/LongPathFixture.cs create mode 100644 src/wix/wixnative/wixnative.manifest diff --git a/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs index 5231e0bed..70b98222f 100644 --- a/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs @@ -69,7 +69,7 @@ private static IEnumerable CollectPackageTags(IntermediateSection s { var payload = payloadSymbolsById[msiPackage.PayloadRef]; - using (var db = new Database(payload.SourceFile.Path, OpenDatabase.ReadOnly)) + using (var db = Database.OpenAsReadOnly(payload.SourceFile.Path)) { if (db.TableExists("SoftwareIdentificationTag")) { diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs index d6cf1cfd7..0b78c5455 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -164,7 +164,7 @@ public WixBundleHarvestedMsiPackageSymbol HarvestPackage() this.CheckIfWindowsInstallerFileTooLarge(this.PackagePayload.SourceLineNumbers, sourcePath, "MSI"); - using (var db = new Database(sourcePath, OpenDatabase.ReadOnly)) + using (var db = Database.OpenAsReadOnly(sourcePath)) { // Read data out of the msi database... 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 b889c2ce1..8d1e9c392 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs @@ -98,7 +98,7 @@ private WixBundleHarvestedMspPackageSymbol HarvestPackage() try { - using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) + using (var db = Database.OpenAsReadOnly(sourcePath, asPatch: true)) { // Read data out of the msp database... 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 000000000..c24f1736b --- /dev/null +++ b/src/wix/WixToolset.Core.Native/LongPathUtil.cs @@ -0,0 +1,78 @@ +// 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. + +namespace WixToolset.Core.Native +{ + using System.IO; + using System.Runtime.InteropServices; + using System.Text; + + internal static class PathUtil + { + private const int MaxPath = 260; + private const string LongPathPrefix = @"\\?\"; + + public static bool CreateOrGetShortPath(string path, out string shortPath) + { + var fileCreated = false; + + // The file must exist so we can get its short path. + if (!File.Exists(path)) + { + using (File.Create(path)) + { + } + + fileCreated = true; + } + + // Use the short path to avoid issues with long paths in the MSI API. + shortPath = GetShortPath(path); + + return fileCreated; + } + + public static string GetPrefixedLongPath(string path) + { + if (path.Length > MaxPath && !path.StartsWith(LongPathPrefix)) + { + path = LongPathPrefix + path; + } + + return path; + } + + public static string GetShortPath(string longPath) + { + var path = GetPrefixedLongPath(longPath); + + var buffer = new StringBuilder(MaxPath); // start with MAX_PATH. + + var result = GetShortPathName(path, buffer, (uint)buffer.Capacity); + + // If result > buffer.Capacity, reallocate and call again (even though we're usually using short names to avoid long path) + // so the short path result is still going to end up too long for APIs requiring a short path. + if (result > buffer.Capacity) + { + buffer = new StringBuilder((int)result); + + result = GetShortPathName(path, buffer, (uint)buffer.Capacity); + } + + // If we succeeded, return the short path without the prefix. + if (result > 0) + { + path = buffer.ToString(); + + if (path.StartsWith(LongPathPrefix)) + { + path = path.Substring(LongPathPrefix.Length); + } + } + + return path; + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern uint GetShortPathName(string lpszLongPath, StringBuilder lpszShortPath, uint cchBuffer); + } +} diff --git a/src/wix/WixToolset.Core.Native/Msi/Database.cs b/src/wix/WixToolset.Core.Native/Msi/Database.cs index 18e5066d8..33eb8de65 100644 --- a/src/wix/WixToolset.Core.Native/Msi/Database.cs +++ b/src/wix/WixToolset.Core.Native/Msi/Database.cs @@ -18,13 +18,14 @@ public sealed class Database : MsiHandle /// /// Path to the database to be opened. /// Persist mode to use when opening the database. - public Database(string path, OpenDatabase type) + private Database(string path, OpenDatabase type) { var error = MsiInterop.MsiOpenDatabase(path, (IntPtr)type, out var handle); if (0 != error) { throw new MsiException(error); } + this.Handle = handle; } @@ -33,15 +34,90 @@ public Database(string path, OpenDatabase type) /// public static int MsiMaxStreamNameLength => MsiInterop.MsiMaxStreamNameLength; + /// + /// Creates a new with the specified path. + /// + /// Path of database to be created. + /// Indicates whether the database should be opened as a patch file. + public static Database Create(string path, bool asPatch = false) + { + var fileCreated = false; + var mode = OpenDatabase.CreateDirect; + + if (asPatch) + { + mode |= OpenDatabase.OpenPatchFile; + } + + try + { + fileCreated = PathUtil.CreateOrGetShortPath(path, out var shortPath); + + return new Database(shortPath, mode); + } + catch // cleanup on error if we created the short path file. + { + if (fileCreated) + { + File.Delete(path); + } + + throw; + } + } + + /// + /// Opens an existing with the specified path. + /// + /// Path of database to open. + /// Indicates whether to open the database in transaction mode. + /// Indicates whether the database should be opened as a patch file. + public static Database Open(string path, bool transact = false, bool asPatch = false) + { + var mode = transact ? OpenDatabase.Transact : OpenDatabase.Direct; + + if (asPatch) + { + mode |= OpenDatabase.OpenPatchFile; + } + + // Use the short path to avoid issues with long paths in the MSI API. + var shortPath = PathUtil.GetShortPath(path); + + return new Database(shortPath, mode); + } + + /// + /// Opens an existing with the specified path. + /// + /// Path of database to open. + /// Indicates whether the database should be opened as a patch file. + public static Database OpenAsReadOnly(string path, bool asPatch = false) + { + var mode = OpenDatabase.ReadOnly; + + if (asPatch) + { + mode |= OpenDatabase.OpenPatchFile; + } + + // Use the short path to avoid issues with long paths in the MSI API. + var shortPath = PathUtil.GetShortPath(path); + + return new Database(shortPath, mode); + } + /// /// Apply a transform to the MSI. /// /// Path to transform to apply. public void ApplyTransform(string transformFile) { + var shortTransformFile = PathUtil.GetShortPath(transformFile); + // get the curret validation bits var conditions = TransformErrorConditions.None; - using (var summaryInfo = new SummaryInformation(transformFile)) + using (var summaryInfo = new SummaryInformation(shortTransformFile)) { try { @@ -64,7 +140,9 @@ public void ApplyTransform(string transformFile) /// Specifies the error conditions that are to be suppressed. public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions) { - var error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions); + var shortTransformFile = PathUtil.GetShortPath(transformFile); + + var error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, shortTransformFile, errorConditions); if (0 != error) { throw new MsiException(error); @@ -118,7 +196,9 @@ public void Commit() /// shows which properties should be validated to verify that this transform can be applied to the database. public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations) { - var error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations); + var shortTransformFile = PathUtil.GetShortPath(transformFile); + + var error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, shortTransformFile, errorConditions, validations); if (0 != error) { throw new MsiException(error); @@ -136,7 +216,9 @@ public void Import(string idtPath) var folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath)); var fileName = Path.GetFileName(idtPath); - var error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName); + var shortFolderPath = PathUtil.GetShortPath(folderPath); + + var error = MsiInterop.MsiDatabaseImport(this.Handle, shortFolderPath, fileName); if (1627 == error) // ERROR_FUNCTION_FAILED { throw new WixInvalidIdtException(idtPath); @@ -160,7 +242,9 @@ public void Export(string tableName, string folderPath, string fileName) folderPath = Environment.CurrentDirectory; } - var error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName); + var shortFolderPath = PathUtil.GetShortPath(folderPath); + + var error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, shortFolderPath, fileName); if (0 != error) { throw new MsiException(error); @@ -176,13 +260,29 @@ public void Export(string tableName, string folderPath, string fileName) /// there are no differences between the two databases. public bool GenerateTransform(Database referenceDatabase, string transformFile) { - var error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0); - if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found + var fileCreated = false; + + try { - throw new MsiException(error); + fileCreated = PathUtil.CreateOrGetShortPath(transformFile, out var shortTransformFile); + + var error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, shortTransformFile, 0, 0); + if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found + { + throw new MsiException(error); + } + + return (0xE8 != error); } + catch // Cleanup on error + { + if (fileCreated) + { + File.Delete(transformFile); + } - return (0xE8 != error); + throw; + } } /// diff --git a/src/wix/WixToolset.Core.Native/Msi/Installer.cs b/src/wix/WixToolset.Core.Native/Msi/Installer.cs index b2c2c6307..147454695 100644 --- a/src/wix/WixToolset.Core.Native/Msi/Installer.cs +++ b/src/wix/WixToolset.Core.Native/Msi/Installer.cs @@ -34,11 +34,13 @@ public static string ExtractPatchXml(string path) var buffer = new StringBuilder(65535); var size = buffer.Capacity; - var error = MsiInterop.MsiExtractPatchXMLData(path, 0, buffer, ref size); + var shortPath = PathUtil.GetShortPath(path); + + var error = MsiInterop.MsiExtractPatchXMLData(shortPath, 0, buffer, ref size); if (234 == error) { buffer.EnsureCapacity(++size); - error = MsiInterop.MsiExtractPatchXMLData(path, 0, buffer, ref size); + error = MsiInterop.MsiExtractPatchXMLData(shortPath, 0, buffer, ref size); } if (error != 0) @@ -57,8 +59,12 @@ public static string ExtractPatchXml(string path) /// Int array that receives the returned file hash information. public static void GetFileHash(string filePath, int options, out int[] hash) { - var hashInterop = new MSIFILEHASHINFO(); - hashInterop.FileHashInfoSize = 20; + var hashInterop = new MSIFILEHASHINFO + { + FileHashInfoSize = 20 + }; + + filePath = PathUtil.GetPrefixedLongPath(filePath); var error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop); if (0 != error) @@ -76,9 +82,9 @@ public static void GetFileHash(string filePath, int options, out int[] hash) } /// - /// Returns the version string and language string in the format that the installer - /// expects to find them in the database. If you just want version information, set - /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set + /// Returns the version string and language string in the format that the installer + /// expects to find them in the database. If you just want version information, set + /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set /// lpVersionBuf and pcchVersionBuf to zero. /// /// Specifies the path to the file. @@ -91,6 +97,8 @@ public static void GetFileVersion(string filePath, out string version, out strin var versionBuffer = new StringBuilder(versionLength); var languageBuffer = new StringBuilder(languageLength); + filePath = PathUtil.GetPrefixedLongPath(filePath); + var error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength); if (234 == error) { diff --git a/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs b/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs index 18a78f778..6d0226d29 100644 --- a/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs +++ b/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs @@ -1,4 +1,4 @@ -// 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. +// 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. namespace WixToolset.Core.Native.Msi { diff --git a/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs b/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs index 3b3dea0f2..3eed1274e 100644 --- a/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs +++ b/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs @@ -220,8 +220,10 @@ public SummaryInformation(string databaseFile) throw new ArgumentNullException(nameof(databaseFile)); } + var shortDatabaseFile = PathUtil.GetShortPath(databaseFile); + var handle = IntPtr.Zero; - var error = MsiInterop.MsiGetSummaryInformation(IntPtr.Zero, databaseFile, 0, ref handle); + var error = MsiInterop.MsiGetSummaryInformation(IntPtr.Zero, shortDatabaseFile, 0, ref handle); if (0 != error) { throw new MsiException(error); diff --git a/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs b/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs index 7978304ae..434b08872 100644 --- a/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs +++ b/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs @@ -93,7 +93,7 @@ private void RunValidations() try { - using (var database = new Database(this.DatabasePath, OpenDatabase.Direct)) + using (var database = Database.Open(this.DatabasePath)) { var propertyTableExists = database.TableExists("Property"); string productCode = null; @@ -130,7 +130,7 @@ private void RunValidations() try { - using (var cubeDatabase = new Database(findCubeFile.Path, OpenDatabase.ReadOnly)) + using (var cubeDatabase = Database.OpenAsReadOnly(findCubeFile.Path)) { try { diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs index b37c7b952..71dc48816 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -413,8 +413,8 @@ public void Execute() Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); // create the transform file - using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) - using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) + using (var targetDatabase = Database.OpenAsReadOnly(targetDatabaseFile)) + using (var updatedDatabase = Database.OpenAsReadOnly(updatedDatabaseFile)) { if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) { diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs index 94ed0afc6..117923e55 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs @@ -90,7 +90,7 @@ private IReadOnlyCollection CreateFacadesForMergeModuleFiles(WixMe try { // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. - using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) + using (var db = Database.OpenAsReadOnly(wixMergeRow.SourceFile)) { if (db.TableExists("File") && db.TableExists("Component")) { diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs index 361f797dd..a248dbd59 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -81,20 +81,13 @@ public void Execute() var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder); - var type = OpenDatabase.CreateDirect; - - if (OutputType.Patch == this.Data.Type) - { - type |= OpenDatabase.OpenPatchFile; - } - try { Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); Directory.CreateDirectory(idtFolder); - using (var db = new Database(this.OutputPath, type)) + using (var db = Database.Create(this.OutputPath, asPatch: OutputType.Patch == this.Data.Type)) { // If we're not using the default codepage, import a new one into our // 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 5f21f496e..71a2e367d 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -223,7 +223,7 @@ public void Execute() return; } - using (var db = new Database(this.OutputPath, OpenDatabase.Direct)) + using (var db = Database.Open(this.OutputPath)) { // Suppress individual actions. foreach (var suppressAction in this.Section.Symbols.OfType()) diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs index c5f198f89..ff7c65793 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs @@ -64,7 +64,7 @@ public void Execute() var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); - using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) + using (var db = Database.OpenAsReadOnly(this.DatabasePath)) { using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index 52d2146c0..75e536c2e 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -76,7 +76,8 @@ private void UpdateFileFacadesInParallel(IEnumerable facades) var assemblySymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); Parallel.ForEach(facades, - new ParallelOptions{ + new ParallelOptions + { CancellationToken = this.CancellationToken, MaxDegreeOfParallelism = this.ThreadCount }, diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs index 455057f67..344cccb8e 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs @@ -61,7 +61,7 @@ public bool Execute() return shouldCommit; } - using (var database = new Database(databasePath, OpenDatabase.Transact)) + using (var database = Database.Open(databasePath, transact: true)) { // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content var codepage = 1252; diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs index cfa532698..dce0d4888 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs @@ -86,7 +86,7 @@ public WindowsInstallerData Execute() { if (this.Database == null) { - database = new Database(this.DatabasePath, OpenDatabase.ReadOnly); + database = Database.OpenAsReadOnly(this.DatabasePath); this.Database = database; } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs index 8846739a4..cc83af725 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs @@ -263,7 +263,7 @@ private void PopulateTransformFromView(string schemaDatabasePath, WindowsInstall private Database ApplyTransformToSchemaDatabase(string schemaDatabasePath, TransformErrorConditions transformConditions) { - var msiDatabase = new Database(schemaDatabasePath, OpenDatabase.Transact); + var msiDatabase = Database.Open(schemaDatabasePath, transact: true); try { diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LongPathFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LongPathFixture.cs new file mode 100644 index 000000000..2cada64d1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/LongPathFixture.cs @@ -0,0 +1,89 @@ +// 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. + +namespace WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixInternal.Core.TestPackage; + using WixInternal.TestSupport; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class LongPathFixture + { + [Fact] + public void TestLongPathSupport() + { + var testDataFolder = TestData.Get(@"TestData", "SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var folder = fs.GetFolder(); + + while (folder.Length < 500) + { + folder = Path.Combine(folder, new string('z', 100)); + } + + CopyDirectory(testDataFolder, folder); + + var baseFolder = fs.GetFolder(); + + while (baseFolder.Length < 500) + { + baseFolder = Path.Combine(baseFolder, new string('a', 100)); + } + + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + [ + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, "bin", "test.msi") + ]); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "PFiles", "Example Corporation MsiPackage", "test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, "bin", "test.wixpdb")); + + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().First(); + WixAssert.StringEqual(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + WixAssert.StringEqual(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + private static void CopyDirectory(string sourceFolder, string targetFolder) + { + // Ensure the target directory exists + Directory.CreateDirectory(targetFolder); + + // Copy all files + foreach (var file in Directory.GetFiles(sourceFolder)) + { + var targetFile = Path.Combine(targetFolder, Path.GetFileName(file)); + + File.Copy(file, targetFile); + } + + // Recursively copy subdirectories + foreach (var subFolder in Directory.GetDirectories(sourceFolder)) + { + var targetSubFolder = Path.Combine(targetFolder, Path.GetFileName(subFolder)); + + CopyDirectory(subFolder, targetSubFolder); + } + } + } +} diff --git a/src/wix/wixnative/wixnative.manifest b/src/wix/wixnative/wixnative.manifest new file mode 100644 index 000000000..ce288e895 --- /dev/null +++ b/src/wix/wixnative/wixnative.manifest @@ -0,0 +1,9 @@ + + + + WiX Toolset Native + + true + + + diff --git a/src/wix/wixnative/wixnative.vcxproj b/src/wix/wixnative/wixnative.vcxproj index e4474bb1a..c4e05a623 100644 --- a/src/wix/wixnative/wixnative.vcxproj +++ b/src/wix/wixnative/wixnative.vcxproj @@ -55,6 +55,9 @@ + + +