From cf15a49da99429ef3bdce564ecb7a7a94198ead4 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 2 Nov 2025 10:48:52 -0800 Subject: Improve error message when MSI is read-only Fixes 9113 --- src/api/wix/WixToolset.Data/ErrorMessages.cs | 6 --- src/wix/WixToolset.Core.Native/Msi/Database.cs | 1 + src/wix/WixToolset.Core.Native/Msi/MsiException.cs | 8 ++-- .../Bind/BindTransformCommand.cs | 5 +++ .../Bind/GenerateDatabaseCommand.cs | 7 +++- .../Unbind/UnbindDatabaseCommand.cs | 2 +- .../Unbind/UnbindTransformCommand.cs | 10 +++++ .../WindowsInstallerBackendErrors.cs | 7 ++++ .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 49 ++++++++++++++++++++++ 9 files changed, 82 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/api/wix/WixToolset.Data/ErrorMessages.cs b/src/api/wix/WixToolset.Data/ErrorMessages.cs index 1d02ffd0..4052917c 100644 --- a/src/api/wix/WixToolset.Data/ErrorMessages.cs +++ b/src/api/wix/WixToolset.Data/ErrorMessages.cs @@ -1561,11 +1561,6 @@ namespace WixToolset.Data return Message(sourceLineNumbers, Ids.NoUniqueActionSequenceNumber2, "The location of the sequenced action related to previous error."); } - public static Message OpenDatabaseFailed(string databaseFile) - { - return Message(null, Ids.OpenDatabaseFailed, "Failed to open database '{0}'. Ensure it is a valid database, and it is not open by another process.", databaseFile); - } - public static Message OrderingReferenceLoopDetected(SourceLineNumber sourceLineNumbers, string loopList) { return Message(sourceLineNumbers, Ids.OrderingReferenceLoopDetected, "A circular reference of ordering dependencies was detected. The infinite loop includes: {0}. Ordering dependency references must form a directed acyclic graph.", loopList); @@ -2479,7 +2474,6 @@ namespace WixToolset.Data InvalidKeyColumn = 220, CollidingModularizationTypes = 221, CubeFileNotFound = 222, - OpenDatabaseFailed = 223, OutputTypeMismatch = 224, RealTableMissingPrimaryKeyColumn = 225, IllegalColumnName = 226, diff --git a/src/wix/WixToolset.Core.Native/Msi/Database.cs b/src/wix/WixToolset.Core.Native/Msi/Database.cs index 18e5066d..ff966302 100644 --- a/src/wix/WixToolset.Core.Native/Msi/Database.cs +++ b/src/wix/WixToolset.Core.Native/Msi/Database.cs @@ -25,6 +25,7 @@ namespace WixToolset.Core.Native.Msi { throw new MsiException(error); } + this.Handle = handle; } diff --git a/src/wix/WixToolset.Core.Native/Msi/MsiException.cs b/src/wix/WixToolset.Core.Native/Msi/MsiException.cs index 218d2a7c..fba2df78 100644 --- a/src/wix/WixToolset.Core.Native/Msi/MsiException.cs +++ b/src/wix/WixToolset.Core.Native/Msi/MsiException.cs @@ -17,16 +17,16 @@ namespace WixToolset.Core.Native.Msi /// The error code from the MsiXxx() function call. public MsiException(int error) : base(error) { - IntPtr handle = MsiInterop.MsiGetLastErrorRecord(); + var handle = MsiInterop.MsiGetLastErrorRecord(); if (IntPtr.Zero != handle) { - using (Record record = new Record(handle)) + using (var record = new Record(handle)) { this.MsiError = record.GetInteger(1); - int errorInfoCount = record.GetFieldCount() - 1; + var errorInfoCount = record.GetFieldCount() - 1; this.ErrorInfo = new string[errorInfoCount]; - for (int i = 0; i < errorInfoCount; ++i) + for (var i = 0; i < errorInfoCount; ++i) { this.ErrorInfo[i] = record.GetString(i + 2); } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs index b37c7b95..e96df21f 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -409,6 +409,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false); this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true); + if (this.Messaging.EncounteredError) + { + return; + } + // make sure the directory exists Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs index 361f797d..3d831577 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -115,8 +115,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind } catch (IOException e) { - // TODO: this error message doesn't seem specific enough - throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e); + this.Messaging.Write(WindowsInstallerBackendErrors.OpenDatabaseFailed(this.OutputPath, e.Message)); + } + catch (MsiException e) + { + this.Messaging.Write(WindowsInstallerBackendErrors.OpenDatabaseFailed(this.OutputPath, e.Message)); } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs index cfa53269..ef02f5d1 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs @@ -104,7 +104,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind { if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED { - throw new WixException(ErrorMessages.OpenDatabaseFailed(this.DatabasePath)); + throw new WixException(WindowsInstallerBackendErrors.OpenDatabaseFailed(this.DatabasePath, e.Message)); } throw; diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs index 8846739a..aad3d34d 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs @@ -82,6 +82,11 @@ namespace WixToolset.Core.WindowsInstaller.Unbind // Bind the schema msi. this.GenerateDatabase(schemaData); + if (this.Messaging.EncounteredError) + { + return transform; + } + var transformViewTable = this.OpenTransformViewForAddedAndModifiedRows(schemaDatabasePath); var addedRows = this.CreatePlaceholdersForModifiedRowsAndIndexAddedRows(schemaData, transformViewTable); @@ -89,6 +94,11 @@ namespace WixToolset.Core.WindowsInstaller.Unbind // Re-bind the schema output with the placeholder rows over top the original schema database. this.GenerateDatabase(schemaData); + if (this.Messaging.EncounteredError) + { + return transform; + } + this.PopulateTransformFromView(schemaDatabasePath, transform, transformViewTable, addedRows); return transform; diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs index dbdbae0b..756bb5e4 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs @@ -7,6 +7,11 @@ namespace WixToolset.Core.WindowsInstaller internal static class WindowsInstallerBackendErrors { + public static Message OpenDatabaseFailed(string databaseFile, string error) + { + return Message(null, Ids.OpenDatabaseFailed, "Failed to open database '{0}'. Ensure it is a valid database, is writable, and it is not open by another process. {1}", databaseFile, error); + } + public static Message CannotLoadWixoutAsTransform(SourceLineNumber sourceLineNumbers, Exception exception) { var additionalDetail = exception == null ? String.Empty : ", detail: " + exception.Message; @@ -51,6 +56,8 @@ namespace WixToolset.Core.WindowsInstaller public enum Ids { + OpenDatabaseFailed = 223, + CannotLoadWixoutAsTransform = 7500, InvalidModuleVersion = 7501, ExceededMaximumAllowedComponentsInMsi = 7502, diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 7f5dfcd0..7928b809 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -55,6 +55,55 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CannotBuildReadOnlyMsi() + { + var folder = TestData.Get(@"TestData", "AllUsers"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var msiPath = Path.Combine(binFolder, "test.msi"); + + var msiFile = new FileInfo(msiPath); + + try + { + msiFile.Directory.Create(); + + using (var stream = msiFile.CreateText()) + { + stream.WriteLine("This is a read-only file."); + } + + msiFile.IsReadOnly = true; + + var result = WixRunner.Execute( + [ + "build", + Path.Combine(folder, "PerMachine.wxs"), + "-bindpath", folder, + "-intermediateFolder", intermediateFolder, + "-o", msiPath + ], out var messages); + Assert.Equal(223, result); + + // Note the use of substring in the message below. The error message detail may vary depending on whether extended + // error information is available on the system. + WixAssert.CompareLineByLine( + [ + @"223 Error - Failed to open database '\bin\test.msi'. Ensure it is a valid database, is writable, and it is not open by another process. ", + ], [.. messages.Select(m => $"{m.Id} {m.Level} - {m.ToString().Replace(folder, "").Replace(baseFolder, "").Substring(0, 136)}")]); + } + finally + { + msiFile.IsReadOnly = false; + } + } + } + [Fact] public void CannotBuildMissingFile() { -- cgit v1.2.3-55-g6feb