aboutsummaryrefslogtreecommitdiff
path: root/src/tools/Dtf/Inventory/components.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-07-14 15:19:53 -0700
committerRob Mensching <rob@firegiant.com>2022-07-14 16:02:24 -0700
commit229242cf7c328b89b5aa65ed7a04e33c8b93b393 (patch)
treede0a9547e73e46490b0946d6850228d5b30258b8 /src/tools/Dtf/Inventory/components.cs
parentf46ca6a9dce91607ffc9855270dd6998216e1a8b (diff)
downloadwix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.tar.gz
wix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.tar.bz2
wix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.zip
Rename "samples" segment to "tools"
This segment is a bit of a "miscellaneous section" in the WiX repo. As such it has been difficult to name. I originally eschewed the name "tools" because what is in the "wix" segment was once called "tools". However, now that wix.exe is firmly established as the entry point for WiX operations, I've become comfortable with its segment being named "wix". That meant "tools" was again available and "tools" better describes the content of this section.
Diffstat (limited to 'src/tools/Dtf/Inventory/components.cs')
-rw-r--r--src/tools/Dtf/Inventory/components.cs626
1 files changed, 626 insertions, 0 deletions
diff --git a/src/tools/Dtf/Inventory/components.cs b/src/tools/Dtf/Inventory/components.cs
new file mode 100644
index 00000000..b516af46
--- /dev/null
+++ b/src/tools/Dtf/Inventory/components.cs
@@ -0,0 +1,626 @@
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
3using System;
4using System.IO;
5using System.Data;
6using System.Text;
7using System.Collections;
8using System.Collections.Generic;
9using System.Globalization;
10using System.Windows.Forms;
11using Microsoft.Win32;
12using WixToolset.Dtf.WindowsInstaller;
13using View = WixToolset.Dtf.WindowsInstaller.View;
14
15namespace 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}