/// <summary> /// Validate a database. /// </summary> /// <param name="databaseFile">The database to validate.</param> /// <returns>true if validation succeeded; false otherwise.</returns> public void Validate(string databaseFile) { Dictionary <string, string> indexedICEs = new Dictionary <string, string>(); Dictionary <string, string> indexedSuppressedICEs = new Dictionary <string, string>(); int previousUILevel = (int)InstallUILevels.Basic; IntPtr previousHwnd = IntPtr.Zero; InstallUIHandler previousUIHandler = null; if (null == databaseFile) { throw new ArgumentNullException("databaseFile"); } // initialize the validator extension this.extension.DatabaseFile = databaseFile; this.extension.Output = this.output; this.extension.InitializeValidator(); // if we don't have the temporary files object yet, get one if (null == this.tempFiles) { this.tempFiles = new TempFileCollection(); } Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there // index the ICEs if (null != this.ices) { foreach (string ice in this.ices) { indexedICEs[ice] = null; } } // index the suppressed ICEs if (null != this.suppressedICEs) { foreach (string suppressedICE in this.suppressedICEs) { indexedSuppressedICEs[suppressedICE] = null; } } // copy the database to a temporary location so it can be manipulated string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(databaseFile)); File.Copy(databaseFile, tempDatabaseFile); // remove the read-only property from the temporary database FileAttributes attributes = File.GetAttributes(tempDatabaseFile); File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly); Mutex mutex = new Mutex(false, "WixValidator"); try { if (!mutex.WaitOne(0, false)) { this.OnMessage(WixVerboses.ValidationSerialized()); mutex.WaitOne(); } using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct)) { bool propertyTableExists = database.TableExists("Property"); string productCode = null; // remove the product code from the database before opening a session to prevent opening an installed product if (propertyTableExists) { using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'")) { using (Record record = view.Fetch()) { if (null != record) { productCode = record.GetString(1); using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) { } } } } } // merge in the cube databases foreach (string cubeFile in this.cubeFiles) { try { using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly)) { try { database.Merge(cubeDatabase, "MergeConflicts"); } catch { // ignore merge errors since they are expected in the _Validation table } } } catch (Win32Exception e) { if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED { throw new WixException(WixErrors.CubeFileNotFound(cubeFile)); } throw; } } // commit the database before proceeding to ensure the streams don't get confused database.Commit(); // the property table may have been added to the database // from a cub database without the proper validation rows if (!propertyTableExists) { using (View view = database.OpenExecuteView("DROP table `Property`")) { } } // get all the action names for ICEs which have not been suppressed List <string> actions = new List <string>(); using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`")) { while (true) { using (Record record = view.Fetch()) { if (null == record) { break; } string action = record.GetString(1); if (!indexedSuppressedICEs.ContainsKey(action)) { actions.Add(action); } } } } if (0 != indexedICEs.Count) { // Walk backwards and remove those that arent in the list for (int i = actions.Count - 1; 0 <= i; i--) { if (!indexedICEs.ContainsKey(actions[i])) { actions.RemoveAt(i); } } } // disable the internal UI handler and set an external UI handler previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd); previousUIHandler = Installer.SetExternalUI(this.validationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero); // create a session for running the ICEs this.validationSessionComplete = false; using (Session session = new Session(database)) { // add the product code back into the database if (null != productCode) { // some CUBs erroneously have a ProductCode property, so delete it if we just picked one up using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) { } using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode))) { } } foreach (string action in actions) { this.actionName = action; try { session.DoAction(action); } catch (Win32Exception e) { if (!Messaging.Instance.EncounteredError) { throw e; } // TODO: Review why this was clearing the error state when an exception had happened but an error was already encountered. That's weird. //else //{ // this.encounteredError = false; //} } this.actionName = null; } // Mark the validation session complete so we ignore any messages that MSI may fire // during session clean-up. this.validationSessionComplete = true; } } } catch (Win32Exception e) { // avoid displaying errors twice since one may have already occurred in the UI handler if (!Messaging.Instance.EncounteredError) { if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED { // databaseFile is not passed since during light // this would be the temporary copy and there would be // no final output since the error occured; during smoke // they should know the path passed into smoke this.OnMessage(WixErrors.ValidationFailedToOpenDatabase()); } else if (0x64D == e.NativeErrorCode) { this.OnMessage(WixErrors.ValidationFailedDueToLowMsiEngine()); } else if (0x654 == e.NativeErrorCode) { this.OnMessage(WixErrors.ValidationFailedDueToInvalidPackage()); } else if (0x658 == e.NativeErrorCode) { this.OnMessage(WixErrors.ValidationFailedDueToMultilanguageMergeModule()); } else if (0x659 == e.NativeErrorCode) { this.OnMessage(WixWarnings.ValidationFailedDueToSystemPolicy()); } else { string msgTemp = e.Message; if (null != this.actionName) { msgTemp = String.Concat("Action - '", this.actionName, "' ", e.Message); } this.OnMessage(WixErrors.Win32Exception(e.NativeErrorCode, msgTemp)); } } } finally { Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero); Installer.SetInternalUI(previousUILevel, ref previousHwnd); this.validationSessionComplete = false; // no validation session at this point, so reset the completion flag. mutex.ReleaseMutex(); this.cubeFiles.Clear(); this.extension.FinalizeValidator(); } }
/// <summary> /// Validate a database. /// </summary> /// <param name="databaseFile">The database to validate.</param> /// <returns>true if validation succeeded; false otherwise.</returns> public bool Validate(string databaseFile) { InstallUIHandler currentUIHandler = null; Hashtable indexedSuppressedICEs = new Hashtable(); int previousUILevel = (int)InstallUILevels.Basic; IntPtr previousHwnd = IntPtr.Zero; InstallUIHandler previousUIHandler = null; if (null == databaseFile) { throw new ArgumentNullException("databaseFile"); } // initialize the validator extension this.extension.DatabaseFile = databaseFile; this.extension.Output = this.output; this.extension.InitializeValidator(); // if we don't have the temporary files object yet, get one if (null == this.tempFiles) { this.tempFiles = new TempFileCollection(); } Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there // index the suppressed ICEs if (null != this.suppressedICEs) { foreach (string suppressedICE in this.suppressedICEs) { indexedSuppressedICEs[suppressedICE] = null; } } // copy the database to a temporary location so it can be manipulated string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(databaseFile)); File.Copy(databaseFile, tempDatabaseFile); // remove the read-only property from the temporary database FileAttributes attributes = File.GetAttributes(tempDatabaseFile); File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly); try { using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct)) { bool propertyTableExists = database.TableExists("Property"); string productCode = null; // remove the product code from the database before opening a session to prevent opening an installed product if (propertyTableExists) { using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'")) { Record record = null; try { if (null != (record = view.Fetch())) { productCode = record.GetString(1); using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) { } } } finally { if (null != record) { record.Close(); } } } } // merge in the cube databases foreach (string cubeFile in this.cubeFiles) { try { using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly)) { try { database.Merge(cubeDatabase, "MergeConflicts"); } catch { // ignore merge errors since they are expected in the _Validation table } } } catch (Win32Exception e) { if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED { throw new WixException(WixErrors.CubeFileNotFound(cubeFile)); } throw; } } // commit the database before proceeding to ensure the streams don't get confused database.Commit(); // the property table may have been added to the database // from a cub database without the proper validation rows if (!propertyTableExists) { using (View view = database.OpenExecuteView("DROP table `Property`")) { } } // get all the action names for ICEs which have not been suppressed ArrayList actions = new ArrayList(); using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`")) { Record record; while (null != (record = view.Fetch())) { string action = record.GetString(1); if (!indexedSuppressedICEs.Contains(action)) { actions.Add(action); } record.Close(); } } // disable the internal UI handler and set an external UI handler previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd); currentUIHandler = new InstallUIHandler(this.ValidationUIHandler); previousUIHandler = Installer.SetExternalUI(currentUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero); // create a session for running the ICEs using (Session session = new Session(database)) { // add the product code back into the database if (null != productCode) { using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode))) { } } foreach (string action in actions) { session.DoAction(action); } } } } catch (Win32Exception e) { // avoid displaying errors twice since one may have already occurred in the UI handler if (!this.encounteredError) { this.OnMessage(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); } } finally { Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero); Installer.SetInternalUI(previousUILevel, ref previousHwnd); // very important - this prevents the external UI delegate from being garbage collected too early GC.KeepAlive(currentUIHandler); this.cubeFiles.Clear(); this.extension.FinalizeValidator(); } return(!this.encounteredError); }