| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. |
| 2 | |
| 3 | using System; |
| 4 | using System.IO; |
| 5 | using System.Data; |
| 6 | using System.Text; |
| 7 | using System.Collections; |
| 8 | using System.Collections.Generic; |
| 9 | using System.Globalization; |
| 10 | using System.Windows.Forms; |
| 11 | using Microsoft.Win32; |
| 12 | using WixToolset.Dtf.WindowsInstaller; |
| 13 | using View = WixToolset.Dtf.WindowsInstaller.View; |
| 14 | |
| 15 | namespace WixToolset.Dtf.Tools.Inventory |
| 16 | { |
| 17 | /// <summary> |
| 18 | /// Provides inventory data about components of products installed on the system. |
| 19 | /// </summary> |
| 20 | public class ComponentsInventory : IInventoryDataProvider |
| 21 | { |
| 22 | private static object syncRoot = new object(); |
| 23 | |
| 24 | public ComponentsInventory() |
| 25 | { |
| 26 | } |
| 27 | |
| 28 | public string Description |
| 29 | { |
| 30 | get { return "Components of installed products"; } |
| 31 | } |
| 32 | |
| 33 | private Hashtable componentProductsMap; |
| 34 | |
| 35 | public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback) |
| 36 | { |
| 37 | ArrayList nodes = new ArrayList(); |
| 38 | componentProductsMap = new Hashtable(); |
| 39 | foreach(ProductInstallation product in ProductInstallation.AllProducts) |
| 40 | { |
| 41 | string productName = MsiUtils.GetProductName(product.ProductCode); |
| 42 | statusCallback(nodes.Count, String.Format(@"Products\{0}", productName)); |
| 43 | |
| 44 | try |
| 45 | { |
| 46 | IntPtr hWnd = IntPtr.Zero; |
| 47 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); |
| 48 | lock(syncRoot) // Only one Installer session can be active at a time |
| 49 | { |
| 50 | using (Session session = Installer.OpenProduct(product.ProductCode)) |
| 51 | { |
| 52 | statusCallback(nodes.Count, String.Format(@"Products\{0}\Features", productName)); |
| 53 | IList<string> features = session.Database.ExecuteStringQuery("SELECT `Feature` FROM `Feature`"); |
| 54 | string[] featuresArray = new string[features.Count]; |
| 55 | features.CopyTo(featuresArray, 0); |
| 56 | Array.Sort(featuresArray, 0, featuresArray.Length, StringComparer.OrdinalIgnoreCase); |
| 57 | foreach (string feature in featuresArray) |
| 58 | { |
| 59 | nodes.Add(String.Format(@"Products\{0}\Features\{1}", productName, feature)); |
| 60 | } |
| 61 | statusCallback(nodes.Count, String.Format(@"Products\{0}\Components", productName)); |
| 62 | nodes.Add(String.Format(@"Products\{0}\Components", productName)); |
| 63 | IList<string> components = session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`"); |
| 64 | for (int i = 0; i < components.Count; i++) |
| 65 | { |
| 66 | string component = components[i]; |
| 67 | if (component.Length > 0) |
| 68 | { |
| 69 | nodes.Add(String.Format(@"Products\{0}\Components\{1}", productName, component)); |
| 70 | ArrayList sharingProducts = (ArrayList) componentProductsMap[component]; |
| 71 | if (sharingProducts == null) |
| 72 | { |
| 73 | sharingProducts = new ArrayList(); |
| 74 | componentProductsMap[component] = sharingProducts; |
| 75 | } |
| 76 | sharingProducts.Add(product.ProductCode); |
| 77 | } |
| 78 | if (i % 100 == 0) statusCallback(nodes.Count, null); |
| 79 | } |
| 80 | nodes.Add(String.Format(@"Products\{0}\Files", productName)); |
| 81 | nodes.Add(String.Format(@"Products\{0}\Registry", productName)); |
| 82 | statusCallback(nodes.Count, String.Empty); |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | catch(InstallerException) { } |
| 87 | } |
| 88 | statusCallback(nodes.Count, @"Products\...\Components\...\Sharing"); |
| 89 | foreach (DictionaryEntry componentProducts in componentProductsMap) |
| 90 | { |
| 91 | string component = (string) componentProducts.Key; |
| 92 | ArrayList products = (ArrayList) componentProducts.Value; |
| 93 | if(products.Count > 1) |
| 94 | { |
| 95 | foreach(string productCode in products) |
| 96 | { |
| 97 | nodes.Add(String.Format(@"Products\{0}\Components\{1}\Sharing", MsiUtils.GetProductName(productCode), component)); |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | statusCallback(nodes.Count, String.Empty); |
| 102 | return (string[]) nodes.ToArray(typeof(string)); |
| 103 | } |
| 104 | |
| 105 | public bool IsNodeSearchable(string searchRoot, string searchNode) |
| 106 | { |
| 107 | string[] rootPath = searchRoot.Split('\\'); |
| 108 | string[] nodePath = searchNode.Split('\\'); |
| 109 | if(rootPath.Length < 3 && nodePath.Length >= 3 && nodePath[0] == "Products" && nodePath[2] == "Components") |
| 110 | { |
| 111 | // When searching an entire product, don't search the "Components" subtree -- |
| 112 | // it just has duplicate data from the Files and Registry table. And if you |
| 113 | // really want to know about the component, it's only a click away from |
| 114 | // those other tables. |
| 115 | return false; |
| 116 | } |
| 117 | return true; |
| 118 | } |
| 119 | |
| 120 | public DataView GetData(string nodePath) |
| 121 | { |
| 122 | string[] path = nodePath.Split('\\'); |
| 123 | |
| 124 | if(path.Length == 4 && path[0] == "Products" && path[2] == "Features") |
| 125 | { |
| 126 | return GetFeatureComponentsData(MsiUtils.GetProductCode(path[1]), path[3]); |
| 127 | } |
| 128 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Components") |
| 129 | { |
| 130 | return GetProductComponentsData(MsiUtils.GetProductCode(path[1])); |
| 131 | } |
| 132 | else if(path.Length == 4 && path[0] == "Products" && path[2] == "Components") |
| 133 | { |
| 134 | return GetComponentData(MsiUtils.GetProductCode(path[1]), path[3]); |
| 135 | } |
| 136 | else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing") |
| 137 | { |
| 138 | return GetComponentProductsData(path[3]); |
| 139 | } |
| 140 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files") |
| 141 | { |
| 142 | return GetProductFilesData(MsiUtils.GetProductCode(path[1])); |
| 143 | } |
| 144 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry") |
| 145 | { |
| 146 | return GetProductRegistryData(MsiUtils.GetProductCode(path[1])); |
| 147 | } |
| 148 | return null; |
| 149 | } |
| 150 | |
| 151 | public DataView GetComponentData(string productCode, string componentCode) |
| 152 | { |
| 153 | DataTable table = new DataTable("ProductComponentItems"); |
| 154 | table.Locale = CultureInfo.InvariantCulture; |
| 155 | table.Columns.Add("ProductComponentItemsIsKey", typeof(bool)); |
| 156 | table.Columns.Add("ProductComponentItemsKey", typeof(string)); |
| 157 | table.Columns.Add("ProductComponentItemsPath", typeof(string)); |
| 158 | table.Columns.Add("ProductComponentItemsExists", typeof(bool)); |
| 159 | table.Columns.Add("ProductComponentItemsDbVersion", typeof(string)); |
| 160 | table.Columns.Add("ProductComponentItemsInstalledVersion", typeof(string)); |
| 161 | table.Columns.Add("ProductComponentItemsInstalledMatch", typeof(bool)); |
| 162 | try |
| 163 | { |
| 164 | IntPtr hWnd = IntPtr.Zero; |
| 165 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); |
| 166 | lock(syncRoot) // Only one Installer session can be active at a time |
| 167 | { |
| 168 | using(Session session = Installer.OpenProduct(productCode)) |
| 169 | { |
| 170 | session.DoAction("CostInitialize"); |
| 171 | session.DoAction("FileCost"); |
| 172 | session.DoAction("CostFinalize"); |
| 173 | |
| 174 | foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, false)) |
| 175 | { |
| 176 | table.Rows.Add(row); |
| 177 | } |
| 178 | foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, false)) |
| 179 | { |
| 180 | table.Rows.Add(row); |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | return new DataView(table, "", "ProductComponentItemsPath ASC", DataViewRowState.CurrentRows); |
| 185 | } |
| 186 | catch(InstallerException) { } |
| 187 | return null; |
| 188 | } |
| 189 | |
| 190 | private object[][] GetComponentFilesRows(string productCode, string componentCode, Session session, bool includeComponent) |
| 191 | { |
| 192 | ArrayList rows = new ArrayList(); |
| 193 | string componentPath = new ComponentInstallation(componentCode, productCode).Path; |
| 194 | |
| 195 | string componentKey = (string) session.Database.ExecuteScalar( |
| 196 | "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode); |
| 197 | if(componentKey == null) return null; |
| 198 | int attributes = Convert.ToInt32(session.Database.ExecuteScalar( |
| 199 | "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey)); |
| 200 | bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0; |
| 201 | if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath); |
| 202 | string keyPath = (string) session.Database.ExecuteScalar( |
| 203 | "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey); |
| 204 | |
| 205 | using (View view = session.Database.OpenView("SELECT `File`, `FileName`, `Version`, `Language`, " + |
| 206 | "`Attributes` FROM `File` WHERE `Component_` = '{0}'", componentKey)) |
| 207 | { |
| 208 | view.Execute(); |
| 209 | |
| 210 | foreach (Record rec in view) using (rec) |
| 211 | { |
| 212 | string fileKey = (string) rec["File"]; |
| 213 | bool isKey = !registryKeyPath && keyPath == fileKey; |
| 214 | |
| 215 | string dbVersion = (string) rec["Version"]; |
| 216 | bool versionedFile = dbVersion.Length != 0; |
| 217 | if(versionedFile) |
| 218 | { |
| 219 | string language = (string) rec["Language"]; |
| 220 | if(language.Length > 0) |
| 221 | { |
| 222 | dbVersion = dbVersion + " (" + language + ")"; |
| 223 | } |
| 224 | } |
| 225 | else if(session.Database.Tables.Contains("MsiFileHash")) |
| 226 | { |
| 227 | IList<int> hash = session.Database.ExecuteIntegerQuery("SELECT `HashPart1`, `HashPart2`, " + |
| 228 | "`HashPart3`, `HashPart4` FROM `MsiFileHash` WHERE `File_` = '{0}'", fileKey); |
| 229 | if(hash != null && hash.Count == 4) |
| 230 | { |
| 231 | dbVersion = this.GetFileHashString(hash); |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | string filePath = GetLongFileName((string) rec["FileName"]); |
| 236 | bool exists = false; |
| 237 | bool installedMatch = false; |
| 238 | string installedVersion = ""; |
| 239 | if(!registryKeyPath && componentPath.Length > 0) |
| 240 | { |
| 241 | filePath = Path.Combine(componentPath, filePath); |
| 242 | |
| 243 | if(File.Exists(filePath)) |
| 244 | { |
| 245 | exists = true; |
| 246 | if(versionedFile) |
| 247 | { |
| 248 | installedVersion = Installer.GetFileVersion(filePath); |
| 249 | string language = Installer.GetFileLanguage(filePath); |
| 250 | if(language.Length > 0) |
| 251 | { |
| 252 | installedVersion = installedVersion + " (" + language + ")"; |
| 253 | } |
| 254 | } |
| 255 | else |
| 256 | { |
| 257 | int[] hash = new int[4]; |
| 258 | Installer.GetFileHash(filePath, hash); |
| 259 | installedVersion = this.GetFileHashString(hash); |
| 260 | } |
| 261 | installedMatch = installedVersion == dbVersion; |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | object[] row; |
| 266 | if(includeComponent) row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch, componentCode }; |
| 267 | else row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch }; |
| 268 | rows.Add(row); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | return (object[][]) rows.ToArray(typeof(object[])); |
| 273 | } |
| 274 | |
| 275 | private string GetLongFileName(string fileName) |
| 276 | { |
| 277 | string[] fileNames = fileName.Split('|'); |
| 278 | return fileNames.Length == 1? fileNames[0] : fileNames[1]; |
| 279 | } |
| 280 | |
| 281 | private string GetFileHashString(IList<int> hash) |
| 282 | { |
| 283 | return String.Format("{0:X8}{1:X8}{2:X8}{3:X8}", (uint) hash[0], (uint) hash[1], (uint) hash[2], (uint) hash[3]); |
| 284 | } |
| 285 | |
| 286 | private object[][] GetComponentRegistryRows(string productCode, string componentCode, Session session, bool includeComponent) |
| 287 | { |
| 288 | ArrayList rows = new ArrayList(); |
| 289 | string componentPath = new ComponentInstallation(componentCode, productCode).Path; |
| 290 | |
| 291 | string componentKey = (string) session.Database.ExecuteScalar( |
| 292 | "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode); |
| 293 | if(componentKey == null) return null; |
| 294 | int attributes = Convert.ToInt32(session.Database.ExecuteScalar( |
| 295 | "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey)); |
| 296 | bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0; |
| 297 | if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath); |
| 298 | string keyPath = (string) session.Database.ExecuteScalar( |
| 299 | "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey); |
| 300 | |
| 301 | using (View view = session.Database.OpenView("SELECT `Registry`, `Root`, `Key`, `Name`, " + |
| 302 | "`Value` FROM `Registry` WHERE `Component_` = '{0}'", componentKey)) |
| 303 | { |
| 304 | view.Execute(); |
| 305 | |
| 306 | foreach (Record rec in view) using (rec) |
| 307 | { |
| 308 | string regName = (string) rec["Name"]; |
| 309 | if(regName == "-") continue; // Don't list deleted keys |
| 310 | |
| 311 | string regTableKey = (string) rec["Registry"]; |
| 312 | bool isKey = registryKeyPath && keyPath == regTableKey; |
| 313 | string regPath = this.GetRegistryPath(session, (RegistryRoot) Convert.ToInt32(rec["Root"]), |
| 314 | (string) rec["Key"], (string) rec["Name"]); |
| 315 | |
| 316 | string dbValue; |
| 317 | using(Record formatRec = new Record(0)) |
| 318 | { |
| 319 | formatRec[0] = rec["Value"]; |
| 320 | dbValue = session.FormatRecord(formatRec); |
| 321 | } |
| 322 | |
| 323 | string installedValue = this.GetRegistryValue(regPath); |
| 324 | bool exists = installedValue != null; |
| 325 | if(!exists) installedValue = ""; |
| 326 | bool match = installedValue == dbValue; |
| 327 | |
| 328 | object[] row; |
| 329 | if(includeComponent) row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match, componentCode }; |
| 330 | else row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match }; |
| 331 | rows.Add(row); |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | return (object[][]) rows.ToArray(typeof(object[])); |
| 336 | } |
| 337 | |
| 338 | private string GetRegistryPath(Session session, RegistryRoot root, string key, string name) |
| 339 | { |
| 340 | bool allUsers = session.EvaluateCondition("ALLUSERS = 1", true); |
| 341 | string rootName = "????"; |
| 342 | switch(root) |
| 343 | { |
| 344 | case RegistryRoot.LocalMachine : rootName = "HKLM"; break; |
| 345 | case RegistryRoot.CurrentUser : rootName = "HKCU"; break; |
| 346 | case RegistryRoot.Users : rootName = "HKU"; break; |
| 347 | case RegistryRoot.UserOrMachine: rootName = (allUsers ? "HKLM" : "HKCU"); break; |
| 348 | case RegistryRoot.ClassesRoot : rootName = (allUsers ? @"HKLM\Software\Classes" : @"HKCU\Software\Classes"); break; |
| 349 | // TODO: Technically, RegistryRoot.ClassesRoot should be under HKLM on NT4. |
| 350 | } |
| 351 | if(name.Length == 0) name = "(Default)"; |
| 352 | if(name == "+" || name == "*") name = ""; |
| 353 | else name = " : " + name; |
| 354 | using(Record formatRec = new Record(0)) |
| 355 | { |
| 356 | formatRec[0] = String.Format(@"{0}\{1}{2}", rootName, key, name); |
| 357 | return session.FormatRecord(formatRec); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | private string GetRegistryValue(string regPath) |
| 362 | { |
| 363 | string valueName = null; |
| 364 | int iColon = regPath.IndexOf(" : ", StringComparison.Ordinal) + 1; |
| 365 | if(iColon > 0) |
| 366 | { |
| 367 | valueName = regPath.Substring(iColon + 2); |
| 368 | regPath = regPath.Substring(0, iColon - 1); |
| 369 | } |
| 370 | if(valueName == "(Default)") valueName = ""; |
| 371 | |
| 372 | RegistryKey root; |
| 373 | if(regPath.StartsWith(@"HKLM\", StringComparison.Ordinal)) |
| 374 | { |
| 375 | root = Registry.LocalMachine; |
| 376 | regPath = regPath.Substring(5); |
| 377 | } |
| 378 | else if(regPath.StartsWith(@"HKCU\", StringComparison.Ordinal)) |
| 379 | { |
| 380 | root = Registry.CurrentUser; |
| 381 | regPath = regPath.Substring(5); |
| 382 | } |
| 383 | else if(regPath.StartsWith(@"HKU\", StringComparison.Ordinal)) |
| 384 | { |
| 385 | root = Registry.Users; |
| 386 | regPath = regPath.Substring(4); |
| 387 | } |
| 388 | else return null; |
| 389 | |
| 390 | using(RegistryKey regKey = root.OpenSubKey(regPath)) |
| 391 | { |
| 392 | if(regKey != null) |
| 393 | { |
| 394 | if(valueName == null) |
| 395 | { |
| 396 | // Just checking for the existence of the key. |
| 397 | return ""; |
| 398 | } |
| 399 | object value = regKey.GetValue(valueName); |
| 400 | if(value is string[]) |
| 401 | { |
| 402 | value = String.Join("[~]", (string[]) value); |
| 403 | } |
| 404 | else if(value is int) |
| 405 | { |
| 406 | value = "#" + value.ToString(); |
| 407 | } |
| 408 | else if(value is byte[]) |
| 409 | { |
| 410 | byte[] valueBytes = (byte[]) value; |
| 411 | StringBuilder byteString = new StringBuilder("#x"); |
| 412 | for(int i = 0; i < valueBytes.Length; i++) |
| 413 | { |
| 414 | byteString.Append(valueBytes[i].ToString("x2")); |
| 415 | } |
| 416 | value = byteString.ToString(); |
| 417 | } |
| 418 | return (value != null ? value.ToString() : null); |
| 419 | } |
| 420 | } |
| 421 | return null; |
| 422 | } |
| 423 | |
| 424 | public DataView GetProductFilesData(string productCode) |
| 425 | { |
| 426 | DataTable table = new DataTable("ProductFiles"); |
| 427 | table.Locale = CultureInfo.InvariantCulture; |
| 428 | table.Columns.Add("ProductFilesIsKey", typeof(bool)); |
| 429 | table.Columns.Add("ProductFilesKey", typeof(string)); |
| 430 | table.Columns.Add("ProductFilesPath", typeof(string)); |
| 431 | table.Columns.Add("ProductFilesExists", typeof(bool)); |
| 432 | table.Columns.Add("ProductFilesDbVersion", typeof(string)); |
| 433 | table.Columns.Add("ProductFilesInstalledVersion", typeof(string)); |
| 434 | table.Columns.Add("ProductFilesInstalledMatch", typeof(bool)); |
| 435 | table.Columns.Add("ProductFilesComponentID", typeof(string)); |
| 436 | try |
| 437 | { |
| 438 | IntPtr hWnd = IntPtr.Zero; |
| 439 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); |
| 440 | lock(syncRoot) // Only one Installer session can be active at a time |
| 441 | { |
| 442 | using(Session session = Installer.OpenProduct(productCode)) |
| 443 | { |
| 444 | session.DoAction("CostInitialize"); |
| 445 | session.DoAction("FileCost"); |
| 446 | session.DoAction("CostFinalize"); |
| 447 | |
| 448 | foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`")) |
| 449 | { |
| 450 | foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, true)) |
| 451 | { |
| 452 | table.Rows.Add(row); |
| 453 | } |
| 454 | } |
| 455 | } |
| 456 | } |
| 457 | return new DataView(table, "", "ProductFilesPath ASC", DataViewRowState.CurrentRows); |
| 458 | } |
| 459 | catch(InstallerException) { } |
| 460 | return null; |
| 461 | } |
| 462 | |
| 463 | public DataView GetProductRegistryData(string productCode) |
| 464 | { |
| 465 | DataTable table = new DataTable("ProductRegistry"); |
| 466 | table.Locale = CultureInfo.InvariantCulture; |
| 467 | table.Columns.Add("ProductRegistryIsKey", typeof(bool)); |
| 468 | table.Columns.Add("ProductRegistryKey", typeof(string)); |
| 469 | table.Columns.Add("ProductRegistryPath", typeof(string)); |
| 470 | table.Columns.Add("ProductRegistryExists", typeof(bool)); |
| 471 | table.Columns.Add("ProductRegistryDbVersion", typeof(string)); |
| 472 | table.Columns.Add("ProductRegistryInstalledVersion", typeof(string)); |
| 473 | table.Columns.Add("ProductRegistryInstalledMatch", typeof(bool)); |
| 474 | table.Columns.Add("ProductRegistryComponentID", typeof(string)); |
| 475 | try |
| 476 | { |
| 477 | IntPtr hWnd = IntPtr.Zero; |
| 478 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); |
| 479 | lock(syncRoot) // Only one Installer session can be active at a time |
| 480 | { |
| 481 | using(Session session = Installer.OpenProduct(productCode)) |
| 482 | { |
| 483 | session.DoAction("CostInitialize"); |
| 484 | session.DoAction("FileCost"); |
| 485 | session.DoAction("CostFinalize"); |
| 486 | |
| 487 | foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`")) |
| 488 | { |
| 489 | foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, true)) |
| 490 | { |
| 491 | table.Rows.Add(row); |
| 492 | } |
| 493 | } |
| 494 | } |
| 495 | } |
| 496 | return new DataView(table, "", "ProductRegistryPath ASC", DataViewRowState.CurrentRows); |
| 497 | } |
| 498 | catch(InstallerException) { } |
| 499 | return null; |
| 500 | } |
| 501 | |
| 502 | public DataView GetComponentProductsData(string componentCode) |
| 503 | { |
| 504 | DataTable table = new DataTable("ComponentProducts"); |
| 505 | table.Locale = CultureInfo.InvariantCulture; |
| 506 | table.Columns.Add("ComponentProductsProductName", typeof(string)); |
| 507 | table.Columns.Add("ComponentProductsProductCode", typeof(string)); |
| 508 | table.Columns.Add("ComponentProductsComponentPath", typeof(string)); |
| 509 | |
| 510 | if(this.componentProductsMap != null) |
| 511 | { |
| 512 | ArrayList componentProducts = (ArrayList) this.componentProductsMap[componentCode]; |
| 513 | foreach(string productCode in componentProducts) |
| 514 | { |
| 515 | string productName = MsiUtils.GetProductName(productCode); |
| 516 | string componentPath = new ComponentInstallation(componentCode, productCode).Path; |
| 517 | table.Rows.Add(new object[] { productName, productCode, componentPath }); |
| 518 | } |
| 519 | return new DataView(table, "", "ComponentProductsProductName ASC", DataViewRowState.CurrentRows); |
| 520 | } |
| 521 | return null; |
| 522 | } |
| 523 | |
| 524 | public DataView GetProductComponentsData(string productCode) |
| 525 | { |
| 526 | DataTable table = new DataTable("ProductComponents"); |
| 527 | table.Locale = CultureInfo.InvariantCulture; |
| 528 | table.Columns.Add("ProductComponentsComponentName", typeof(string)); |
| 529 | table.Columns.Add("ProductComponentsComponentID", typeof(string)); |
| 530 | table.Columns.Add("ProductComponentsInstallState", typeof(string)); |
| 531 | |
| 532 | try |
| 533 | { |
| 534 | IntPtr hWnd = IntPtr.Zero; |
| 535 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); |
| 536 | lock(syncRoot) // Only one Installer session can be active at a time |
| 537 | { |
| 538 | using(Session session = Installer.OpenProduct(productCode)) |
| 539 | { |
| 540 | session.DoAction("CostInitialize"); |
| 541 | session.DoAction("FileCost"); |
| 542 | session.DoAction("CostFinalize"); |
| 543 | |
| 544 | IList<string> componentsAndIds = session.Database.ExecuteStringQuery( |
| 545 | "SELECT `Component`, `ComponentId` FROM `Component`"); |
| 546 | |
| 547 | for (int i = 0; i < componentsAndIds.Count; i += 2) |
| 548 | { |
| 549 | if(componentsAndIds[i+1] == "Temporary Id") continue; |
| 550 | InstallState compState = session.Components[componentsAndIds[i]].CurrentState; |
| 551 | table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1], |
| 552 | (compState == InstallState.Advertised ? "Advertised" : compState.ToString())}); |
| 553 | } |
| 554 | } |
| 555 | } |
| 556 | return new DataView(table, "", "ProductComponentsComponentName ASC", DataViewRowState.CurrentRows); |
| 557 | } |
| 558 | catch(InstallerException) { } |
| 559 | return null; |
| 560 | } |
| 561 | |
| 562 | public DataView GetFeatureComponentsData(string productCode, string feature) |
| 563 | { |
| 564 | DataTable table = new DataTable("ProductFeatureComponents"); |
| 565 | table.Locale = CultureInfo.InvariantCulture; |
| 566 | table.Columns.Add("ProductFeatureComponentsComponentName", typeof(string)); |
| 567 | table.Columns.Add("ProductFeatureComponentsComponentID", typeof(string)); |
| 568 | |
| 569 | try |
| 570 | { |
| 571 | IntPtr hWnd = IntPtr.Zero; |
| 572 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); |
| 573 | lock(syncRoot) // Only one Installer session can be active at a time |
| 574 | { |
| 575 | using(Session session = Installer.OpenProduct(productCode)) |
| 576 | { |
| 577 | IList<string> componentsAndIds = session.Database.ExecuteStringQuery( |
| 578 | "SELECT `FeatureComponents`.`Component_`, " + |
| 579 | "`Component`.`ComponentId` FROM `FeatureComponents`, `Component` " + |
| 580 | "WHERE `FeatureComponents`.`Component_` = `Component`.`Component` " + |
| 581 | "AND `FeatureComponents`.`Feature_` = '{0}'", feature); |
| 582 | for (int i = 0; i < componentsAndIds.Count; i += 2) |
| 583 | { |
| 584 | table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1] }); |
| 585 | } |
| 586 | } |
| 587 | } |
| 588 | return new DataView(table, "", "ProductFeatureComponentsComponentName ASC", DataViewRowState.CurrentRows); |
| 589 | } |
| 590 | catch(InstallerException) { } |
| 591 | return null; |
| 592 | } |
| 593 | |
| 594 | public string GetLink(string nodePath, DataRow row) |
| 595 | { |
| 596 | string[] path = nodePath.Split('\\'); |
| 597 | |
| 598 | if(path.Length == 3 && path[0] == "Products" && path[2] == "Components") |
| 599 | { |
| 600 | string component = (string) row["ProductComponentsComponentID"]; |
| 601 | return String.Format(@"Products\{0}\Components\{1}", path[1], component); |
| 602 | } |
| 603 | else if(path.Length == 4 && path[0] == "Products" && path[2] == "Features") |
| 604 | { |
| 605 | string component = (string) row["ProductFeatureComponentsComponentID"]; |
| 606 | return String.Format(@"Products\{0}\Components\{1}", path[1], component); |
| 607 | } |
| 608 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files") |
| 609 | { |
| 610 | string component = (string) row["ProductFilesComponentID"]; |
| 611 | return String.Format(@"Products\{0}\Components\{1}", path[1], component); |
| 612 | } |
| 613 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry") |
| 614 | { |
| 615 | string component = (string) row["ProductRegistryComponentID"]; |
| 616 | return String.Format(@"Products\{0}\Components\{1}", path[1], component); |
| 617 | } |
| 618 | else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing") |
| 619 | { |
| 620 | string product = (string) row["ComponentProductsProductCode"]; |
| 621 | return String.Format(@"Products\{0}\Components\{1}", MsiUtils.GetProductName(product), path[3]); |
| 622 | } |
| 623 | return null; |
| 624 | } |
| 625 | } |
| 626 | } |