/// <summary> /// Creates a list of <see cref="MsiFile"/> objects from the specified database. /// </summary> public static MsiFile[] CreateMsiFilesFromMSI(Database msidb) { TableRow[] rows = TableRow.GetRowsFromTable(msidb, "File"); // do some prep work to cache values from MSI for finding directories later... MsiDirectory[] rootDirectories; MsiDirectory[] allDirectories; MsiDirectory.GetMsiDirectories(msidb, out rootDirectories, out allDirectories); //find the target directory for each by reviewing the Component Table TableRow[] components = TableRow.GetRowsFromTable(msidb, "Component"); //Component table: http://msdn.microsoft.com/en-us/library/aa368007(v=vs.85).aspx //build a table of components keyed by it's "Component" column value Hashtable componentsByComponentTable = new Hashtable(); foreach (TableRow component in components) { componentsByComponentTable[component.GetString("Component")] = component; } ArrayList /*<MsiFile>*/ files = new ArrayList(rows.Length); foreach (TableRow row in rows) { MsiFile file = new MsiFile(); string fileName = row.GetString("FileName"); string[] split = fileName.Split('|'); file.ShortFileName = split[0]; if (split.Length > 1) { file.LongFileName = split[1]; } else { file.LongFileName = split[0]; } file.File = row.GetString("File"); file.FileSize = row.GetInt32("FileSize"); file.Version = row.GetString("Version"); file.Component = row.GetString("Component_"); file.Directory = GetDirectoryForFile(file, allDirectories, componentsByComponentTable); files.Add(file); } return((MsiFile[])files.ToArray(typeof(MsiFile))); }
private static MsiDirectory GetDirectoryForFile(MsiFile file, MsiDirectory[] allDirectories, IDictionary componentsByComponentTable) { // get the component for the file TableRow componentRow = componentsByComponentTable[file.Component] as TableRow; if (componentRow == null) { Debug.Assert(false, "File '{0}' has no component entry.", file.LongFileName); return(null); } // found component, get the directory: string componentDirectory = componentRow.GetString("Directory_"); MsiDirectory directory = FindDirectoryByDirectoryKey(allDirectories, componentDirectory); if (directory != null) { //Trace.WriteLine(string.Format("Directory for '{0}' is '{1}'.", file.LongFileName, directory.GetPath())); } else { Debug.Fail(string.Format("directory not found for file '{0}'.", file.LongFileName)); } return(directory); }
/// <summary> /// Creates a list of <see cref="MsiDirectory"/> objects from the specified database. /// </summary> /// <param name="allDirectories">All directories in the table.</param> /// <param name="msidb">The databse to get directories from.</param> /// <param name="rootDirectories"> /// Only the root directories (those with no parent). Use <see cref="MsiDirectory.Children"/> to traverse the rest of the directories. /// </param> public static void GetMsiDirectories(Database msidb, out MsiDirectory[] rootDirectories, out MsiDirectory[] allDirectories) { TableRow[] rows = TableRow.GetRowsFromTable(msidb, "Directory"); Hashtable directoriesByDirID = new Hashtable(); foreach (TableRow row in rows) { MsiDirectory directory = new MsiDirectory(); directory._defaultDir = row.GetString("DefaultDir"); if (directory._defaultDir != null && directory._defaultDir.Length > 0) { string[] split = directory._defaultDir.Split('|'); directory._shortName = split[0]; if (split.Length > 1) { directory._targetName = split[1]; } else { directory._targetName = split[0]; } //Semi colons can delmit the "target" and "sorce" names of the directory in DefaultDir, so we're going to use the Target here (in looking at MSI files, I found Target seems most meaningful. #region MSDN Docs on this Table /* From: http://msdn.microsoft.com/en-us/library/aa368295%28VS.85%29.aspx * The DefaultDir column contains the directory's name (localizable)under the parent directory. * By default, this is the name of both the target and source directories. * To specify different source and target directory names, separate the target and source names with a colon as follows: [targetname]:[sourcename]. * If the value of the Directory_Parent column is null or is equal to the Directory column, the DefaultDir column specifies the name of a root source directory. * For a non-root source directory, a period (.) entered in the DefaultDir column for the source directory name or the target directory name indicates the directory should be located in its parent directory without a subdirectory. * The directory names in this column may be formatted as short filename | long filename pairs. */ #endregion split = directory._shortName.Split(':'); if (split.Length > 1) { //semicolon present directory._shortName = split[0]; } split = directory._targetName.Split(':'); if (split.Length > 1) { //semicolon present directory._targetName = split[0]; directory._sourceName = split[1]; } else { directory._sourceName = directory._targetName; } } directory._directory = row.GetString("Directory"); directory._directoryParent = row.GetString("Directory_Parent"); directoriesByDirID.Add(directory.Directory, directory); } // Adding directory to the table if it is specified as parent, but missing in the directories database. // This is workaround in case directories database is modify in runtime. var parentsList = directoriesByDirID.Values.OfType <MsiDirectory>().Select(v => v.DirectoryParent).Distinct().ToList(); foreach (var parent in parentsList) { if (!string.IsNullOrEmpty(parent) && !directoriesByDirID.ContainsKey(parent)) { MsiDirectory directory = new MsiDirectory() { _directoryParent = "", _directory = parent, _targetName = parent }; directoriesByDirID.Add(directory.Directory, directory); } } //Now we have all directories in the table, create a structure for them based on their parents. ArrayList rootDirectoriesList = new ArrayList(); foreach (MsiDirectory dir in directoriesByDirID.Values) { // If the value of the Directory_Parent column is null... var isRoot = string.IsNullOrEmpty(dir.DirectoryParent); // ...or is equal to the Directory column, the DefaultDir column specifies the name of a root source directory. - https://msdn.microsoft.com/en-us/library/windows/desktop/aa368295(v=vs.85).aspx if (!isRoot && string.Equals(dir.Directory, dir.DirectoryParent, StringComparison.InvariantCulture)) { isRoot = true; } if (isRoot) { rootDirectoriesList.Add(dir); continue; } MsiDirectory parent = directoriesByDirID[dir.DirectoryParent] as MsiDirectory; dir._parent = parent; parent._children.Add(dir); } // return the values: rootDirectories = (MsiDirectory[])rootDirectoriesList.ToArray(typeof(MsiDirectory)); MsiDirectory[] allDirectoriesLocal = new MsiDirectory[directoriesByDirID.Values.Count]; directoriesByDirID.Values.CopyTo(allDirectoriesLocal, 0); allDirectories = allDirectoriesLocal; }