/// <summary> /// Merges in any modules to the output database. /// </summary> /// <param name="tempDatabaseFile">The temporary database file.</param> /// <param name="output">Output that specifies database and modules to merge.</param> /// <param name="fileRows">The indexed file rows.</param> /// <param name="suppressedTableNames">The names of tables that are suppressed.</param> /// <remarks>Expects that output's database has already been generated.</remarks> private void MergeModules(string tempDatabaseFile, Output output, FileRowCollection fileRows, StringCollection suppressedTableNames) { Debug.Assert(OutputType.Product == output.Type); Table wixMergeTable = output.Tables["WixMerge"]; Table wixFeatureModulesTable = output.Tables["WixFeatureModules"]; // check for merge rows to see if there is any work to do if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count) { return; } IMsmMerge2 merge = null; bool commit = true; bool logOpen = false; bool databaseOpen = false; string logPath = null; try { merge = NativeMethods.GetMsmMerge(); logPath = Path.Combine(this.TempFilesLocation, "merge.log"); merge.OpenLog(logPath); logOpen = true; merge.OpenDatabase(tempDatabaseFile); databaseOpen = true; // process all the merge rows foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows) { bool moduleOpen = false; try { short mergeLanguage; try { mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); } catch (System.FormatException) { this.core.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); continue; } this.core.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); moduleOpen = true; // If there is merge configuration data, create a callback object to contain it all. ConfigurationCallback callback = null; if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) { callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); } // merge the module into the database that's being built this.core.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile)); merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback); // connect any non-primary features if (null != wixFeatureModulesTable) { foreach (Row row in wixFeatureModulesTable.Rows) { if (wixMergeRow.Id == (string)row[1]) { this.core.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0])); merge.Connect((string)row[0]); } } } } catch (COMException) { commit = false; } finally { IMsmErrors mergeErrors = merge.Errors; // display all the errors encountered during the merge operations for this module for (int i = 1; i <= mergeErrors.Count; i++) { IMsmError mergeError = mergeErrors[i]; StringBuilder databaseKeys = new StringBuilder(); StringBuilder moduleKeys = new StringBuilder(); // build a string of the database keys for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++) { if (1 != j) { databaseKeys.Append(';'); } databaseKeys.Append(mergeError.DatabaseKeys[j]); } // build a string of the module keys for (int j = 1; j <= mergeError.ModuleKeys.Count; j++) { if (1 != j) { moduleKeys.Append(';'); } moduleKeys.Append(mergeError.ModuleKeys[j]); } // display the merge error based on the msm error type switch (mergeError.Type) { case MsmErrorType.msmErrorExclusion: this.core.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); break; case MsmErrorType.msmErrorFeatureRequired: this.core.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); break; case MsmErrorType.msmErrorLanguageFailed: this.core.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorLanguageUnsupported: this.core.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorResequenceMerge: this.core.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorTableMerge: if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table { this.core.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); } break; case MsmErrorType.msmErrorPlatformMismatch: this.core.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); break; default: this.core.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); break; } } if (0 >= mergeErrors.Count && !commit) { this.core.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); } if (moduleOpen) { merge.CloseModule(); } } } } finally { if (databaseOpen) { merge.CloseDatabase(commit); } if (logOpen) { merge.CloseLog(); } } // stop processing if an error previously occurred if (this.core.EncounteredError) { return; } using (Database db = new Database(tempDatabaseFile, OpenDatabase.Direct)) { Table suppressActionTable = output.Tables["WixSuppressAction"]; // suppress individual actions if (null != suppressActionTable) { foreach (Row row in suppressActionTable.Rows) { if (db.TableExists((string)row[0])) { string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); using (View view = db.OpenExecuteView(query)) { using (Record record = view.Fetch()) { if (null != record) { this.core.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); view.Modify(ModifyView.Delete, record); } } } } } } // query for merge module actions in suppressed sequences and drop them foreach (string tableName in suppressedTableNames) { if (!db.TableExists(tableName)) { continue; } using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) { while (true) { using (Record resultRecord = view.Fetch()) { if (null == resultRecord) { break; } this.core.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); } } } // drop suppressed sequences using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) { } // delete the validation rows using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) { using (Record record = new Record(1)) { record.SetString(1, tableName); view.Execute(record); } } } // now update the Attributes column for the files from the Merge Modules this.core.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) { foreach (FileRow fileRow in fileRows) { if (!fileRow.FromModule) { continue; } using (Record record = new Record(1)) { record.SetString(1, fileRow.File); view.Execute(record); } using (Record recordUpdate = view.Fetch()) { if (null == recordUpdate) { throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); } recordUpdate.SetInteger(1, fileRow.Sequence); // update the file attributes to match the compression specified // on the Merge element or on the Package element int attributes = 0; // get the current value if its not null if (!recordUpdate.IsNull(2)) { attributes = recordUpdate.GetInteger(2); } if (YesNoType.Yes == fileRow.Compressed) { // these are mutually exclusive attributes |= MsiInterop.MsidbFileAttributesCompressed; attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; } else if (YesNoType.No == fileRow.Compressed) { // these are mutually exclusive attributes |= MsiInterop.MsidbFileAttributesNoncompressed; attributes &= ~MsiInterop.MsidbFileAttributesCompressed; } else // not specified { Debug.Assert(YesNoType.NotSet == fileRow.Compressed); // clear any compression bits attributes &= ~MsiInterop.MsidbFileAttributesCompressed; attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; } recordUpdate.SetInteger(2, attributes); view.Modify(ModifyView.Update, recordUpdate); } } } db.Commit(); } }
/// <summary> /// Instantiate a new MsiException with a given error. /// </summary> /// <param name="error">The error code from the MsiXxx() function call.</param> public MsiException(int error) : base(error) { uint handle = MsiInterop.MsiGetLastErrorRecord(); if (0 != handle) { using (Record record = new Record(handle)) { this.MsiError = record.GetInteger(1); int errorInfoCount = record.GetFieldCount() - 1; this.ErrorInfo = new string[errorInfoCount]; for (int i = 0; i < errorInfoCount; ++i) { this.ErrorInfo[i] = record.GetString(i + 2); } } } else { this.MsiError = 0; this.ErrorInfo = new string[0]; } this.Error = error; }
/// <summary> /// Query an MSI table for a single value /// </summary> /// <param name="msi">The path to an MSI</param> /// <param name="sql">An MSI query</param> /// <returns>The results as a string or null if no results are returned</returns> /// <remarks> /// Returns the value of the first field in the first record /// </remarks> public static string Query(string msi, string query) { string result = null; using (Database database = new Database(msi, OpenDatabase.ReadOnly)) { using (View view = database.OpenExecuteView(query)) { using (Microsoft.Tools.WindowsInstallerXml.Msi.Record record = view.Fetch()) { if (null != record) { result = Convert.ToString(record.GetString(1)); } } } } return(result); }
/// <summary> /// Process uncompressed files. /// </summary> /// <param name="tempDatabaseFile">The temporary database file.</param> /// <param name="fileRows">The collection of files to copy into the image.</param> /// <param name="fileTransfers">Array of files to be transfered.</param> /// <param name="mediaRows">The indexed media rows.</param> /// <param name="layoutDirectory">The directory in which the image should be layed out.</param> /// <param name="compressed">Flag if source image should be compressed.</param> /// <param name="longNamesInImage">Flag if long names should be used.</param> private void ProcessUncompressedFiles(string tempDatabaseFile, FileRowCollection fileRows, ArrayList fileTransfers, MediaRowCollection mediaRows, string layoutDirectory, bool compressed, bool longNamesInImage) { if (0 == fileRows.Count || this.core.EncounteredError) { return; } Hashtable directories = new Hashtable(); using (Database db = new Database(tempDatabaseFile, OpenDatabase.ReadOnly)) { using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { while (true) { using (Record directoryRecord = directoryView.Fetch()) { if (null == directoryRecord) { break; } string sourceName = Installer.GetName(directoryRecord.GetString(3), true, longNamesInImage); directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); } } } using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) { using (Record fileQueryRecord = new Record(1)) { // for each file in the array of uncompressed files foreach (FileRow fileRow in fileRows) { string relativeFileLayoutPath = null; string mediaLayoutDirectory = this.FileManager.ResolveMedia(mediaRows[fileRow.DiskId], layoutDirectory); // setup up the query record and find the appropriate file in the // previously executed file view fileQueryRecord[1] = fileRow.File; fileView.Execute(fileQueryRecord); using (Record fileRecord = fileView.Fetch()) { if (null == fileRecord) { throw new WixException(WixErrors.FileIdentifierNotFound(fileRow.SourceLineNumbers, fileRow.File)); } relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], compressed, longNamesInImage); } // finally put together the base media layout path and the relative file layout path string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); FileTransfer transfer; if (FileTransfer.TryCreate(fileRow.Source, fileLayoutPath, false, "File", fileRow.SourceLineNumbers, out transfer)) { fileTransfers.Add(transfer); } } } } } }
/// <summary> /// Processes the feature record under the specified parent. /// </summary> /// <param name="writer">XmlWriter where the Intermediate should persist itself as XML.</param> /// <param name="parentWriter">Parent XmlWriter to point to add the reference.</param> /// <param name="parent">Parent feature for this feature.</param> /// <param name="record">Feature record.</param> private void ProcessFeatureRecord(XmlWriter writer, XmlWriter parentWriter, string parent, Record record) { const string tableName = "Feature"; const string tableNameRef = "FeatureRef"; string featureName = record[(int)MsiInterop.Feature.Feature]; this.core.OnMessage(WixVerboses.ProcessingFeature(null, VerboseLevel.Verbose, featureName)); int displayLevel = 0; string display = null; int bits = Convert.ToInt32(record[(int)MsiInterop.Feature.Attributes]); // non-nullable field if (0 < record[(int)MsiInterop.Feature.Display].Length) { displayLevel = Convert.ToInt32(record[(int)MsiInterop.Feature.Display]); } if (0 == displayLevel) { display = "hidden"; } else if (0 < (displayLevel & 1)) // is the first bit set { display = "expand"; } if (this.generateFragments) { writer.WriteStartElement("Fragment"); } writer.WriteStartElement(tableName); this.core.WriteAttributeString(writer, "Id", featureName); this.core.WriteAttributeString(writer, "Title", record[(int)MsiInterop.Feature.Title]); this.core.WriteAttributeString(writer, "Description", record[(int)MsiInterop.Feature.Description]); this.core.WriteAttributeString(writer, "Display", display); // if the condition is null then the level in the condition is actual level for the feature string level = record[(int)MsiInterop.Feature.Level]; if (this.inputDatabase.TableExists("Condition")) { using (View conditionView = this.inputDatabase.OpenExecuteView(String.Concat("SELECT * FROM `Condition` WHERE `Feature_` = '", featureName, "'"))) { Record conditionRecord; while (conditionView.Fetch(out conditionRecord)) { string condition = conditionRecord[(int)MsiInterop.Condition.Condition]; if (0 == condition.Length) { this.core.OnMessage(WixWarnings.NullConditionInConditionTable(null, WarningLevel.Major, featureName)); level = conditionRecord[(int)MsiInterop.Condition.Level]; } } } } this.core.WriteAttributeString(writer, "Level", level); this.core.WriteAttributeString(writer, "ConfigurableDirectory", record[(int)MsiInterop.Feature.Directory]); if (0 < (bits & MsiInterop.MsidbFeatureAttributesFavorSource)) { this.core.WriteAttributeString(writer, "InstallDefault", "source"); } if (0 < (bits & MsiInterop.MsidbFeatureAttributesFavorAdvertise)) { this.core.WriteAttributeString(writer, "TypicalDefault", "advertise"); } if (0 < (bits & MsiInterop.MsidbFeatureAttributesFollowParent)) { this.core.WriteAttributeString(writer, "InstallDefault", "followParent"); } if (0 < (bits & MsiInterop.MsidbFeatureAttributesUIDisallowAbsent)) { this.core.WriteAttributeString(writer, "Absent", "disallow"); } if (0 < (bits & MsiInterop.MsidbFeatureAttributesDisallowAdvertise)) { this.core.WriteAttributeString(writer, "AllowAdvertise", "no"); } else if (0 < (bits & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise)) { this.core.WriteAttributeString(writer, "AllowAdvertise", "system"); } this.ProcessConditionTable(writer, featureName); this.ProcessFeatureComponentTable(writer, featureName); if (this.generateFragments) { parentWriter.WriteStartElement(tableNameRef); this.core.WriteAttributeString(parentWriter, "Id", featureName); parentWriter.WriteEndElement(); } this.ProcessFeatureTable(writer, featureName); // support decompiler extension here foreach (DecompilerExtension extension in this.extensionList) { extension.ExtendChildrenOfElement("Feature", featureName); } if (this.generateFragments) { writer.WriteEndElement(); // close parent (sometimes self) } writer.WriteEndElement(); // close fragment or self }
/// <summary> /// Lays out the binaries for the uncompressed portion of a source image. /// </summary> /// <param name="databasePath">Path to database.</param> /// <param name="output">Output being created.</param> /// <param name="files">Array of files to copy into image.</param> /// <param name="packageCompressed">Flag if package is compressed.</param> /// <param name="fileTransfers">Array of files to be transfered.</param> private void CreateUncompressedImage(string databasePath, Output output, ArrayList files, bool packageCompressed, ArrayList fileTransfers) { if (0 == files.Count || this.foundError) { return; } bool longNamesInImage = output.LongFileNames; Hashtable directories = new Hashtable(); using (Database db = new Database(databasePath, OpenDatabase.ReadOnly)) { using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { Record directoryRecord; while (directoryView.Fetch(out directoryRecord)) { string sourceName = GetSourceName(directoryRecord.GetString(3), longNamesInImage); directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); } } using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) { // if an output path was specified for our image, use that as our default base, // otherwise use the directory where the output is being placed string defaultBaseOuputPath = null != this.imagebaseOutputPath ? this.imagebaseOutputPath : Path.GetDirectoryName(output.Path); using (Record fileQueryRecord = new Record(1)) { // for each file in the array of uncompressed files foreach (FileMediaInformation fmi in files) { string currentSourcePath = null; string relativeSourcePath = null; // determine what the base of the file should be. If there was // no src specified in the Media element (the default) then just // use the default output path (usually the same directory as the // output file). If there was a build directory specified then // check if it is a absolute path, and if not add the default // output path to the root MediaRow mediaRow = output.MediaRows[fmi.Media]; string baseRelativeSourcePath = mediaRow.Layout; if (null == baseRelativeSourcePath) { baseRelativeSourcePath = defaultBaseOuputPath; } else if (!Path.IsPathRooted(baseRelativeSourcePath)) { baseRelativeSourcePath = Path.Combine(defaultBaseOuputPath, baseRelativeSourcePath); } // setup up the query record and find the appropriate file in the // previously executed file view fileQueryRecord[1] = fmi.File; fileView.Execute(fileQueryRecord); Record fileRecord; if (!fileView.Fetch(out fileRecord)) { throw new WixFileMediaInformationKeyNotFoundException(fmi.File); } string fileName = GetSourceName(fileRecord[2], longNamesInImage); if (packageCompressed) { // use just the file name of the file since all uncompressed files must appear // in the root of the image in a compressed package relativeSourcePath = fileName; } else { // get the relative path of where we want the source to be as specified // in the Directory table string directoryPath = GetDirectoryPath(directories, fileRecord[1], longNamesInImage); relativeSourcePath = Path.Combine(directoryPath, fileName); } // if the relative source path was not resolved above then we have to bail if (null == relativeSourcePath) { throw new WixFileMediaInformationKeyNotFoundException(fmi.File); } // strip off "SourceDir" if it's still on there if (relativeSourcePath.StartsWith("SourceDir\\")) { relativeSourcePath = relativeSourcePath.Substring(10); } // resolve the src path for the file and ensure it exists try { currentSourcePath = this.extension.FileResolutionHandler(fmi.Source, FileResolutionType.File); } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(null, ErrorLevel.Normal, wfnfe.Message)); continue; } if (!(File.Exists(currentSourcePath))) { this.OnMessage(WixErrors.CannotFindFile(null, ErrorLevel.Normal, fmi.FileId, fmi.File, fmi.Source)); continue; } // finally put together the base image output path and the resolved source path string resolvedSourcePath = Path.Combine(baseRelativeSourcePath, relativeSourcePath); // if the current source path (where we know that the file already exists) and the resolved // path as dictated by the Directory table are not the same, then propagate the file. The // image that we create may have already been done by some other process other than the linker, so // there is no reason to copy the files to the resolved source if they are already there. if (0 != String.Compare(Path.GetFullPath(currentSourcePath), Path.GetFullPath(resolvedSourcePath), true)) { // just put the file in the transfers array, how anti-climatic fileTransfers.Add(new FileTransfer(currentSourcePath, resolvedSourcePath, false)); } } } } } }
/// <summary> /// Returns a record containing column names or definitions. /// </summary> /// <param name="columnType">Specifies a flag indicating what type of information is needed. Either MSICOLINFO_NAMES or MSICOLINFO_TYPES.</param> /// <param name="record">Record to get column info about.</param> public void GetColumnInfo(int columnType, out Record record) { if (IntPtr.Zero == handle) { throw new ArgumentNullException(); // TODO: come up with a real exception to throw } IntPtr recordHandle; uint error = MsiInterop.MsiViewGetColumnInfo(this.handle, columnType, out recordHandle); if (0 != error) { throw new System.Runtime.InteropServices.ExternalException("Failed to get column info on view", (int)error); } record = new Record(recordHandle); }
/// <summary> /// Executes a query substituing the values from the records into the customizable parameters /// in the view. /// </summary> /// <param name="record">Record containing parameters to be substituded into the view.</param> public void Execute(Record record) { if (IntPtr.Zero == this.handle) { throw new ArgumentNullException(); // TODO: come up with a real exception to throw } uint error = MsiInterop.MsiViewExecute(this.handle, null == record ? IntPtr.Zero : record.InternalHandle); if (0 != error) { throw new System.Runtime.InteropServices.ExternalException("Failed to execute view", (int)error); } }
/// <summary> /// Processes Component table for a given directory. /// </summary> /// <param name="writer">XmlWriter which should will persist the XML.</param> /// <param name="directory">Directory node to process Components for.</param> /// <param name="rootPathShort">Directory path to process components for.</param> /// <param name="rootPathLong">Directory path to process components for.</param> /// <param name="record">Record from the component table to process.</param> private void ProcessComponentRecord(XmlWriter writer, string directory, string rootPathShort, string rootPathLong, Record record) { const string tableName = "Component"; string id = record[(int)MsiInterop.Component.ComponentId]; string rawComponent = record[(int)MsiInterop.Component.Component]; string component = this.core.GetValidIdentifier(rawComponent, "Component"); if ((0 < id.Length && !this.processedComponents.ContainsKey(id)) || !this.processedComponents.ContainsKey(rawComponent)) { string condition = record[(int)MsiInterop.Component.Condition].Trim(); int bits = Convert.ToInt32(record[(int)MsiInterop.Component.Attributes]); // non-nullable field string keyPath = record[(int)MsiInterop.Component.KeyPath]; string regKeyPath = null; string fileKeyPath = null; string odbcKeyPath = null; if (this.generateFragments) { writer.WriteStartElement("Fragment"); if (null != directory) { writer.WriteStartElement("DirectoryRef"); string directoryId = this.StripModuleId(directory); this.core.WriteAttributeString(writer, "Id", directoryId); } } writer.WriteStartElement(tableName); this.core.WriteAttributeString(writer, "Id", this.StripModuleId(component)); this.core.WriteAttributeString(writer, null, "Guid", this.StripBraces(id), true); if (0 < keyPath.Length) { if (0 < (bits & MsiInterop.MsidbComponentAttributesRegistryKeyPath)) { regKeyPath = keyPath; } else if (0 < (bits & MsiInterop.MsidbComponentAttributesODBCDataSource)) { odbcKeyPath = keyPath; } else { fileKeyPath = keyPath; } } else // the Component is using the directory as the keypath { this.core.WriteAttributeString(writer, "KeyPath", "yes"); } if (0 < (bits & MsiInterop.MsidbComponentAttributesSharedDllRefCount)) { this.core.WriteAttributeString(writer, "SharedDllRefCount", "yes"); } if (0 < (bits & MsiInterop.MsidbComponentAttributesPermanent)) { this.core.WriteAttributeString(writer, "Permanent", "yes"); } if (0 < (bits & MsiInterop.MsidbComponentAttributesTransitive)) { this.core.WriteAttributeString(writer, "Transitive", "yes"); } if (0 < (bits & MsiInterop.MsidbComponentAttributesNeverOverwrite)) { this.core.WriteAttributeString(writer, "NeverOverwrite", "yes"); } if (0 < (bits & MsiInterop.MsidbComponentAttributesOptional)) { this.core.WriteAttributeString(writer, "Location", "either"); } else if (0 < (bits & MsiInterop.MsidbComponentAttributesSourceOnly)) { this.core.WriteAttributeString(writer, "Location", "source"); } if (0 < (bits & MsiInterop.MsidbComponentAttributesDisableRegistryReflection)) { this.core.WriteAttributeString(writer, "DisableRegistryReflection", "yes"); } if (this.inputDatabase.TableExists("Complus")) { using (View complusView = this.inputDatabase.OpenExecuteView(String.Concat("SELECT * FROM `Complus` WHERE `Component_`='", rawComponent, "'"))) { Record complusRecord; while (complusView.Fetch(out complusRecord)) { this.core.WriteAttributeString(writer, "ComPlusFlags", complusRecord[(int)MsiInterop.Complus.ExpType]); } } } // support decompiler extension here foreach (DecompilerExtension extension in this.extensionList) { extension.ExtendAttributesOfElement("Component", rawComponent); } if (0 < condition.Length) { writer.WriteStartElement("Condition"); writer.WriteCData(condition); writer.WriteEndElement(); } this.ProcessFileTable(writer, rawComponent, fileKeyPath, rootPathShort, rootPathLong); this.ProcessCreateFolderTable(writer, rawComponent, directory); this.ProcessRegistryTable(writer, rawComponent, regKeyPath, "Registry"); this.ProcessRegistryTable(writer, rawComponent, regKeyPath, "RemoveRegistry"); this.ProcessServiceControlTable(writer, rawComponent); this.ProcessServiceInstallTable(writer, rawComponent); this.ProcessEnvironmentTable(writer, rawComponent); this.ProcessRemoveFileTable(writer, rawComponent); this.ProcessReserveCostTable(writer, rawComponent); this.ProcessShortcutTable(writer, rawComponent); this.ProcessClassTable(writer, rawComponent); this.ProcessExtensionTable(writer, rawComponent, null); this.ProcessIniFileTable(writer, rawComponent, "IniFile"); this.ProcessIniFileTable(writer, rawComponent, "RemoveIniFile"); this.ProcessMoveFileTable(writer, rawComponent); this.ProcessDuplicateFileTable(writer, rawComponent, null); this.ProcessIsolatedComponentTable(writer, rawComponent); this.ProcessTypeLibTable(writer, rawComponent); this.ProcessPublishComponentTable(writer, rawComponent); this.ProcessODBCRootTable(writer, rawComponent, null, "ODBCDriver"); this.ProcessODBCRootTable(writer, rawComponent, null, "ODBCTranslator"); this.ProcessODBCDataSourceTable(writer, rawComponent, null, keyPath); // support decompiler extension here foreach (DecompilerExtension extension in this.extensionList) { extension.ExtendChildrenOfElement("Component", rawComponent); } if (this.generateFragments) { if (null != directory) { writer.WriteEndElement(); // close self } writer.WriteEndElement(); // close parent (sometimes self) writer.WriteEndElement(); // close fragment } else { writer.WriteEndElement(); } if (0 < id.Length) { this.processedComponents.Add(id, 1); } else { this.processedComponents.Add(rawComponent, 1); } } }
/// <summary> /// Print File Search /// </summary> /// <param name="writer">XmlWriter where the Intermediate should persist itself as XML.</param> /// <param name="fileRecord">file record.</param> private void PrintFileSearch(XmlWriter writer, Record fileRecord) { if (fileRecord != null) { string signature = fileRecord[(int)MsiInterop.Signature.Signature]; this.core.OnMessage(WixVerboses.ProcessingFileSearchSignature(null, VerboseLevel.Verbose, signature)); if (this.fileSearchSignatures.ContainsKey(this.StripModuleId(signature))) { writer.WriteStartElement("FileSearchRef"); this.core.WriteAttributeString(writer, "Id", this.StripModuleId(signature)); } else { this.fileSearchSignatures[this.StripModuleId(signature)] = true; string fileName = fileRecord[(int)MsiInterop.Signature.FileName]; string minversion = fileRecord[(int)MsiInterop.Signature.MinVersion]; string maxversion = fileRecord[(int)MsiInterop.Signature.MaxVersion]; string minsize = fileRecord[(int)MsiInterop.Signature.MinSize]; string maxsize = fileRecord[(int)MsiInterop.Signature.MaxSize]; string mindate = this.core.ConvertMSIDateToXmlDate(fileRecord[(int)MsiInterop.Signature.MinDate]); string maxdate = this.core.ConvertMSIDateToXmlDate(fileRecord[(int)MsiInterop.Signature.MaxDate]); string languages = fileRecord[(int)MsiInterop.Signature.Languages]; // due to a Windows Installer bug, it's legal to have either the Name or LongName // attribute or both attributes string name = null; string longName = null; string[] fileNames = fileName.Split("|".ToCharArray()); if (2 == fileNames.Length) // both short and long names { name = fileNames[0]; longName = fileNames[1]; } else if (CompilerCore.IsValidShortFilename(fileName)) // only short name is present { name = fileName; } else // only long name is present { longName = fileName; } writer.WriteStartElement("FileSearch"); this.core.WriteAttributeString(writer, "Id", this.StripModuleId(signature)); this.core.WriteAttributeString(writer, "LongName", longName); this.core.WriteAttributeString(writer, "Name", name); this.core.WriteAttributeString(writer, "MinVersion", minversion); this.core.WriteAttributeString(writer, "MaxVersion", maxversion); this.core.WriteAttributeString(writer, "MinSize", minsize); this.core.WriteAttributeString(writer, "MaxSize", maxsize); this.core.WriteAttributeString(writer, "MinDate", mindate); this.core.WriteAttributeString(writer, "MaxDate", maxdate); this.core.WriteAttributeString(writer, "Languages", languages); } writer.WriteEndElement(); } }
/// <summary> /// Unbind an MSI database file. /// </summary> /// <param name="databaseFile">The database file.</param> /// <param name="database">The opened database.</param> /// <param name="outputType">The type of output to create.</param> /// <param name="exportBasePath">The path where files should be exported.</param> /// <param name="skipSummaryInfo">Option to skip unbinding the _SummaryInformation table.</param> /// <returns>The output representing the database.</returns> private Output UnbindDatabase(string databaseFile, Database database, OutputType outputType, string exportBasePath, bool skipSummaryInfo) { string modularizationGuid = null; Output output = new Output(SourceLineNumberCollection.FromFileName(databaseFile)); View validationView = null; // set the output type output.Type = outputType; // get the codepage database.Export("_ForceCodepage", this.TempFilesLocation, "_ForceCodepage.idt"); using (StreamReader sr = File.OpenText(Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"))) { string line; while (null != (line = sr.ReadLine())) { string[] data = line.Split('\t'); if (2 == data.Length) { output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); } } } // get the summary information table if it exists; it won't if unbinding a transform if (!skipSummaryInfo) { using (SummaryInformation summaryInformation = new SummaryInformation(database)) { Table table = new Table(null, this.tableDefinitions["_SummaryInformation"]); for (int i = 1; 19 >= i; i++) { string value = summaryInformation.GetProperty(i); if (0 < value.Length) { Row row = table.CreateRow(output.SourceLineNumbers); row[0] = i; row[1] = value; } } output.Tables.Add(table); } } try { // open a view on the validation table if it exists if (database.TableExists("_Validation")) { validationView = database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); } // get the normal tables using (View tablesView = database.OpenExecuteView("SELECT * FROM _Tables")) { while (true) { using (Record tableRecord = tablesView.Fetch()) { if (null == tableRecord) { break; } string tableName = tableRecord.GetString(1); using (View tableView = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) { TableDefinition tableDefinition = new TableDefinition(tableName, false, false); Hashtable tablePrimaryKeys = new Hashtable(); using (Record columnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES), columnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES)) { int columnCount = columnNameRecord.GetFieldCount(); // index the primary keys using (Record primaryKeysRecord = database.PrimaryKeys(tableName)) { int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); for (int i = 1; i <= primaryKeysFieldCount; i++) { tablePrimaryKeys[primaryKeysRecord.GetString(i)] = null; } } for (int i = 1; i <= columnCount; i++) { string columnName = columnNameRecord.GetString(i); string idtType = columnTypeRecord.GetString(i); ColumnType columnType; int length; bool nullable; ColumnCategory columnCategory = ColumnCategory.Unknown; ColumnModularizeType columnModularizeType = ColumnModularizeType.None; bool primary = tablePrimaryKeys.Contains(columnName); bool minValueSet = false; int minValue = -1; bool maxValueSet = false; int maxValue = -1; string keyTable = null; bool keyColumnSet = false; int keyColumn = -1; string category = null; string set = null; string description = null; // get the column type, length, and whether its nullable switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) { case 'i': columnType = ColumnType.Number; break; case 'l': columnType = ColumnType.Localized; break; case 's': columnType = ColumnType.String; break; case 'v': columnType = ColumnType.Object; break; default: // TODO: error columnType = ColumnType.Unknown; break; } length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); nullable = Char.IsUpper(idtType[0]); // try to get validation information if (null != validationView) { using (Record validationRecord = new Record(2)) { validationRecord.SetString(1, tableName); validationRecord.SetString(2, columnName); validationView.Execute(validationRecord); } using (Record validationRecord = validationView.Fetch()) { if (null != validationRecord) { string validationNullable = validationRecord.GetString(3); minValueSet = !validationRecord.IsNull(4); minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); maxValueSet = !validationRecord.IsNull(5); maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); keyColumnSet = !validationRecord.IsNull(7); keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); // check the validation nullable value against the column definition if (null == validationNullable) { // TODO: warn for illegal validation nullable column } else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) { // TODO: warn for mismatch between column definition and validation nullable } // convert category to ColumnCategory if (null != category) { try { columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); } catch (ArgumentException) { columnCategory = ColumnCategory.Unknown; } } } else { // TODO: warn about no validation information } } } // guess the modularization type if ("Icon" == keyTable && 1 == keyColumn) { columnModularizeType = ColumnModularizeType.Icon; } else if ("Condition" == columnName) { columnModularizeType = ColumnModularizeType.Condition; } else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) { columnModularizeType = ColumnModularizeType.Property; } else if (ColumnCategory.Identifier == columnCategory) { columnModularizeType = ColumnModularizeType.Column; } tableDefinition.Columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); } } // use our table definitions if core properties are the same; this allows us to take advantage // of wix concepts like localizable columns which current code assumes if (this.tableDefinitions.Contains(tableName) && 0 == tableDefinition.CompareTo(this.tableDefinitions[tableName])) { tableDefinition = this.tableDefinitions[tableName]; } Table table = new Table(null, tableDefinition); while (true) { using (Record rowRecord = tableView.Fetch()) { if (null == rowRecord) { break; } int recordCount = rowRecord.GetFieldCount(); Row row = table.CreateRow(output.SourceLineNumbers); for (int i = 0; recordCount > i && row.Fields.Length > i; i++) { if (rowRecord.IsNull(i + 1)) { if (!row.Fields[i].Column.IsNullable) { // TODO: display an error for a null value in a non-nullable field OR // display a warning and put an empty string in the value to let the compiler handle it // (the second option is risky because the later code may make certain assumptions about // the contents of a row value) } } else { switch (row.Fields[i].Column.Type) { case ColumnType.Number: bool success = false; int intValue = rowRecord.GetInteger(i + 1); if (row.Fields[i].Column.IsLocalizable) { success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); } else { success = row.BestEffortSetField(i, intValue); } if (!success) { this.OnMessage(WixWarnings.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); } break; case ColumnType.Object: string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; if (null != exportBasePath) { string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); sourceFile = Path.Combine(exportBasePath, relativeSourceFile); // ensure the parent directory exists System.IO.Directory.CreateDirectory(Path.Combine(exportBasePath, tableName)); using (FileStream fs = System.IO.File.Create(sourceFile)) { int bytesRead; byte[] buffer = new byte[512]; while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } } row[i] = sourceFile; break; default: string value = rowRecord.GetString(i + 1); switch (row.Fields[i].Column.Category) { case ColumnCategory.Guid: value = value.ToUpper(CultureInfo.InvariantCulture); break; } // de-modularize if (!this.suppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) { Regex modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); if (null == modularizationGuid) { Match match = modularization.Match(value); if (match.Success) { modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); } } value = modularization.Replace(value, String.Empty); } // escape "$(" for the preprocessor value = value.Replace("$(", "$$("); // escape things that look like wix variables MatchCollection matches = Common.WixVariableRegex.Matches(value); for (int j = matches.Count - 1; 0 <= j; j--) { value = value.Insert(matches[j].Index, "!"); } row[i] = value; break; } } } } } output.Tables.Add(table); } } } } } finally { if (null != validationView) { validationView.Close(); } } // set the modularization guid as the PackageCode if (null != modularizationGuid) { Table table = output.Tables["_SummaryInformation"]; foreach (Row row in table.Rows) { if (9 == (int)row[0]) // PID_REVNUMBER { row[1] = modularizationGuid; } } } if (this.isAdminImage) { GenerateWixFileTable(databaseFile, output); GenerateSectionIds(output); } return output; }
/// <summary> /// Extract the cabinets from a database. /// </summary> /// <param name="output">The output to use when finding cabinets.</param> /// <param name="database">The database containing the cabinets.</param> /// <param name="databaseFile">The location of the database file.</param> /// <param name="exportBasePath">The path where the files should be exported.</param> private void ExtractCabinets(Output output, Database database, string databaseFile, string exportBasePath) { string databaseBasePath = Path.GetDirectoryName(databaseFile); StringCollection cabinetFiles = new StringCollection(); SortedList embeddedCabinets = new SortedList(); // index all of the cabinet files if (OutputType.Module == output.Type) { embeddedCabinets.Add(0, "MergeModule.CABinet"); } else if (null != output.Tables["Media"]) { foreach (MediaRow mediaRow in output.Tables["Media"].Rows) { if (null != mediaRow.Cabinet) { if (OutputType.Product == output.Type || (OutputType.Transform == output.Type && RowOperation.Add == mediaRow.Operation)) { if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) { embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); } else { cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet)); } } } } } // extract the embedded cabinet files from the database if (0 < embeddedCabinets.Count) { using (View streamsView = database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) { foreach (int diskId in embeddedCabinets.Keys) { using(Record record = new Record(1)) { record.SetString(1, (string)embeddedCabinets[diskId]); streamsView.Execute(record); } using (Record record = streamsView.Fetch()) { if (null != record) { // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not case-sensitive, // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work string cabinetFile = Path.Combine(this.TempFilesLocation, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); // ensure the parent directory exists System.IO.Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); using (FileStream fs = System.IO.File.Create(cabinetFile)) { int bytesRead; byte[] buffer = new byte[512]; while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } cabinetFiles.Add(cabinetFile); } else { // TODO: warning about missing embedded cabinet } } } } } // extract the cabinet files if (0 < cabinetFiles.Count) { string fileDirectory = Path.Combine(exportBasePath, "File"); // delete the directory and its files to prevent cab extraction due to an existing file if (Directory.Exists(fileDirectory)) { Directory.Delete(fileDirectory, true); } // ensure the directory exists or extraction will fail Directory.CreateDirectory(fileDirectory); foreach (string cabinetFile in cabinetFiles) { using (WixExtractCab extractCab = new WixExtractCab()) { try { extractCab.Extract(cabinetFile, fileDirectory); } catch (FileNotFoundException) { throw new WixException(WixErrors.FileNotFound(SourceLineNumberCollection.FromFileName(databaseFile), cabinetFile)); } } } } }
/// <summary> /// Updates a fetched record. /// </summary> /// <param name="type">Type of modification mode.</param> /// <param name="record">Record to be modified.</param> public void Modify(ModifyView type, Record record) { int error = MsiInterop.MsiViewModify(this.Handle, Convert.ToInt32(type, CultureInfo.InvariantCulture), record.Handle); if (0 != error) { throw new MsiException(error); } }
/// <summary> /// Executes a query substituing the values from the records into the customizable parameters /// in the view. /// </summary> /// <param name="record">Record containing parameters to be substituded into the view.</param> public void Execute(Record record) { int error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle); if (0 != error) { throw new MsiException(error); } }
/// <summary> /// Adds all the streams to the final output. /// </summary> /// <param name="databasePath">Path to database.</param> /// <param name="output">Output object that points at final output.</param> private void ImportStreams(string databasePath, Output output) { using (Database db = new Database(databasePath, OpenDatabase.Direct)) { View streamsView = null; View binaryView = null; View iconView = null; View certificateView = null; try { streamsView = db.OpenExecuteView("SELECT `Name`, `Data` FROM `_Streams`"); if (db.TableExists("Binary")) { binaryView = db.OpenExecuteView("SELECT `Name`, `Data` FROM `Binary`"); } if (db.TableExists("Icon")) { iconView = db.OpenExecuteView("SELECT `Name`, `Data` FROM `Icon`"); } if (db.TableExists("MsiDigitalCertificate")) { certificateView = db.OpenExecuteView("SELECT `DigitalCertificate`, `CertData` FROM `MsiDigitalCertificate`"); } foreach (ImportStream importStream in output.ImportStreams) { string src; using (Record record = new Record(2)) { try { switch (importStream.Type) { case ImportStreamType.Cabinet: this.OnMessage(WixVerboses.ImportCabinetStream(importStream.StreamName, importStream.Path)); record[1] = importStream.StreamName; record.SetStream(2, importStream.Path); streamsView.Modify(ModifyView.Assign, record); break; case ImportStreamType.DigitalCertificate: src = this.extension.FileResolutionHandler(importStream.Path, FileResolutionType.DigitalCertificate); this.OnMessage(WixVerboses.ImportDigitalCertificateStream(null, VerboseLevel.Trace, src)); record[1] = importStream.StreamName; record.SetStream(2, importStream.Path); certificateView.Modify(ModifyView.Assign, record); break; case ImportStreamType.Binary: src = this.extension.FileResolutionHandler(importStream.Path, FileResolutionType.Binary); this.OnMessage(WixVerboses.ImportBinaryStream(null, VerboseLevel.Trace, src)); if (OutputType.Module == output.Type) { record[1] = String.Concat(importStream.StreamName, ".", output.ModularizationGuid); } else { record[1] = importStream.StreamName; } if (55 < record[1].Length) { throw new WixInvalidAttributeException(null, "Binary", "Id", String.Format("Identifier cannot be longer than 55 characters. Binary identifier: {0}", record[1])); } record.SetStream(2, src); binaryView.Modify(ModifyView.Assign, record); break; case ImportStreamType.Icon: src = this.extension.FileResolutionHandler(importStream.Path, FileResolutionType.Icon); this.OnMessage(WixVerboses.ImportIconStream(null, VerboseLevel.Verbose, src)); if (OutputType.Module == output.Type) { int start = importStream.StreamName.LastIndexOf("."); if (-1 == start) { record[1] = String.Concat(importStream.StreamName, ".", output.ModularizationGuid); } else { record[1] = String.Concat(importStream.StreamName.Substring(0, start), ".", output.ModularizationGuid, importStream.StreamName.Substring(start)); } } else { record[1] = importStream.StreamName; } if (55 < record[1].Length) { throw new WixInvalidAttributeException(null, "Icon", "Id", String.Format("Identifier cannot be longer than 55 characters. Icon identifier: {0}", record[1])); } record.SetStream(2, src); iconView.Modify(ModifyView.Assign, record); break; default: throw new ArgumentException(String.Format("unknown import stream type: {0}, name: {1}", importStream.Type, importStream.StreamName), "importStream"); } } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(null, ErrorLevel.Normal, wfnfe.Message)); } } } db.Commit(); } catch (FileNotFoundException fnfe) { throw new WixFileNotFoundException(null, fnfe.FileName, fnfe); } finally { if (null != certificateView) { certificateView.Close(); } if (null != iconView) { iconView.Close(); } if (null != binaryView) { binaryView.Close(); } if (null != streamsView) { streamsView.Close(); } } } }
/// <summary> /// Creates the MSI/MSM/PCP database. /// </summary> /// <param name="output">Output to create database for.</param> /// <param name="databaseFile">The database file to create.</param> /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param> /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param> internal void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) { // add the _Validation rows if (!this.suppressAddingValidationRows) { Table validationTable = output.EnsureTable(this.core.TableDefinitions["_Validation"]); foreach (Table table in output.Tables) { if (!table.Definition.IsUnreal) { // add the validation rows for this table table.Definition.AddValidationRows(validationTable); } } } // set the base directory string baseDirectory = this.TempFilesLocation; if (useSubdirectory) { string filename = Path.GetFileNameWithoutExtension(databaseFile); baseDirectory = Path.Combine(baseDirectory, filename); // make sure the directory exists Directory.CreateDirectory(baseDirectory); } try { OpenDatabase type = OpenDatabase.CreateDirect; // set special flag for patch files if (OutputType.Patch == output.Type) { type |= OpenDatabase.OpenPatchFile; } // try to create the database using (Database db = new Database(databaseFile, type)) { // localize the codepage if a value was specified by the localizer if (null != this.Localizer && -1 != this.Localizer.Codepage) { output.Codepage = this.Localizer.Codepage; } // 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 // with the wrong codepage) if (0 != output.Codepage) { this.SetDatabaseCodepage(db, output); } // insert substorages (like transforms inside a patch) if (0 < output.SubStorages.Count) { using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) { foreach (SubStorage subStorage in output.SubStorages) { string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); // bind the transform if (this.BindTransform(subStorage.Data, transformFile)) { // add the storage using (Record record = new Record(2)) { record.SetString(1, subStorage.Name); record.SetStream(2, transformFile); storagesView.Modify(ModifyView.Assign, record); } } } } // some empty transforms may have been excluded // we need to remove these from the final patch summary information if (OutputType.Patch == output.Type && this.AllowEmptyTransforms) { Table patchSummaryInfo = output.EnsureTable(this.core.TableDefinitions["_SummaryInformation"]); for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) { Row row = patchSummaryInfo.Rows[i]; if ((int)SummaryInformation.Patch.ProductCodes == (int)row[0]) { if (nonEmptyProductCodes.Count > 0) { string[] productCodes = new string[nonEmptyProductCodes.Count]; nonEmptyProductCodes.CopyTo(productCodes, 0); row[1] = String.Join(";", productCodes); } else { row[1] = Binder.NullString; } } else if ((int)SummaryInformation.Patch.TransformNames == (int)row[0]) { if (nonEmptyTransformNames.Count > 0) { string[] transformNames = new string[nonEmptyTransformNames.Count]; nonEmptyTransformNames.CopyTo(transformNames, 0); row[1] = String.Join(";", transformNames); } else { row[1] = Binder.NullString; } } } } } foreach (Table table in output.Tables) { Table importTable = table; bool hasBinaryColumn = false; // skip all unreal tables other than _Streams if (table.Definition.IsUnreal && "_Streams" != table.Name) { continue; } // Do not put the _Validation table in patches, it is not needed if (OutputType.Patch == output.Type && "_Validation" == table.Name) { continue; } // The only way to import binary data is to copy it to a local subdirectory first. // To avoid this extra copying and perf hit, import an empty table with the same // definition and later import the binary data from source using records. foreach (ColumnDefinition columnDefinition in table.Definition.Columns) { if (ColumnType.Object == columnDefinition.Type) { importTable = new Table(table.Section, table.Definition); hasBinaryColumn = true; break; } } // create the table via IDT import if ("_Streams" != importTable.Name) { try { db.ImportTable(output.Codepage, this.core, importTable, baseDirectory, keepAddedColumns); } catch (WixInvalidIdtException) { // If ValidateRows finds anything it doesn't like, it throws importTable.ValidateRows(); // Otherwise we rethrow the InvalidIdt throw; } } // insert the rows via SQL query if this table contains object fields if (hasBinaryColumn) { StringBuilder query = new StringBuilder("SELECT "); // build the query for the view bool firstColumn = true; foreach (ColumnDefinition columnDefinition in table.Definition.Columns) { if (!firstColumn) { query.Append(","); } query.AppendFormat(" `{0}`", columnDefinition.Name); firstColumn = false; } query.AppendFormat(" FROM `{0}`", table.Name); using (View tableView = db.OpenExecuteView(query.ToString())) { // import each row containing a stream foreach (Row row in table.Rows) { using (Record record = new Record(table.Definition.Columns.Count)) { StringBuilder streamName = new StringBuilder(); bool needStream = false; // the _Streams table doesn't prepend the table name (or a period) if ("_Streams" != table.Name) { streamName.Append(table.Name); } for (int i = 0; i < table.Definition.Columns.Count; i++) { ColumnDefinition columnDefinition = table.Definition.Columns[i]; switch (columnDefinition.Type) { case ColumnType.Localized: case ColumnType.Preserved: case ColumnType.String: if (columnDefinition.IsPrimaryKey) { if (0 < streamName.Length) { streamName.Append("."); } streamName.Append((string)row[i]); } record.SetString(i + 1, (string)row[i]); break; case ColumnType.Number: record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture)); break; case ColumnType.Object: if (null != row[i]) { needStream = true; try { record.SetStream(i + 1, (string)row[i]); } catch (Win32Exception e) { if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME { throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i])); } else { throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); } } } break; } } // stream names are created by concatenating the name of the table with the values // of the primary key (delimited by periods) // check for a stream name that is more than 62 characters long (the maximum allowed length) if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) { this.core.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); } else // add the row to the database { tableView.Modify(ModifyView.Assign, record); } } } } // Remove rows from the _Streams table for wixpdbs. if ("_Streams" == table.Name) { table.Rows.Clear(); } } } // we're good, commit the changes to the new MSI db.Commit(); } } catch (IOException) { // TODO: this error message doesn't seem specific enough throw new WixFileNotFoundException(SourceLineNumberCollection.FromFileName(databaseFile), databaseFile); } }
/// <summary> /// Executes the view to fetch the value for a specified property. /// </summary> /// <param name="view">View that is already open on the Property table.</param> /// <param name="propertyName">Name of the property to get the value for.</param> /// <returns>String value of the property.</returns> private string FetchPropertyValue(View view, string propertyName) { string propertyValue = null; using (Record recIn = new Record(1)) { recIn[1] = propertyName; view.Execute(recIn); using (Record recOut = view.Fetch()) { if (recOut != null) { propertyValue = recOut[1]; } } } return propertyValue; }
/// <summary> /// Process control. /// </summary> /// <param name="writer">XmlWriter where the Intermediate should persist itself as XML.</param> /// <param name="parentWriter">XmlWriter where the Intermediate should persist itself as XML.</param> /// <param name="record">Record from Control table.</param> /// <param name="defaultControl">Name of default control.</param> /// <param name="cancelControl">Name of cancel control.</param> /// <param name="tabDisabled">Specifies if control should not be tabbed to.</param> private void ProcessControl(XmlWriter writer, XmlWriter parentWriter, Record record, string defaultControl, string cancelControl, bool tabDisabled) { const string tableName = "Control"; const string tableNameDependent = "CheckBox"; bool hasCheckBoxTable = false; if (!this.inputDatabase.TableExists(tableName)) { return; } if (this.inputDatabase.TableExists(tableNameDependent)) { hasCheckBoxTable = true; } string id = record[(int)MsiInterop.Control.Control]; string controlType = record[(int)MsiInterop.Control.Type]; string text = record[(int)MsiInterop.Control.Text]; if (!(this.skipInstallShield && 0 < text.IndexOf("InstallShield"))) { writer.WriteStartElement(tableName); this.core.WriteAttributeString(writer, "Id", id); this.core.WriteAttributeString(writer, "Type", controlType); this.core.WriteAttributeString(writer, "X", record[(int)MsiInterop.Control.X]); this.core.WriteAttributeString(writer, "Y", record[(int)MsiInterop.Control.Y]); this.core.WriteAttributeString(writer, "Width", record[(int)MsiInterop.Control.Width]); this.core.WriteAttributeString(writer, "Height", record[(int)MsiInterop.Control.Height]); this.core.WriteAttributeString(writer, "Property", record[(int)MsiInterop.Control.Property]); string[] helpArray = record[(int)MsiInterop.Control.Help].Split('|'); if (0 < helpArray.Length) { this.core.WriteAttributeString(writer, "ToolTip", helpArray[0]); if (1 < helpArray.Length && 0 < helpArray[1].Length) { this.core.WriteAttributeString(writer, "Help", helpArray[1]); } } if (hasCheckBoxTable && "CheckBox" == controlType) { using (View checkBoxView = this.inputDatabase.OpenExecuteView(String.Concat("SELECT `Value` FROM `", tableNameDependent, "` WHERE `Property`='", record[(int)MsiInterop.Control.Property], "'"))) { Record checkBoxRecord; while (checkBoxView.Fetch(out checkBoxRecord)) // will return a single value or will fail { this.core.WriteAttributeString(writer, "CheckBoxValue", checkBoxRecord[1]); // hard coded value because select (above) is for a single column } } } if (id == defaultControl) { this.core.WriteAttributeString(writer, "Default", "yes"); } if (id == cancelControl) { this.core.WriteAttributeString(writer, "Cancel", "yes"); } // TODO: test Attributes for blowing up when null, error violation of SDK constraint "This must be a non-negative number" this.ProcessControlAttributes(writer, controlType, Convert.ToInt32(record[(int)MsiInterop.Control.Attributes]), tabDisabled); if (0 < record[(int)MsiInterop.Control.Text].Length) { if (20 >= text.Length && !this.NeedsEscape(text)) { this.core.WriteAttributeString(writer, "Text", text); } else { writer.WriteStartElement("Text"); writer.WriteCData(text); writer.WriteEndElement(); } } string property = record[(int)MsiInterop.Control.Property]; if (0 < property.Length) { if ("ListBox" == controlType) { this.ProcessControlGroupTable(writer, "ListBox", record[(int)MsiInterop.Control.Property]); } else if ("RadioButtonGroup" == controlType) { this.ProcessControlGroupTable(writer, "RadioButton", record[(int)MsiInterop.Control.Property]); } else if ("ListView" == controlType) { this.ProcessControlGroupTable(writer, "ListView", record[(int)MsiInterop.Control.Property]); } else if ("ComboBox" == controlType) { this.ProcessControlGroupTable(writer, "ComboBox", record[(int)MsiInterop.Control.Property]); } else { this.EmitProperty(parentWriter, this.nonuiproperties, property); } } /* broken and not needed right now if ("Icon" == controlType && 0 < record[(int)MsiInterop.Control.Text].Length) { this.ProcessBinaryTable(writer, BinaryType.Binary, record[(int)MsiInterop.Control.Text]); } */ Record sqlParams = new Record(2); sqlParams.SetString(1, record[(int)MsiInterop.Control.Dialog]); sqlParams.SetString(2, record[(int)MsiInterop.Control.Control]); if (this.inputDatabase.TableExists("ControlEvent")) { using (View publishView = this.inputDatabase.OpenView("SELECT * FROM `ControlEvent` WHERE `Dialog_` = ? AND `Control_` = ? ORDER BY `Ordering`")) { publishView.Execute(sqlParams); Record publishRecord; while (publishView.Fetch(out publishRecord)) { writer.WriteStartElement("Publish"); string controlEvent = publishRecord[(int)MsiInterop.ControlEvent.Event]; if (controlEvent.StartsWith("[") && controlEvent.EndsWith("]")) { this.core.WriteAttributeString(writer, "Property", controlEvent.Substring(1, controlEvent.Length - 2)); } else { this.core.WriteAttributeString(writer, "Event", controlEvent); } this.core.WriteAttributeString(writer, "Value", publishRecord[(int)MsiInterop.ControlEvent.Argument]); writer.WriteCData(publishRecord[(int)MsiInterop.ControlEvent.Condition]); writer.WriteEndElement(); } } } if (this.inputDatabase.TableExists("EventMapping")) { using (View subscribeView = this.inputDatabase.OpenView("SELECT * FROM `EventMapping` WHERE `Dialog_` = ? AND `Control_` = ?")) { subscribeView.Execute(sqlParams); Record subscribeRecord; while (subscribeView.Fetch(out subscribeRecord)) { writer.WriteStartElement("Subscribe"); this.core.WriteAttributeString(writer, "Event", subscribeRecord[(int)MsiInterop.EventMapping.Event]); this.core.WriteAttributeString(writer, "Attribute", subscribeRecord[(int)MsiInterop.EventMapping.Attribute]); writer.WriteEndElement(); } } } if (this.inputDatabase.TableExists("ControlCondition")) { using (View conditionView = this.inputDatabase.OpenView("SELECT * FROM `ControlCondition` WHERE `Dialog_` = ? AND `Control_` = ?")) { conditionView.Execute(sqlParams); Record conditionRecord; while (conditionView.Fetch(out conditionRecord)) { writer.WriteStartElement("Condition"); this.core.WriteAttributeString(writer, "Action", conditionRecord[(int)MsiInterop.ControlCondition.Action].ToLower()); writer.WriteCData(conditionRecord[(int)MsiInterop.ControlCondition.Condition]); writer.WriteEndElement(); } } } writer.WriteEndElement(); } else { this.core.OnMessage(WixWarnings.FilteringInstallShieldStuff(null, WarningLevel.Major, "Control", String.Concat(record[(int)MsiInterop.Control.Dialog], "::", record[(int)MsiInterop.Control.Control]))); } }
/// <summary> /// Processes the feature record under the specified parent. /// </summary> /// <param name="parentWriter">XmlWriter where the Intermediate should persist itself as XML.</param> /// <param name="record">record from the custom action table that needs to be estruded as XML.</param> private void ProcessCustomActionRecord(XmlWriter parentWriter, Record record) { const string tableName = "CustomAction"; XmlWriter writer = parentWriter; string id = this.StripModuleId(this.core.GetValidIdentifier(record[(int)MsiInterop.CustomAction.Action], "CustomAction; Column: Action")); if (null == writer) { writer = this.InitializeXmlTextWriter(Path.GetFullPath(Path.Combine(this.outputFolder, String.Concat("BaseLibrary\\CustomActions\\", id, ".wxs")))); } if (this.generateFragments && null == parentWriter) { writer.WriteStartElement("Fragment"); this.core.WriteAttributeString(writer, "Id", id); } string source = record[(int)MsiInterop.CustomAction.Source]; string target = record[(int)MsiInterop.CustomAction.Target]; // type is a non-nullable field int bits = Convert.ToInt32(record[(int)MsiInterop.CustomAction.Type]); writer.WriteStartElement(tableName); this.core.WriteAttributeString(writer, "Id", id); string returnType = null; switch (bits & MsiInterop.MsidbCustomActionTypeReturnBits) { case 0: returnType = "check"; break; case MsiInterop.MsidbCustomActionTypeContinue: returnType = "ignore"; break; case MsiInterop.MsidbCustomActionTypeAsync: returnType = "asyncWait"; break; case MsiInterop.MsidbCustomActionTypeAsync + MsiInterop.MsidbCustomActionTypeContinue: returnType = "asyncNoWait"; break; default: // TODO: throw an exception break; } this.core.WriteAttributeString(writer, "Return", returnType); string execute = null; switch (bits & MsiInterop.MsidbCustomActionTypeExecuteBits) { case 0: execute = null; break; case MsiInterop.MsidbCustomActionTypeFirstSequence: execute = "firstSequence"; break; case MsiInterop.MsidbCustomActionTypeOncePerProcess: execute = "oncePerProcess"; break; case MsiInterop.MsidbCustomActionTypeClientRepeat: execute = "secondSequence"; break; case MsiInterop.MsidbCustomActionTypeInScript: execute = "deferred"; break; case MsiInterop.MsidbCustomActionTypeInScript + MsiInterop.MsidbCustomActionTypeRollback: execute = "rollback"; break; case MsiInterop.MsidbCustomActionTypeInScript + MsiInterop.MsidbCustomActionTypeCommit: execute = "commit"; break; case 7: default: // TODO: throw an exception break; } this.core.WriteAttributeString(writer, "Execute", execute); if (0 < (bits & MsiInterop.MsidbCustomActionTypeNoImpersonate)) { this.core.WriteAttributeString(writer, "Impersonate", "no"); } string name = null; string customActionType = null; switch (bits & MsiInterop.MsidbCustomActionTypeSourceBits) { case MsiInterop.MsidbCustomActionTypeBinaryData: name = "BinaryKey"; break; case MsiInterop.MsidbCustomActionTypeSourceFile: name = "FileKey"; break; case MsiInterop.MsidbCustomActionTypeDirectory: name = "Directory"; break; case MsiInterop.MsidbCustomActionTypeProperty: name = "Property"; break; default: // TODO: throw an exception break; } bool canBeEmptyCustomActionTypeValue = false; switch (bits & MsiInterop.MsidbCustomActionTypeTypeBits) { case MsiInterop.MsidbCustomActionTypeDll: customActionType = "DllEntry"; break; case MsiInterop.MsidbCustomActionTypeExe: canBeEmptyCustomActionTypeValue = true; customActionType = "ExeCommand"; break; case MsiInterop.MsidbCustomActionTypeTextData: canBeEmptyCustomActionTypeValue = true; customActionType = "Value"; break; case MsiInterop.MsidbCustomActionTypeJScript: canBeEmptyCustomActionTypeValue = true; customActionType = "JScriptCall"; break; case MsiInterop.MsidbCustomActionTypeVBScript: canBeEmptyCustomActionTypeValue = true; customActionType = "VBScriptCall"; break; case MsiInterop.MsidbCustomActionTypeInstall: customActionType = "InstallProperties"; if ("Directory" == name) { name = "PackageProductCode"; } if ("FileKey" == name) { name = "PackagePath"; } if ("BinaryKey" == name) { name = "PackageSubstorage"; } break; case 0: case 4: default: // TODO: throw an exception: Fail "Unsupported custom action type: " break; } if ("FileKey" == name && "Value" == customActionType) { this.core.WriteAttributeString(writer, "Error", target); } else { if (0 < source.Length) { source = this.StripModuleId(source); if ("Directory" == name) { source = this.core.GetValidIdentifier(source, "CustomAction; Column: Source"); // nullable } this.core.WriteAttributeString(writer, name, source); // target is a formatted field so it is possible the value is a // foriegn key with a module id in it. target = this.StripModuleId(target); this.core.WriteAttributeString(writer, null, customActionType, target, canBeEmptyCustomActionTypeValue); } else { if ("JScriptCall" == customActionType) { this.core.WriteAttributeString(writer, "Script", "jscript"); } else if ("VBScriptCall" == customActionType) { this.core.WriteAttributeString(writer, "Script", "vbscript"); } else { // TODO: throw an exception } writer.WriteCData(target); } } writer.WriteEndElement(); if (this.generateFragments) { this.EmitSequence("AdminUISequence", writer, id); // write InstallUISequence this.EmitSequence("InstallUISequence", writer, id); this.EmitSequence("AdvertiseExecuteSequence", writer, id); this.EmitSequence("AdminExecuteSequence", writer, id); this.EmitSequence("InstallExecuteSequence", writer, id); if (null == parentWriter) { writer.WriteEndElement(); } } }
/// <summary> /// Processes the Dialog table. /// </summary> /// <param name="parentWriter">XmlWriter where the Intermediate should persist itself as XML.</param> private void ProcessDialogTable(XmlWriter parentWriter) { const string tableName = "Dialog"; if (!this.inputDatabase.TableExists(tableName)) { return; } using (View view = this.inputDatabase.OpenExecuteView(String.Concat("SELECT * FROM `", tableName, "`"))) { Record record; while (view.Fetch(out record)) { XmlTextWriter writer = this.InitializeXmlTextWriter(Path.GetFullPath(Path.Combine(this.outputFolder, String.Concat("SkuLibrary\\UI\\Dialogs\\", record[(int)MsiInterop.Dialog.Dialog], ".wxs")))); if (this.generateFragments) { // initalize the fragment writer.WriteStartElement("Fragment"); this.core.WriteAttributeString(writer, "Id", record[(int)MsiInterop.Dialog.Dialog]); // initalize the UI element writer.WriteStartElement("UI"); // create reference to fragment parentWriter.WriteStartElement("FragmentRef"); this.core.WriteAttributeString(parentWriter, "Id", record[(int)MsiInterop.Dialog.Dialog]); parentWriter.WriteEndElement(); } // TODO: test this for blowing up when null, error violation of SDK constraint "This must be a non-negative number" int bits = Convert.ToInt32(record[(int)MsiInterop.Dialog.Attributes]) ^ MsiInterop.DialogAttributesInvert; // nullable field string firstControl = record[(int)MsiInterop.Dialog.ControlFirst]; string defaultControl = record[(int)MsiInterop.Dialog.ControlDefault]; string cancelControl = record[(int)MsiInterop.Dialog.ControlCancel]; string str; writer.WriteStartElement(tableName); this.core.WriteAttributeString(writer, "Id", record[(int)MsiInterop.Dialog.Dialog]); if (50 != Convert.ToInt32(record[(int)MsiInterop.Dialog.HCentering])) // non-nullable field { this.core.WriteAttributeString(writer, "X", record[(int)MsiInterop.Dialog.HCentering]); } if (50 != Convert.ToInt32(record[(int)MsiInterop.Dialog.VCentering])) // non-nullable field { this.core.WriteAttributeString(writer, "Y", record[(int)MsiInterop.Dialog.VCentering]); } this.core.WriteAttributeString(writer, "Width", record[(int)MsiInterop.Dialog.Width]); this.core.WriteAttributeString(writer, "Height", record[(int)MsiInterop.Dialog.Height]); str = record[(int)MsiInterop.Dialog.Title]; this.core.WriteAttributeString(writer, "Title", str); if (0 < (bits & MsiInterop.MsidbDialogAttributesModal)) { bits = bits & ~MsiInterop.MsidbDialogAttributesMinimize; } if (0 < (bits & MsiInterop.MsidbDialogAttributesError)) { this.core.WriteAttributeString(writer, "ErrorDialog", "yes"); // can we discover this ourselves? } for (int index = 0; index <= MsiInterop.DialogAttributes.Length; index++) { if (0 < (bits & 1)) { string name = MsiInterop.DialogAttributes[index]; if (null == name || 0 == name.Length) { // TODO: throw an exception - Fail "Unknown attribute at bit position " & index } this.core.WriteAttributeString(writer, name, "yes"); } bits = bits / 2; } Record sqlParams = new Record(2); if (this.inputDatabase.TableExists("Control")) { using (View dialogView = this.inputDatabase.OpenView("SELECT * FROM `Control` WHERE `Dialog_` = ?")) { ArrayList processedControls = new ArrayList(); using (View dialogControlView = this.inputDatabase.OpenView("SELECT * FROM `Control` WHERE `Dialog_` = ? AND `Control` = ?")) { string nextControl = "_WiX_First_Control_Never_Matches_Anything_"; sqlParams.SetString(1, record[(int)MsiInterop.Dialog.Dialog]); while (0 < nextControl.Length && nextControl != firstControl) { if ("_WiX_First_Control_Never_Matches_Anything_" == nextControl) { nextControl = firstControl; } sqlParams.SetString(2, nextControl); dialogControlView.Execute(sqlParams); Record dialogControlRecord; dialogControlView.Fetch(out dialogControlRecord); if (null == dialogControlRecord) { // TODO: throw an exception - Fail "Control " & nextControl & " not found" break; } this.ProcessControl(writer, parentWriter, dialogControlRecord, defaultControl, cancelControl, false); processedControls.Add(nextControl); nextControl = dialogControlRecord[(int)MsiInterop.Control.ControlNext]; } } dialogView.Execute(sqlParams); Record dialogRecord; while (dialogView.Fetch(out dialogRecord)) { string nextControl = dialogRecord[(int)MsiInterop.Control.Control]; if (null != nextControl && nextControl != firstControl && !processedControls.Contains(nextControl)) { this.ProcessControl(writer, parentWriter, dialogRecord, defaultControl, cancelControl, true); } } } } if (this.generateFragments) { // terminate the UI element writer.WriteEndElement(); // terminate the fragment writer.WriteEndElement(); } writer.WriteEndElement(); } } }
/// <summary> /// Fetches the next row in the view. /// </summary> /// <param name="record">Record for recieving the data in the next row of the view.</param> /// <returns>Returns true if there was another record to be fetched and false if there wasn't.</returns> public bool Fetch(out Record record) { if (IntPtr.Zero == this.handle) { throw new ArgumentNullException(); // TODO: come up with a real exception to throw } IntPtr recordHandle; uint error = MsiInterop.MsiViewFetch(this.handle, out recordHandle); if (259 == error) { record = null; return false; } else if (0 != error) { throw new System.Runtime.InteropServices.ExternalException("Failed to fetch record from view", (int)error); } record = new Record(recordHandle); return true; }
/// <summary> /// Process DrLocator table /// </summary> /// <param name="writer">XmlWriter where the Intermediate should persist itself as XML.</param> /// <param name="signature">Enclosing signature context.</param> /// <param name="fileRecord">Enclosing file context.</param> /// <param name="rootLocator">true if this is the root locator.</param> /// <param name="findRMCCP">true if one is processing RMCCP.</param> /// <returns>count of nested directories</returns> private int ProcessDrLocator(XmlWriter writer, string signature, Record fileRecord, bool rootLocator, bool findRMCCP) { const string tableName = "DrLocator"; string parent = ""; string path = ""; string depth = ""; int ndirs = 0; int ret = 0; int i = 0; if (!this.inputDatabase.TableExists(tableName)) { return ret; } string query = String.Concat("SELECT * FROM `", tableName, "` WHERE `Signature_`='", signature, "'"); if (findRMCCP) { query = String.Concat(query, " AND `Parent`='CCP_DRIVE'"); } else { query = String.Concat(query, " AND `Parent`<>'CCP_DRIVE'"); } using (View view = this.inputDatabase.OpenExecuteView(query)) { Record record; while (view.Fetch(out record)) { parent = record[(int)MsiInterop.DrLocator.Parent]; if (0 < parent.Length) { if (!findRMCCP) { ndirs = this.ProcessDrLocator(writer, parent, null, false, findRMCCP); } } path = record[(int)MsiInterop.DrLocator.Path]; depth = record[(int)MsiInterop.DrLocator.Depth]; string key = String.Concat(this.StripModuleId(signature), ".", parent); if (this.directorySearchSignatures.ContainsKey(key)) { writer.WriteStartElement("DirectorySearchRef"); this.core.WriteAttributeString(writer, "Id", this.StripModuleId(signature)); if (0 < parent.Length && 0 == ndirs) { this.core.WriteAttributeString(writer, "Parent", parent); } this.core.WriteAttributeString(writer, "Path", path); } else { this.searchSignatures[this.StripModuleId(signature)] = true; this.directorySearchSignatures[key] = true; writer.WriteStartElement("DirectorySearch"); this.core.WriteAttributeString(writer, "Id", this.StripModuleId(signature)); this.core.WriteAttributeString(writer, "Path", path); this.core.WriteAttributeString(writer, "Depth", depth); } if (rootLocator) { this.PrintFileSearch(writer, fileRecord); for (i = 0; i <= ndirs; i++) { writer.WriteEndElement(); } } else { ret = ndirs + 1; } } } return ret; }
/// <summary> /// Updates a fetched record. /// </summary> /// <param name="type">Type of modification mode.</param> /// <param name="record">Record to be modified.</param> public void Modify(ModifyView type, Record record) { if (IntPtr.Zero == this.handle) { throw new ArgumentNullException(); // TODO: come up with a real exception to throw } uint error = MsiInterop.MsiViewModify(handle, Convert.ToInt32(type), record.InternalHandle); if (0 != error) { throw new System.Runtime.InteropServices.ExternalException("Failed to modify view", (int)error); } }
/// <summary> /// Merges in any modules to the output database. /// </summary> /// <param name="databasePath">Path to database.</param> /// <param name="output">Output that specifies database and modules to merge.</param> /// <remarks>Expects that output's database has already been generated.</remarks> private void MergeModules(string databasePath, Output output) { Debug.Assert(OutputType.Product == output.Type); if (0 == output.Modules.Count) // no work to do { return; } IMsmMerge2 merge = null; bool commit = false; bool logOpen = false; bool databaseOpen = false; bool moduleOpen = false; try { bool foundError = false; MsmMerge msm = new MsmMerge(); merge = (IMsmMerge2)msm; merge.OpenLog(String.Concat(this.tempFiles.BasePath, Path.DirectorySeparatorChar, "merge.log")); logOpen = true; merge.OpenDatabase(databasePath); databaseOpen = true; // process all the merge rows foreach (MergeRow mergeRow in output.Modules) { string mergeModulePath = null; try { mergeModulePath = this.extension.FileResolutionHandler(mergeRow.SourceFile, FileResolutionType.Module); } catch (WixFileNotFoundException wfnfe) { this.OnMessage(WixErrors.BinderExtensionMissingFile(mergeRow.SourceLineNumbers, ErrorLevel.Normal, wfnfe.Message)); foundError = true; continue; } try { merge.OpenModule(mergeModulePath, mergeRow.Language); } catch (COMException ce) { if (-2147023273 == ce.ErrorCode) // 0x80070657 - ERROR_INSTALL_LANGUAGE_UNSUPPORTED { throw new WixUnknownMergeLanguageException(mergeRow.SourceLineNumbers, mergeRow.Id, mergeModulePath, mergeRow.Language, ce); } else { throw; } } moduleOpen = true; ConnectToFeature connection = output.ModulesToFeatures[mergeRow.Id]; if (null == connection) { throw new WixMergeModuleMissingFeatureException(mergeRow.SourceLineNumbers, mergeRow.Id); } string configData = mergeRow.ConfigurationData; if (null != configData) { ConfigurationCallback callback = new ConfigurationCallback(configData); merge.MergeEx(connection.PrimaryFeature, mergeRow.Directory, callback); } else { merge.Merge(connection.PrimaryFeature, mergeRow.Directory); } /* IMsmErrors errorCollection = null; merge.get_Errors(out errorCollection); long count = errorCollection.get_Count(); if (0 < count) { throw new WixMergeFailureException(null, this.tempFiles.BasePath, count, null); } */ foreach (string connectTo in connection.ConnectFeatures) { merge.Connect(connectTo); } // if the module has files and creating layout if (mergeRow.HasFiles && !this.suppressLayout) { string hashedMergeId = mergeRow.Id.GetHashCode().ToString("X4", CultureInfo.InvariantCulture.NumberFormat); // extract the module cabinet, then explode all of the files to a temp directory string moduleCabPath = String.Concat(this.tempFiles.BasePath, Path.DirectorySeparatorChar, hashedMergeId, ".module.cab"); merge.ExtractCAB(moduleCabPath); string mergeIdPath = String.Concat(this.tempFiles.BasePath, Path.DirectorySeparatorChar, "MergeId.", hashedMergeId); Directory.CreateDirectory(mergeIdPath); WixExtractCab extCab = null; try { extCab = new WixExtractCab(); extCab.Extract(moduleCabPath, mergeIdPath); } catch (WixCabExtractionException wce) { COMException comException = wce.InnerException as COMException; foundError = true; if (null != comException && 0x80070002 == unchecked((uint)comException.ErrorCode)) { extCab = null; // Cab doesn't exist, so drop the object. this.OnMessage(WixErrors.CabFileDoesNotExist(moduleCabPath, mergeModulePath, mergeIdPath)); } else { this.OnMessage(WixErrors.CabExtractionFailed(moduleCabPath, mergeModulePath, mergeIdPath)); } } finally { if (null != extCab) { try { extCab.Close(); } catch (WixCabExtractionException) { this.OnMessage(WixErrors.CabClosureFailed(moduleCabPath)); } } } } moduleOpen = false; merge.CloseModule(); } commit = !foundError; // if all seems to have progressed cleanly, feel free to commit the changes to the database } finally { if (moduleOpen) { merge.CloseModule(); } if (databaseOpen) { merge.CloseDatabase(commit); } if (logOpen) { merge.CloseLog(); } } // create a Hashtable of all the suppressed sequence types Hashtable suppressedTableNames = new Hashtable(); if (output.SuppressAdminSequence) { suppressedTableNames[Action.SequenceTypeToString(SequenceType.adminExecute)] = null; suppressedTableNames[Action.SequenceTypeToString(SequenceType.adminUI)] = null; } if (output.SuppressAdvertiseSequence) { suppressedTableNames[Action.SequenceTypeToString(SequenceType.advertiseExecute)] = null; } if (output.SuppressUISequence) { suppressedTableNames[Action.SequenceTypeToString(SequenceType.adminUI)] = null; suppressedTableNames[Action.SequenceTypeToString(SequenceType.installUI)] = null; } using (Database db = new Database(databasePath, OpenDatabase.Direct)) { OutputTable suppressActionOutputTable = output.OutputTables["SuppressAction"]; // suppress individual actions if (null != suppressActionOutputTable) { foreach (OutputRow outputRow in suppressActionOutputTable.OutputRows) { if (db.TableExists((string)outputRow.Row[0])) { Row row = outputRow.Row; string query = String.Format("SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); using (View view = db.OpenExecuteView(query)) { Record record; if (view.Fetch(out record)) { this.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); view.Modify(ModifyView.Delete, record); record.Close(); } } } } } // query for merge module actions in suppressed sequences and drop them foreach (string tableName in suppressedTableNames.Keys) { if (!db.TableExists(tableName)) { continue; } using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) { Record resultRecord; while (view.Fetch(out resultRecord)) { this.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); resultRecord.Close(); } } // drop suppressed sequences using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) { } // delete the validation rows using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) { Record record = new Record(1); record.SetString(1, tableName); view.Execute(record); } } // now update the Attributes column for the files from the Merge Modules using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) { foreach (FileMediaInformation fmi in output.FileMediaInformationCollection) { if (!fmi.IsInModule) { continue; } Record record = new Record(1); record.SetString(1, fmi.File); view.Execute(record); Record recordUpdate; view.Fetch(out recordUpdate); if (null == recordUpdate) { throw new WixMergeFailureException(null, this.tempFiles.BasePath, 1, null); } recordUpdate.SetInteger(1, fmi.Sequence); // update the file attributes to match the compression specified // on the Merge element or on the Package element int attributes = 0; // get the current value if its not null if (!recordUpdate.IsNull(2)) { attributes = recordUpdate.GetInteger(2); } if (FileCompressionValue.Yes == fmi.FileCompression) { attributes |= MsiInterop.MsidbFileAttributesCompressed; } else if (FileCompressionValue.No == fmi.FileCompression) { attributes |= MsiInterop.MsidbFileAttributesNoncompressed; } else // not specified { Debug.Assert(FileCompressionValue.NotSpecified == fmi.FileCompression); // clear any compression bits attributes &= ~MsiInterop.MsidbFileAttributesCompressed; attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; } recordUpdate.SetInteger(2, attributes); view.Modify(ModifyView.Update, recordUpdate); } } db.Commit(); } }