summaryrefslogtreecommitdiff
path: root/src/tools/Dtf/Inventory
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
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')
-rw-r--r--src/tools/Dtf/Inventory/Columns.resx252
-rw-r--r--src/tools/Dtf/Inventory/Features.cs107
-rw-r--r--src/tools/Dtf/Inventory/IInventoryDataProvider.cs67
-rw-r--r--src/tools/Dtf/Inventory/Inventory.cs1231
-rw-r--r--src/tools/Dtf/Inventory/Inventory.csproj42
-rw-r--r--src/tools/Dtf/Inventory/Inventory.icobin0 -> 4710 bytes
-rw-r--r--src/tools/Dtf/Inventory/Inventory.resx265
-rw-r--r--src/tools/Dtf/Inventory/components.cs626
-rw-r--r--src/tools/Dtf/Inventory/msiutils.cs46
-rw-r--r--src/tools/Dtf/Inventory/patches.cs227
-rw-r--r--src/tools/Dtf/Inventory/products.cs145
-rw-r--r--src/tools/Dtf/Inventory/xp.manifest15
12 files changed, 3023 insertions, 0 deletions
diff --git a/src/tools/Dtf/Inventory/Columns.resx b/src/tools/Dtf/Inventory/Columns.resx
new file mode 100644
index 00000000..cfeb11e3
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Columns.resx
@@ -0,0 +1,252 @@
1<?xml version="1.0" encoding="utf-8"?>
2<root>
3 <!--
4 Microsoft ResX Schema
5
6 Version 2.0
7
8 The primary goals of this format is to allow a simple XML format
9 that is mostly human readable. The generation and parsing of the
10 various data types are done through the TypeConverter classes
11 associated with the data types.
12
13 Example:
14
15 ... ado.net/XML headers & schema ...
16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 <resheader name="version">2.0</resheader>
18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 <value>[base64 mime encoded serialized .NET Framework object]</value>
24 </data>
25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27 <comment>This is a comment</comment>
28 </data>
29
30 There are any number of "resheader" rows that contain simple
31 name/value pairs.
32
33 Each data row contains a name, and value. The row also contains a
34 type or mimetype. Type corresponds to a .NET class that support
35 text/value conversion through the TypeConverter architecture.
36 Classes that don't support this are serialized and stored with the
37 mimetype set.
38
39 The mimetype is used for serialized objects, and tells the
40 ResXResourceReader how to depersist the object. This is currently not
41 extensible. For a given mimetype the value must be set accordingly:
42
43 Note - application/x-microsoft.net.object.binary.base64 is the format
44 that the ResXResourceWriter will generate, however the reader can
45 read any of the formats listed below.
46
47 mimetype: application/x-microsoft.net.object.binary.base64
48 value : The object must be serialized with
49 : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50 : and then encoded with base64 encoding.
51
52 mimetype: application/x-microsoft.net.object.soap.base64
53 value : The object must be serialized with
54 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55 : and then encoded with base64 encoding.
56
57 mimetype: application/x-microsoft.net.object.bytearray.base64
58 value : The object must be serialized into a byte array
59 : using a System.ComponentModel.TypeConverter
60 : and then encoded with base64 encoding.
61 -->
62 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63 <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64 <xsd:element name="root" msdata:IsDataSet="true">
65 <xsd:complexType>
66 <xsd:choice maxOccurs="unbounded">
67 <xsd:element name="metadata">
68 <xsd:complexType>
69 <xsd:sequence>
70 <xsd:element name="value" type="xsd:string" minOccurs="0" />
71 </xsd:sequence>
72 <xsd:attribute name="name" use="required" type="xsd:string" />
73 <xsd:attribute name="type" type="xsd:string" />
74 <xsd:attribute name="mimetype" type="xsd:string" />
75 <xsd:attribute ref="xml:space" />
76 </xsd:complexType>
77 </xsd:element>
78 <xsd:element name="assembly">
79 <xsd:complexType>
80 <xsd:attribute name="alias" type="xsd:string" />
81 <xsd:attribute name="name" type="xsd:string" />
82 </xsd:complexType>
83 </xsd:element>
84 <xsd:element name="data">
85 <xsd:complexType>
86 <xsd:sequence>
87 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89 </xsd:sequence>
90 <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93 <xsd:attribute ref="xml:space" />
94 </xsd:complexType>
95 </xsd:element>
96 <xsd:element name="resheader">
97 <xsd:complexType>
98 <xsd:sequence>
99 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100 </xsd:sequence>
101 <xsd:attribute name="name" type="xsd:string" use="required" />
102 </xsd:complexType>
103 </xsd:element>
104 </xsd:choice>
105 </xsd:complexType>
106 </xsd:element>
107 </xsd:schema>
108 <resheader name="resmimetype">
109 <value>text/microsoft-resx</value>
110 </resheader>
111 <resheader name="version">
112 <value>2.0</value>
113 </resheader>
114 <resheader name="reader">
115 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116 </resheader>
117 <resheader name="writer">
118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119 </resheader>
120 <data name="ProductsProductName" xml:space="preserve">
121 <value>Product Name,250</value>
122 </data>
123 <data name="ProductsProductCode" xml:space="preserve">
124 <value>Product Code,250</value>
125 </data>
126 <data name="ProductPropertiesProperty" xml:space="preserve">
127 <value>Property,100</value>
128 </data>
129 <data name="ProductPropertiesValue" xml:space="preserve">
130 <value>Value,300</value>
131 </data>
132 <data name="ProductFeaturesFeatureTitle" xml:space="preserve">
133 <value>Feature Title,230</value>
134 </data>
135 <data name="ProductFeaturesFeatureName" xml:space="preserve">
136 <value>Feature,200</value>
137 </data>
138 <data name="ProductFeaturesInstallState" xml:space="preserve">
139 <value>Install State,70</value>
140 </data>
141 <data name="ProductFeatureComponentsComponentName" xml:space="preserve">
142 <value>Component,250</value>
143 </data>
144 <data name="ProductFeatureComponentsComponentID" xml:space="preserve">
145 <value>Component ID,250</value>
146 </data>
147 <data name="ProductComponentsComponentName" xml:space="preserve">
148 <value>Component,180</value>
149 </data>
150 <data name="ProductComponentsComponentID" xml:space="preserve">
151 <value>Component ID,250</value>
152 </data>
153 <data name="ProductComponentsInstallState" xml:space="preserve">
154 <value>Install State,70</value>
155 </data>
156 <data name="ComponentProductsProductName" xml:space="preserve">
157 <value>Product Name,250</value>
158 </data>
159 <data name="ComponentProductsProductCode" xml:space="preserve">
160 <value>Product Code,250</value>
161 </data>
162 <data name="ComponentProductsComponentPath" xml:space="preserve">
163 <value>Component Path,300</value>
164 </data>
165 <data name="ProductComponentItemsIsKey" xml:space="preserve">
166 <value>Key,35</value>
167 </data>
168 <data name="ProductComponentItemsKey" xml:space="preserve">
169 <value>Name,250</value>
170 </data>
171 <data name="ProductComponentItemsPath" xml:space="preserve">
172 <value>Install Path,350</value>
173 </data>
174 <data name="ProductComponentItemsExists" xml:space="preserve">
175 <value>Exists,40</value>
176 </data>
177 <data name="ProductComponentItemsDbVersion" xml:space="preserve">
178 <value>Version in Database,100</value>
179 </data>
180 <data name="ProductComponentItemsInstalledVersion" xml:space="preserve">
181 <value>Version Installed,100</value>
182 </data>
183 <data name="ProductComponentItemsInstalledMatch" xml:space="preserve">
184 <value>Match,40</value>
185 </data>
186 <data name="ProductFilesIsKey" xml:space="preserve">
187 <value>Key,35</value>
188 </data>
189 <data name="ProductFilesKey" xml:space="preserve">
190 <value>Name,250</value>
191 </data>
192 <data name="ProductFilesPath" xml:space="preserve">
193 <value>Install Path,350</value>
194 </data>
195 <data name="ProductFilesExists" xml:space="preserve">
196 <value>Exists,40</value>
197 </data>
198 <data name="ProductFilesDbVersion" xml:space="preserve">
199 <value>Version in Database,120</value>
200 </data>
201 <data name="ProductFilesInstalledVersion" xml:space="preserve">
202 <value>Version Installed,120</value>
203 </data>
204 <data name="ProductFilesInstalledMatch" xml:space="preserve">
205 <value>Match,40</value>
206 </data>
207 <data name="ProductFilesComponentID" xml:space="preserve">
208 <value>Component ID,250</value>
209 </data>
210 <data name="ProductRegistryIsKey" xml:space="preserve">
211 <value>Key,35</value>
212 </data>
213 <data name="ProductRegistryKey" xml:space="preserve">
214 <value>Name,250</value>
215 </data>
216 <data name="ProductRegistryPath" xml:space="preserve">
217 <value>Install Path,350</value>
218 </data>
219 <data name="ProductRegistryExists" xml:space="preserve">
220 <value>Exists,40</value>
221 </data>
222 <data name="ProductRegistryDbVersion" xml:space="preserve">
223 <value>Value in Database,120</value>
224 </data>
225 <data name="ProductRegistryInstalledVersion" xml:space="preserve">
226 <value>Value Installed,120</value>
227 </data>
228 <data name="ProductRegistryInstalledMatch" xml:space="preserve">
229 <value>Match,40</value>
230 </data>
231 <data name="ProductRegistryComponentID" xml:space="preserve">
232 <value>Component ID,250</value>
233 </data>
234 <data name="PatchesPatchCode" xml:space="preserve">
235 <value>Patch Code,250</value>
236 </data>
237 <data name="ProductPatchesPatchCode" xml:space="preserve">
238 <value>Patch Code,250</value>
239 </data>
240 <data name="PatchPropertiesProperty" xml:space="preserve">
241 <value>Property,130</value>
242 </data>
243 <data name="PatchPropertiesValue" xml:space="preserve">
244 <value>Value,360</value>
245 </data>
246 <data name="PatchTargetsProductName" xml:space="preserve">
247 <value>Product Name,360</value>
248 </data>
249 <data name="PatchTargetsProductCode" xml:space="preserve">
250 <value>Product Code,360</value>
251 </data>
252</root> \ No newline at end of file
diff --git a/src/tools/Dtf/Inventory/Features.cs b/src/tools/Dtf/Inventory/Features.cs
new file mode 100644
index 00000000..9d2747ba
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Features.cs
@@ -0,0 +1,107 @@
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.Collections;
7using System.Collections.Generic;
8using System.Globalization;
9using System.Windows.Forms;
10using WixToolset.Dtf.WindowsInstaller;
11
12namespace WixToolset.Dtf.Tools.Inventory
13{
14 /// <summary>
15 /// Provides inventory data about features of products installed on the system.
16 /// </summary>
17 public class FeaturesInventory : IInventoryDataProvider
18 {
19 private static object syncRoot = new object();
20
21 public FeaturesInventory()
22 {
23 }
24
25 public string Description
26 {
27 get { return "Features of installed products"; }
28 }
29
30 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
31 {
32 statusCallback(0, @"Products\...\Features");
33 ArrayList nodes = new ArrayList();
34 foreach (ProductInstallation product in ProductInstallation.AllProducts)
35 {
36 nodes.Add(String.Format(@"Products\{0}\Features", MsiUtils.GetProductName(product.ProductCode)));
37 }
38 statusCallback(nodes.Count, String.Empty);
39 return (string[]) nodes.ToArray(typeof(string));
40 }
41
42 public bool IsNodeSearchable(string searchRoot, string searchNode)
43 {
44 return true;
45 }
46
47 public DataView GetData(string nodePath)
48 {
49 string[] path = nodePath.Split('\\');
50
51 if(path.Length == 3 && path[0] == "Products" && path[2] == "Features")
52 {
53 return GetProductFeaturesData(MsiUtils.GetProductCode(path[1]));
54 }
55 return null;
56 }
57
58 public DataView GetProductFeaturesData(string productCode)
59 {
60 DataTable table = new DataTable("ProductFeatures");
61 table.Locale = CultureInfo.InvariantCulture;
62 table.Columns.Add("ProductFeaturesFeatureTitle", typeof(string));
63 table.Columns.Add("ProductFeaturesFeatureName", typeof(string));
64 table.Columns.Add("ProductFeaturesInstallState", typeof(string));
65
66 try
67 {
68 IntPtr hWnd = IntPtr.Zero;
69 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
70 lock(syncRoot) // Only one Installer session can be active at a time
71 {
72 using(Session session = Installer.OpenProduct(productCode))
73 {
74 session.DoAction("CostInitialize");
75 session.DoAction("FileCost");
76 session.DoAction("CostFinalize");
77
78 IList<string> featuresAndTitles = session.Database.ExecuteStringQuery(
79 "SELECT `Title`, `Feature` FROM `Feature`");
80
81 for(int i = 0; i < featuresAndTitles.Count; i += 2)
82 {
83 InstallState featureState = session.Features[featuresAndTitles[i + 1]].CurrentState;
84 table.Rows.Add(new object[] { featuresAndTitles[i], featuresAndTitles[i+1],
85 (featureState == InstallState.Advertised ? "Advertised" : featureState.ToString()) });
86 }
87 }
88 }
89 return new DataView(table, "", "ProductFeaturesFeatureTitle ASC", DataViewRowState.CurrentRows);
90 }
91 catch(InstallerException) { }
92 catch(IOException) { }
93 return null;
94 }
95
96 public string GetLink(string nodePath, DataRow row)
97 {
98 string[] path = nodePath.Split('\\');
99
100 if(path.Length == 3 && path[0] == "Products" && path[2] == "Features")
101 {
102 return String.Format(@"Products\{0}\Features\{1}", path[1], row["ProductFeaturesFeatureName"]);
103 }
104 return null;
105 }
106 }
107}
diff --git a/src/tools/Dtf/Inventory/IInventoryDataProvider.cs b/src/tools/Dtf/Inventory/IInventoryDataProvider.cs
new file mode 100644
index 00000000..affcb358
--- /dev/null
+++ b/src/tools/Dtf/Inventory/IInventoryDataProvider.cs
@@ -0,0 +1,67 @@
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.Data;
5
6namespace WixToolset.Dtf.Tools.Inventory
7{
8 /// <summary>
9 /// Reports the total number of items loaded so far by <see cref="IInventoryDataProvider.GetNodes"/>.
10 /// </summary>
11 public delegate void InventoryDataLoadStatusCallback(int itemsLoaded, string currentNode);
12
13 /// <summary>
14 /// Inventory data providers implement this interface to provide a particular type of data.
15 /// Implementors must provide a parameterless constructor.
16 /// </summary>
17 public interface IInventoryDataProvider
18 {
19 /// <summary>
20 /// Gets a description of the data provided. This description allows
21 /// the user to choose what type of data to gather.
22 /// </summary>
23 string Description { get; }
24
25 /// <summary>
26 /// Gets the paths of all nodes for which this object provides data.
27 /// </summary>
28 /// <param name="statusCallback">Callback for reporting status.
29 /// The callback should not necessarily be invoked for every individual
30 /// node loaded, rather only every significant chunk.</param>
31 /// <returns>An array of node paths. The parts of the node paths
32 /// are delimited by backslashes (\).</returns>
33 string[] GetNodes(InventoryDataLoadStatusCallback statusCallback);
34
35 /// <summary>
36 /// When related nodes of a tree consist of duplicate data, it's
37 /// inefficient to search them all. This method indicates which
38 /// nodes should be search and which should be ignored.
39 /// </summary>
40 /// <param name="searchRoot">Root node of the subtree-search.</param>
41 /// <param name="searchNode">Node which may or may not be searched.</param>
42 /// <returns>True if the node should be searched, false otherwise.</returns>
43 bool IsNodeSearchable(string searchRoot, string searchNode);
44
45 /// <summary>
46 /// Gets the data for a particular node.
47 /// </summary>
48 /// <param name="nodePath">Path of the node for which data is requested.
49 /// This is one of the paths returned by <see cref="GetNodes"/>.</param>
50 /// <returns>DataView of a table filled with data, or null if data is
51 /// not available.</returns>
52 DataView GetData(string nodePath);
53
54 /// <summary>
55 /// Gets the path of another node which provides more details about
56 /// a particular data row.
57 /// </summary>
58 /// <param name="nodePath">Path of the node containing the data
59 /// row being queried.</param>
60 /// <param name="row">Data row being queried.</param>
61 /// <returns>Path to another node. This is not necessarily
62 /// one of the nodes returned by <see cref="GetNodes"/>. If the
63 /// node path is unknown, it will be ignored. This method may
64 /// return null if there is no detail node for the row.</returns>
65 string GetLink(string nodePath, DataRow row);
66 }
67}
diff --git a/src/tools/Dtf/Inventory/Inventory.cs b/src/tools/Dtf/Inventory/Inventory.cs
new file mode 100644
index 00000000..07735086
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Inventory.cs
@@ -0,0 +1,1231 @@
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.Drawing;
6using System.Collections;
7using System.ComponentModel;
8using System.Diagnostics.CodeAnalysis;
9using System.Windows.Forms;
10using System.Globalization;
11using System.Reflection;
12using System.Resources;
13using System.Threading;
14using System.Security.Permissions;
15using System.Data;
16
17
18[assembly: AssemblyDescription("Shows a hierarchical, relational, searchable " +
19 " view of all of the product, feature, component, file, and patch data managed " +
20 "by MSI, for all products installed on the system.")]
21
22[assembly: SecurityPermission(SecurityAction.RequestMinimum, UnmanagedCode=true)]
23
24
25namespace WixToolset.Dtf.Tools.Inventory
26{
27 public class Inventory : System.Windows.Forms.Form
28 {
29 [STAThread]
30 public static void Main()
31 {
32 if (WixToolset.Dtf.WindowsInstaller.Installer.Version < new Version(3, 0))
33 {
34 MessageBox.Show("This application requires Windows Installer version 3.0 or later.",
35 "Inventory", MessageBoxButtons.OK, MessageBoxIcon.Error);
36 return;
37 }
38
39 Application.Run(new Inventory());
40 }
41
42 private IInventoryDataProvider[] dataProviders;
43 private Hashtable dataProviderMap;
44 private Hashtable data;
45 private ArrayList tablesLoading;
46 private bool searching;
47 private bool stopSearch;
48 private bool navigating;
49 private string continueSearchRoot;
50 private string continueSearchPath;
51 private DataGridCell continueSearchCell;
52 private DataGridCell continueSearchEndCell;
53 private bool mouseOverGridLink = false;
54 private Stack historyBack;
55 private Stack historyForward;
56 private Stack cellHistoryBack;
57 private Stack cellHistoryForward;
58 private static readonly DataGridCell anyCell = new DataGridCell(-1,-1);
59 private static readonly DataGridCell zeroCell = new DataGridCell(0,0);
60 private static object syncRoot = new object();
61
62 private System.Windows.Forms.DataGrid dataGrid;
63 private System.Windows.Forms.TreeView treeView;
64 private System.Windows.Forms.Panel toolPanel;
65 private System.Windows.Forms.Splitter splitter;
66 private System.Windows.Forms.Panel dataPanel;
67 private System.Windows.Forms.Button backButton;
68 private System.Windows.Forms.Button forwardButton;
69 private System.Windows.Forms.Button findButton;
70 private System.Windows.Forms.TextBox findTextBox;
71 private System.Windows.Forms.Button refreshButton;
72 private System.Windows.Forms.Button findStopButton;
73 private System.Windows.Forms.CheckBox searchTreeCheckBox;
74 private System.Windows.Forms.ToolTip gridLinkTip;
75 private System.ComponentModel.IContainer components;
76
77 public Inventory()
78 {
79 InitializeComponent();
80
81 this.gridLinkTip.InitialDelay = 0;
82 this.gridLinkTip.ReshowDelay = 0;
83
84 this.dataProviderMap = new Hashtable();
85 this.data = new Hashtable();
86 this.tablesLoading = new ArrayList();
87 this.historyBack = new Stack();
88 this.historyForward = new Stack();
89 this.cellHistoryBack = new Stack();
90 this.cellHistoryForward = new Stack();
91 }
92
93 protected override void Dispose(bool disposing)
94 {
95 if(disposing)
96 {
97 if(components != null)
98 {
99 components.Dispose();
100 }
101 }
102 base.Dispose(disposing);
103 }
104
105 #region Windows Form Designer generated code
106 /// <summary>
107 /// Required method for Designer support - do not modify
108 /// the contents of this method with the code editor.
109 /// </summary>
110 private void InitializeComponent()
111 {
112 this.components = new System.ComponentModel.Container();
113 this.dataGrid = new System.Windows.Forms.DataGrid();
114 this.treeView = new System.Windows.Forms.TreeView();
115 this.toolPanel = new System.Windows.Forms.Panel();
116 this.findStopButton = new System.Windows.Forms.Button();
117 this.findButton = new System.Windows.Forms.Button();
118 this.searchTreeCheckBox = new System.Windows.Forms.CheckBox();
119 this.findTextBox = new System.Windows.Forms.TextBox();
120 this.refreshButton = new System.Windows.Forms.Button();
121 this.forwardButton = new System.Windows.Forms.Button();
122 this.backButton = new System.Windows.Forms.Button();
123 this.dataPanel = new System.Windows.Forms.Panel();
124 this.splitter = new System.Windows.Forms.Splitter();
125 this.gridLinkTip = new System.Windows.Forms.ToolTip(this.components);
126 ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).BeginInit();
127 this.toolPanel.SuspendLayout();
128 this.dataPanel.SuspendLayout();
129 this.SuspendLayout();
130 //
131 // dataGrid
132 //
133 this.dataGrid.DataMember = "";
134 this.dataGrid.Dock = System.Windows.Forms.DockStyle.Fill;
135 this.dataGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText;
136 this.dataGrid.Location = new System.Drawing.Point(230, 0);
137 this.dataGrid.Name = "dataGrid";
138 this.dataGrid.ReadOnly = true;
139 this.dataGrid.SelectionBackColor = System.Drawing.SystemColors.Highlight;
140 this.dataGrid.Size = new System.Drawing.Size(562, 432);
141 this.dataGrid.TabIndex = 1;
142 this.dataGrid.KeyDown += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyDown);
143 this.dataGrid.MouseDown += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseDown);
144 this.dataGrid.KeyUp += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyUp);
145 this.dataGrid.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseMove);
146 this.dataGrid.MouseLeave += new System.EventHandler(this.dataGrid_MouseLeave);
147 //
148 // treeView
149 //
150 this.treeView.Dock = System.Windows.Forms.DockStyle.Left;
151 this.treeView.HideSelection = false;
152 this.treeView.ImageIndex = -1;
153 this.treeView.Location = new System.Drawing.Point(0, 0);
154 this.treeView.Name = "treeView";
155 this.treeView.SelectedImageIndex = -1;
156 this.treeView.Size = new System.Drawing.Size(224, 432);
157 this.treeView.TabIndex = 0;
158 this.treeView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyDown);
159 this.treeView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown);
160 this.treeView.KeyUp += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyUp);
161 this.treeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView_AfterSelect);
162 //
163 // toolPanel
164 //
165 this.toolPanel.Controls.Add(this.findStopButton);
166 this.toolPanel.Controls.Add(this.findButton);
167 this.toolPanel.Controls.Add(this.searchTreeCheckBox);
168 this.toolPanel.Controls.Add(this.findTextBox);
169 this.toolPanel.Controls.Add(this.refreshButton);
170 this.toolPanel.Controls.Add(this.forwardButton);
171 this.toolPanel.Controls.Add(this.backButton);
172 this.toolPanel.Dock = System.Windows.Forms.DockStyle.Top;
173 this.toolPanel.Location = new System.Drawing.Point(0, 0);
174 this.toolPanel.Name = "toolPanel";
175 this.toolPanel.Size = new System.Drawing.Size(792, 40);
176 this.toolPanel.TabIndex = 2;
177 //
178 // findStopButton
179 //
180 this.findStopButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
181 this.findStopButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
182 this.findStopButton.Location = new System.Drawing.Point(704, 8);
183 this.findStopButton.Name = "findStopButton";
184 this.findStopButton.Size = new System.Drawing.Size(72, 25);
185 this.findStopButton.TabIndex = 6;
186 this.findStopButton.Text = "Stop";
187 this.findStopButton.Visible = false;
188 this.findStopButton.Click += new System.EventHandler(this.findStopButton_Click);
189 //
190 // findButton
191 //
192 this.findButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
193 this.findButton.Enabled = false;
194 this.findButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
195 this.findButton.Location = new System.Drawing.Point(624, 8);
196 this.findButton.Name = "findButton";
197 this.findButton.Size = new System.Drawing.Size(72, 25);
198 this.findButton.TabIndex = 4;
199 this.findButton.Text = "Find";
200 this.findButton.Click += new System.EventHandler(this.findButton_Click);
201 //
202 // searchTreeCheckBox
203 //
204 this.searchTreeCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
205 this.searchTreeCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System;
206 this.searchTreeCheckBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
207 this.searchTreeCheckBox.Location = new System.Drawing.Point(704, 10);
208 this.searchTreeCheckBox.Name = "searchTreeCheckBox";
209 this.searchTreeCheckBox.Size = new System.Drawing.Size(80, 22);
210 this.searchTreeCheckBox.TabIndex = 5;
211 this.searchTreeCheckBox.Text = "In Subtree";
212 this.searchTreeCheckBox.CheckedChanged += new System.EventHandler(this.searchTreeCheckBox_CheckedChanged);
213 //
214 // findTextBox
215 //
216 this.findTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
217 this.findTextBox.Location = new System.Drawing.Point(344, 10);
218 this.findTextBox.Name = "findTextBox";
219 this.findTextBox.Size = new System.Drawing.Size(272, 20);
220 this.findTextBox.TabIndex = 3;
221 this.findTextBox.Text = "";
222 this.findTextBox.TextChanged += new System.EventHandler(this.findTextBox_TextChanged);
223 this.findTextBox.Enter += new System.EventHandler(this.findTextBox_Enter);
224 //
225 // refreshButton
226 //
227 this.refreshButton.Enabled = false;
228 this.refreshButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
229 this.refreshButton.Location = new System.Drawing.Point(160, 8);
230 this.refreshButton.Name = "refreshButton";
231 this.refreshButton.Size = new System.Drawing.Size(72, 25);
232 this.refreshButton.TabIndex = 2;
233 this.refreshButton.Text = "Refresh";
234 this.refreshButton.Click += new System.EventHandler(this.refreshButton_Click);
235 //
236 // forwardButton
237 //
238 this.forwardButton.Enabled = false;
239 this.forwardButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
240 this.forwardButton.Location = new System.Drawing.Point(80, 8);
241 this.forwardButton.Name = "forwardButton";
242 this.forwardButton.Size = new System.Drawing.Size(72, 25);
243 this.forwardButton.TabIndex = 1;
244 this.forwardButton.Text = "Forward";
245 this.forwardButton.Click += new System.EventHandler(this.forwardButton_Click);
246 //
247 // backButton
248 //
249 this.backButton.Enabled = false;
250 this.backButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
251 this.backButton.Location = new System.Drawing.Point(8, 8);
252 this.backButton.Name = "backButton";
253 this.backButton.Size = new System.Drawing.Size(72, 25);
254 this.backButton.TabIndex = 0;
255 this.backButton.Text = "Back";
256 this.backButton.Click += new System.EventHandler(this.backButton_Click);
257 //
258 // dataPanel
259 //
260 this.dataPanel.Controls.Add(this.dataGrid);
261 this.dataPanel.Controls.Add(this.splitter);
262 this.dataPanel.Controls.Add(this.treeView);
263 this.dataPanel.Dock = System.Windows.Forms.DockStyle.Fill;
264 this.dataPanel.Location = new System.Drawing.Point(0, 40);
265 this.dataPanel.Name = "dataPanel";
266 this.dataPanel.Size = new System.Drawing.Size(792, 432);
267 this.dataPanel.TabIndex = 1;
268 //
269 // splitter
270 //
271 this.splitter.Location = new System.Drawing.Point(224, 0);
272 this.splitter.Name = "splitter";
273 this.splitter.Size = new System.Drawing.Size(6, 432);
274 this.splitter.TabIndex = 2;
275 this.splitter.TabStop = false;
276 //
277 // Inventory
278 //
279 this.AcceptButton = this.findButton;
280 this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
281 this.ClientSize = new System.Drawing.Size(792, 472);
282 this.Controls.Add(this.dataPanel);
283 this.Controls.Add(this.toolPanel);
284 this.MinimumSize = new System.Drawing.Size(700, 0);
285 this.Name = "Inventory";
286 this.Text = "MSI Inventory";
287 this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyDown);
288 this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown);
289 this.Load += new System.EventHandler(this.Inventory_Load);
290 this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyUp);
291 ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).EndInit();
292 this.toolPanel.ResumeLayout(false);
293 this.dataPanel.ResumeLayout(false);
294 this.ResumeLayout(false);
295
296 }
297 #endregion
298
299
300 #region DataProviders
301
302 private IInventoryDataProvider[] DataProviders
303 {
304 get
305 {
306 if(this.dataProviders == null)
307 {
308 ArrayList providerList = new ArrayList();
309 providerList.AddRange(FindDataProviders(Assembly.GetExecutingAssembly()));
310
311 Uri codebase = new Uri(Assembly.GetExecutingAssembly().CodeBase);
312 if(codebase.IsFile)
313 {
314 foreach(string module in Directory.GetFiles(Path.GetDirectoryName(codebase.LocalPath), "*Inventory.dll"))
315 {
316 try
317 {
318 providerList.AddRange(FindDataProviders(Assembly.LoadFrom(module)));
319 }
320 catch(Exception) { }
321 }
322 }
323
324 this.dataProviders = (IInventoryDataProvider[]) providerList.ToArray(typeof(IInventoryDataProvider));
325 }
326 return this.dataProviders;
327 }
328 }
329
330 private static IList FindDataProviders(Assembly assembly)
331 {
332 ArrayList providerList = new ArrayList();
333 foreach(Type type in assembly.GetTypes())
334 {
335 if(type.IsClass)
336 {
337 foreach(Type implementedInterface in type.GetInterfaces())
338 {
339 if(implementedInterface.Equals(typeof(IInventoryDataProvider)))
340 {
341 try
342 {
343 providerList.Add(assembly.CreateInstance(type.FullName));
344 }
345 catch(Exception)
346 {
347 // Data provider's constructor threw an exception for some reason.
348 // Well, now we can't get any data from that one.
349 }
350 }
351 }
352 }
353 }
354 return providerList;
355 }
356
357 #endregion
358
359 private void GoTo(string nodePath, DataGridCell cell)
360 {
361 lock(syncRoot)
362 {
363 if(this.tablesLoading == null) return; // The tree is being loaded
364 if(this.navigating) return; // This method is already on the callstack
365
366 DataView table = (DataView) this.data[nodePath];
367 if(table != null && table == this.dataGrid.DataSource)
368 {
369 // Grid is already in view
370 if(!cell.Equals(anyCell)) this.dataGrid.CurrentCell = cell;
371 return;
372 }
373 if(cell.Equals(anyCell)) cell = zeroCell;
374
375 if(this.historyBack.Count == 0 || nodePath != (string) this.historyBack.Peek())
376 {
377 this.historyBack.Push(nodePath);
378 if(this.cellHistoryBack.Count > 0 && this.historyForward != null)
379 {
380 this.cellHistoryBack.Pop();
381 this.cellHistoryBack.Push(this.dataGrid.CurrentCell);
382 }
383 this.cellHistoryBack.Push(cell);
384 }
385 if(this.historyForward != null)
386 {
387 this.historyForward.Clear();
388 this.cellHistoryForward.Clear();
389 }
390
391 if(table != null || nodePath.Length == 0 || this.dataProviderMap[nodePath] == null)
392 {
393 this.dataGrid.CaptionText = nodePath;
394 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
395 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
396 this.dataGrid.DataSource = table;
397 this.dataGrid.CurrentCell = cell;
398 this.dataGrid.Focus();
399 }
400 else
401 {
402 this.dataGrid.CaptionText = nodePath + " (loading...)";
403 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
404 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
405 this.dataGrid.DataSource = table;
406 if(!this.tablesLoading.Contains(nodePath))
407 {
408 this.tablesLoading.Add(nodePath);
409 this.SetCursor();
410 #if SINGLETHREAD
411 this.LoadTable(nodePath);
412 #else
413 new WaitCallback(this.LoadTable).BeginInvoke(nodePath, null, null);
414 #endif
415 }
416 }
417
418 this.findButton.Enabled = this.findTextBox.Text.Length > 0 && !searching;
419
420 TreeNode treeNode = this.FindNode(nodePath);
421 if(treeNode != this.treeView.SelectedNode)
422 {
423 this.navigating = true;
424 this.treeView.SelectedNode = treeNode;
425 this.navigating = false;
426 }
427 }
428 }
429
430 private void LoadTable(object nodePathObj)
431 {
432 string nodePath = (string) nodePathObj;
433 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
434 DataView table = null;
435 if(dataProvider != null)
436 {
437 try
438 {
439 table = dataProvider.GetData(nodePath);
440 }
441 catch(Exception)
442 {
443 // Data provider threw an exception for some reason.
444 // Treat it like it returned no data.
445 }
446 }
447
448 lock(syncRoot)
449 {
450 if(this.tablesLoading == null || !tablesLoading.Contains(nodePath)) return;
451 if(table == null)
452 {
453 this.dataProviderMap.Remove(nodePath);
454 }
455 else
456 {
457 this.data[nodePath] = table;
458 }
459 this.tablesLoading.Remove(nodePath);
460 }
461 #if SINGLETHREAD
462 this.TableLoaded(nodePath);
463 #else
464 this.Invoke(new WaitCallback(this.TableLoaded), new object[] { nodePath });
465 #endif
466 }
467
468 private void TableLoaded(object nodePathObj)
469 {
470 string nodePath = (string) nodePathObj;
471 lock(syncRoot)
472 {
473 this.LoadTableStyle(nodePath);
474 if(nodePath == this.CurrentNodePath)
475 {
476 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
477 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
478 this.dataGrid.CaptionText = nodePath;
479 this.dataGrid.DataSource = this.CurrentTable;
480 this.dataGrid.CurrentCell = (DataGridCell) this.cellHistoryBack.Peek();
481 this.dataGrid.Focus();
482 }
483 this.SetCursor();
484 }
485 }
486
487 private void RefreshData()
488 {
489 lock(syncRoot)
490 {
491 this.GoTo("", zeroCell);
492 this.treeView.Nodes.Clear();
493 this.dataGrid.TableStyles.Clear();
494 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
495 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
496 this.SetControlsEnabled(false);
497 this.treeView.BeginUpdate();
498 #if SINGLETHREAD
499 this.LoadTree();
500 #else
501 new ThreadStart(this.LoadTree).BeginInvoke(null, null);
502 #endif
503 }
504 }
505
506 private void SetControlsEnabled(bool enabled)
507 {
508 this.backButton.Enabled = enabled && this.historyBack.Count > 1;
509 this.forwardButton.Enabled = enabled && this.historyForward.Count > 0;
510 this.refreshButton.Enabled = enabled;
511 this.findButton.Enabled = enabled && this.findTextBox.Text.Length > 0 && !searching;
512 }
513
514 private WaitCallback treeStatusCallback;
515 private int treeNodesLoaded;
516 private int treeNodesLoadedBase;
517 private string treeNodesLoading;
518 private void TreeLoadDataProviderStatus(int status, string currentNode)
519 {
520 if (currentNode != null)
521 {
522 this.treeNodesLoading = currentNode;
523 }
524
525 this.treeNodesLoaded = treeNodesLoadedBase + status;
526 string statusString = String.Format("Loading tree... " + this.treeNodesLoaded);
527 if (!String.IsNullOrEmpty(this.treeNodesLoading))
528 {
529 statusString += ": " + treeNodesLoading;
530 }
531
532 #if SINGLETHREAD
533 treeStatusCallback(statusString);
534 #else
535 this.Invoke(treeStatusCallback, new object[] { statusString });
536 #endif
537 }
538
539 private void UpdateTreeLoadStatus(object status)
540 {
541 if(status == null)
542 {
543 // Loading is complete.
544 this.treeView.EndUpdate();
545 this.SetCursor();
546 this.GoTo("Products", new DataGridCell(0, 0));
547 this.SetControlsEnabled(true);
548 }
549 else
550 {
551 this.dataGrid.CaptionText = (string) status;
552 }
553 }
554
555 private void LoadTree()
556 {
557 lock(syncRoot)
558 {
559 if(this.tablesLoading == null) return;
560 this.tablesLoading = null;
561 this.dataProviderMap.Clear();
562 this.data.Clear();
563 this.Invoke(new ThreadStart(this.SetCursor));
564 }
565
566 this.treeStatusCallback = new WaitCallback(UpdateTreeLoadStatus);
567 this.LoadTreeNodes();
568 this.RenderTreeNodes();
569
570 lock(syncRoot)
571 {
572 this.tablesLoading = new ArrayList();
573 }
574 // Use a status of null to signal loading complete.
575 #if SINGLETHREAD
576 this.UpdateTreeLoadStatus(null);
577 #else
578 this.Invoke(new WaitCallback(this.UpdateTreeLoadStatus), new object[] { null });
579 #endif
580 }
581
582 private void LoadTreeNodes()
583 {
584 #if SINGLETHREAD
585 this.treeStatusCallback("Loading tree... ");
586 #else
587 this.Invoke(this.treeStatusCallback, new object[] { "Loading tree... " });
588 #endif
589 this.treeNodesLoaded = 0;
590 this.treeNodesLoading = null;
591 foreach(IInventoryDataProvider dataProvider in this.DataProviders)
592 {
593 this.treeNodesLoadedBase = this.treeNodesLoaded;
594 string[] nodePaths = null;
595 try
596 {
597 nodePaths = dataProvider.GetNodes(new InventoryDataLoadStatusCallback(this.TreeLoadDataProviderStatus));
598 }
599 catch(Exception)
600 {
601 // Data provider threw an exception for some reason.
602 // Treat it like it returned no data.
603 }
604 if(nodePaths != null)
605 {
606 foreach(string nodePath in nodePaths)
607 {
608 if(!this.dataProviderMap.Contains(nodePath))
609 {
610 this.dataProviderMap.Add(nodePath, dataProvider);
611 }
612 }
613 }
614 }
615 }
616
617 private void RenderTreeNodes()
618 {
619 #if SINGLETHREAD
620 this.treeStatusCallback("Rendering tree... ");
621 #else
622 this.Invoke(this.treeStatusCallback, new object[] { "Rendering tree... " });
623 #endif
624 this.treeNodesLoaded = 0;
625 foreach(DictionaryEntry nodePathAndProvider in this.dataProviderMap)
626 {
627 string nodePath = (string) nodePathAndProvider.Key;
628 #if SINGLETHREAD
629 this.AddNode(nodePath);
630 #else
631 this.Invoke(new WaitCallback(this.AddNode), new object[] { nodePath });
632 #endif
633 }
634 }
635
636 private void LoadTableStyle(string nodePath)
637 {
638 DataView table = (DataView) this.data[nodePath];
639 if(table != null)
640 {
641 DataGridTableStyle tableStyle = this.dataGrid.TableStyles[table.Table.TableName];
642 if(tableStyle == null)
643 {
644 tableStyle = new DataGridTableStyle();
645 tableStyle.MappingName = table.Table.TableName;
646 tableStyle.RowHeadersVisible = true;
647 this.dataGrid.TableStyles.Add(tableStyle);
648 }
649 foreach(DataColumn column in table.Table.Columns)
650 {
651 if(!tableStyle.GridColumnStyles.Contains(column.ColumnName))
652 {
653 string colStyle = (string) ColumnResources.GetObject(column.ColumnName, CultureInfo.InvariantCulture);
654 if(colStyle != null)
655 {
656 string[] colStyleParts = colStyle.Split(',');
657 DataGridColumnStyle columnStyle = (colStyleParts.Length > 2 && colStyleParts[2] == "bool"
658 ? (DataGridColumnStyle) new DataGridBoolColumn() : (DataGridColumnStyle) new DataGridTextBoxColumn());
659 try { if(colStyleParts.Length > 1) columnStyle.Width = Int32.Parse(colStyleParts[1]); }
660 catch(FormatException) { }
661 columnStyle.HeaderText = colStyleParts[0];
662 columnStyle.MappingName = column.ColumnName;
663 tableStyle.GridColumnStyles.Add(columnStyle);
664 }
665 }
666 }
667 }
668 }
669
670 private static ResourceManager ColumnResources
671 {
672 get
673 {
674 if(columnResources == null)
675 {
676 columnResources = new ResourceManager(typeof(Inventory).Name + ".Columns", typeof(Inventory).Assembly);
677 }
678 return columnResources;
679 }
680 }
681 private static ResourceManager columnResources;
682
683 private void AddNode(object nodePathObj)
684 {
685 string nodePath = (string) nodePathObj;
686 string[] path = nodePath.Split('\\');
687 TreeNodeCollection nodes = this.treeView.Nodes;
688 TreeNode node = null;
689 foreach(string pathPart in path)
690 {
691 node = null;
692 for(int i = 0; i < nodes.Count; i++)
693 {
694 int c = string.CompareOrdinal(nodes[i].Text, pathPart);
695 if(c == 0)
696 {
697 node = nodes[i];
698 break;
699 }
700 else if(c > 0)
701 {
702 node = new TreeNode(pathPart);
703 nodes.Insert(i, node);
704 break;
705 }
706 }
707 if(node == null)
708 {
709 node = new TreeNode(pathPart);
710 nodes.Add(node);
711 }
712 nodes = node.Nodes;
713 }
714 if(++this.treeNodesLoaded % 1000 == 0)
715 {
716 this.UpdateTreeLoadStatus("Rendering tree... " +
717 (100 * this.treeNodesLoaded / this.dataProviderMap.Count) + "%");
718 }
719 }
720
721 public string CurrentNodePath
722 {
723 get
724 {
725 TreeNode currentNode = this.treeView.SelectedNode;
726 return currentNode != null ? currentNode.FullPath : null;
727 }
728 }
729
730 public DataView CurrentTable
731 {
732 get
733 {
734 string currentNodePath = this.CurrentNodePath;
735 return currentNodePath != null ? (DataView) this.data[this.CurrentNodePath] : null;
736 }
737 }
738
739 private TreeNode FindNode(string nodePath)
740 {
741 if(nodePath == null) return null;
742 string[] path = nodePath.Split('\\');
743 TreeNodeCollection nodes = this.treeView.Nodes;
744 TreeNode node = null;
745 foreach(string pathPart in path)
746 {
747 node = null;
748 for(int i = 0; i < nodes.Count; i++)
749 {
750 if(nodes[i].Text == pathPart)
751 {
752 node = nodes[i];
753 break;
754 }
755 }
756 if(node != null)
757 {
758 nodes = node.Nodes;
759 }
760 }
761 return node;
762 }
763
764 private void dataGrid_MouseDown(object sender, MouseEventArgs e)
765 {
766 Keys modKeys = Control.ModifierKeys;
767 if(e.Button == MouseButtons.Left && (modKeys & (Keys.Shift | Keys.Control)) == 0)
768 {
769 DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y);
770 string link = this.GetLinkForGridHit(hit);
771 if(link != null)
772 {
773 TreeNode node = this.FindNode(link);
774 if(node != null)
775 {
776 this.treeView.SelectedNode = node;
777 node.Expand();
778 }
779 }
780 }
781 this.Inventory_MouseDown(sender, e);
782 }
783
784 private void dataGrid_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
785 {
786 //this.gridLinkTip.SetToolTip(this.dataGrid, null);
787 DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y);
788 if(hit.Type == DataGrid.HitTestType.RowHeader)
789 {
790 string link = this.GetLinkForGridHit(hit);
791 if(link != null)
792 {
793 this.mouseOverGridLink = true;
794 this.SetCursor();
795 return;
796 }
797 }
798 else if(this.mouseOverGridLink)
799 {
800 this.mouseOverGridLink = false;
801 this.SetCursor();
802 }
803 }
804
805 private void dataGrid_MouseLeave(object sender, System.EventArgs e)
806 {
807 this.mouseOverGridLink = false;
808 this.SetCursor();
809 }
810
811 private string GetLinkForGridHit(DataGrid.HitTestInfo hit)
812 {
813 if(hit.Type == DataGrid.HitTestType.RowHeader && this.tablesLoading != null)
814 {
815 string nodePath = this.CurrentNodePath;
816 DataView table = (DataView) this.data[nodePath];
817 if(table != null)
818 {
819 DataRow row = table[hit.Row].Row;
820 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
821 return dataProvider.GetLink(nodePath, table[hit.Row].Row);
822 }
823 }
824 return null;
825 }
826
827 private void HistoryBack()
828 {
829 lock(syncRoot)
830 {
831 if(this.historyBack.Count > 1)
832 {
833 string nodePath = (string) this.historyBack.Pop();
834 this.cellHistoryBack.Pop();
835 DataGridCell cell = this.dataGrid.CurrentCell;
836 Stack saveForward = this.historyForward;
837 this.historyForward = null;
838 this.GoTo((string) this.historyBack.Pop(), (DataGridCell) this.cellHistoryBack.Pop());
839 this.historyForward = saveForward;
840 this.historyForward.Push(nodePath);
841 this.cellHistoryForward.Push(cell);
842 this.backButton.Enabled = this.historyBack.Count > 1;
843 this.forwardButton.Enabled = this.historyForward.Count > 0;
844 }
845 }
846 }
847
848 private void HistoryForward()
849 {
850 lock(syncRoot)
851 {
852 if(this.historyForward.Count > 0)
853 {
854 string nodePath = (string) this.historyForward.Pop();
855 DataGridCell cell = (DataGridCell) this.cellHistoryForward.Pop();
856 Stack saveForward = this.historyForward;
857 this.historyForward = null;
858 this.GoTo(nodePath, cell);
859 this.historyForward = saveForward;
860 this.backButton.Enabled = this.historyBack.Count > 1;
861 this.forwardButton.Enabled = this.historyForward.Count > 0;
862 }
863 }
864 }
865
866 #region Find
867
868 private void Find()
869 {
870 this.BeginFind();
871 object[] findNextArgs = new object[] { this.CurrentNodePath, this.dataGrid.CurrentCell, this.treeView.SelectedNode };
872 #if SINGLETHREAD
873 this.FindNext(findNextArgs);
874 #else
875 new WaitCallback(this.FindNext).BeginInvoke(findNextArgs, null, null);
876 #endif
877 }
878
879 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
880 private void FindNext(object start)
881 {
882 string nodePath = (string) ((object[]) start)[0];
883 DataGridCell startCell = (DataGridCell) ((object[]) start)[1];
884 TreeNode searchNode = (TreeNode) ((object[]) start)[2];
885 DataGridCell endCell = startCell;
886
887 string searchString = this.findTextBox.Text;
888 if(searchString.Length == 0) return;
889
890 bool ignoreCase = true; // TODO: make this a configurable option?
891 if(ignoreCase) searchString = searchString.ToLowerInvariant();
892
893 if(!this.searchTreeCheckBox.Checked)
894 {
895 DataGridCell foundCell;
896 startCell.ColumnNumber++;
897 if(FindInTable((DataView) this.data[nodePath], searchString, ignoreCase,
898 startCell, startCell, true, out foundCell))
899 {
900 #if SINGLETHREAD
901 this.EndFind(new object[] { nodePath, foundCell });
902 #else
903 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodePath, foundCell } });
904 #endif
905 return;
906 }
907 }
908 else
909 {
910 if(this.continueSearchRoot != null)
911 {
912 searchNode = this.FindNode(this.continueSearchRoot);
913 startCell = this.continueSearchCell;
914 endCell = this.continueSearchEndCell;
915 }
916 else
917 {
918 this.continueSearchRoot = searchNode.FullPath;
919 this.continueSearchPath = this.continueSearchRoot;
920 this.continueSearchEndCell = endCell;
921 }
922 //if(searchNode == null) return;
923 ArrayList nodesList = new ArrayList();
924 nodesList.Add(searchNode);
925 this.GetFlatTreeNodes(searchNode.Nodes, nodesList, true, this.continueSearchRoot);
926 TreeNode[] nodes = (TreeNode[]) nodesList.ToArray(typeof(TreeNode));
927 int startNode = nodesList.IndexOf(this.FindNode(this.continueSearchPath));
928 DataGridCell foundCell;
929 startCell.ColumnNumber++;
930 for(int i = startNode; i < nodes.Length; i++)
931 {
932 if(this.stopSearch) break;
933 DataGridCell startCellOnThisNode = zeroCell;
934 if(i == startNode) startCellOnThisNode = startCell;
935 DataView table = this.GetTableForSearch(nodes[i].FullPath);
936 if(table != null)
937 {
938 if(FindInTable(table, searchString, ignoreCase, startCellOnThisNode, zeroCell, false, out foundCell))
939 {
940 #if SINGLETHREAD
941 this.EndFind(new object[] { nodes[i].FullPath, foundCell });
942 #else
943 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodes[i].FullPath, foundCell } });
944 #endif
945 return;
946 }
947 }
948 }
949 if(!this.stopSearch)
950 {
951 DataView table = this.GetTableForSearch(searchNode.FullPath);
952 if(table != null)
953 {
954 if(FindInTable(table, searchString, ignoreCase, zeroCell, endCell, false, out foundCell))
955 {
956 #if SINGLETHREAD
957 this.EndFind(new object[] { searchNode.FullPath, foundCell });
958 #else
959 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { searchNode.FullPath, foundCell } });
960 #endif
961 return;
962 }
963 }
964 }
965 }
966 #if SINGLETHREAD
967 this.EndFind(null);
968 #else
969 this.Invoke(new WaitCallback(this.EndFind), new object[] { null });
970 #endif
971 }
972
973 private DataView GetTableForSearch(string nodePath)
974 {
975 DataView table = (DataView) this.data[nodePath];
976 string status = nodePath;
977 if(table == null) status = status + " (loading)";
978 #if SINGLETHREAD
979 this.FindStatus(nodePath);
980 #else
981 this.Invoke(new WaitCallback(this.FindStatus), new object[] { status });
982 #endif
983 if(table == null)
984 {
985 this.tablesLoading.Add(nodePath);
986 this.Invoke(new ThreadStart(this.SetCursor));
987 this.LoadTable(nodePath);
988 table = (DataView) this.data[nodePath];
989 }
990 return table;
991 }
992
993 private void GetFlatTreeNodes(TreeNodeCollection nodes, IList resultsList, bool searchable, string searchRoot)
994 {
995 foreach(TreeNode node in nodes)
996 {
997 string nodePath = node.FullPath;
998 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
999 if(!searchable || (dataProvider != null && dataProvider.IsNodeSearchable(searchRoot, nodePath)))
1000 {
1001 resultsList.Add(node);
1002 }
1003 GetFlatTreeNodes(node.Nodes, resultsList, searchable, searchRoot);
1004 }
1005 }
1006
1007 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
1008 private bool FindInTable(DataView table, string searchString, bool lowerCase,
1009 DataGridCell startCell, DataGridCell endCell, bool wrap, out DataGridCell foundCell)
1010 {
1011 foundCell = new DataGridCell(-1, -1);
1012 if(table == null) return false;
1013 if(startCell.RowNumber < 0) startCell.RowNumber = 0;
1014 if(startCell.ColumnNumber < 0) startCell.ColumnNumber = 0;
1015 for(int searchRow = startCell.RowNumber; searchRow < table.Count; searchRow++)
1016 {
1017 if(this.stopSearch) break;
1018 if(endCell.RowNumber > startCell.RowNumber && searchRow > endCell.RowNumber) break;
1019
1020 DataRowView tableRow = table[searchRow];
1021 for(int searchCol = (searchRow == startCell.RowNumber
1022 ? startCell.ColumnNumber : 0); searchCol < table.Table.Columns.Count; searchCol++)
1023 {
1024 if(this.stopSearch) break;
1025 if(endCell.RowNumber > startCell.RowNumber && searchRow == endCell.RowNumber
1026 && searchCol >= endCell.ColumnNumber) break;
1027
1028 string value = tableRow[searchCol].ToString();
1029 if(lowerCase) value = value.ToLowerInvariant();
1030 if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0)
1031 {
1032 foundCell.RowNumber = searchRow;
1033 foundCell.ColumnNumber = searchCol;
1034 return true;
1035 }
1036 }
1037 }
1038 if(wrap)
1039 {
1040 for(int searchRow = 0; searchRow <= endCell.RowNumber; searchRow++)
1041 {
1042 if(this.stopSearch) break;
1043 DataRowView tableRow = table[searchRow];
1044 for(int searchCol = 0; searchCol < (searchRow == endCell.RowNumber
1045 ? endCell.ColumnNumber : table.Table.Columns.Count); searchCol++)
1046 {
1047 if(this.stopSearch) break;
1048 string value = tableRow[searchCol].ToString();
1049 if(lowerCase) value = value.ToLowerInvariant();
1050 if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0)
1051 {
1052 foundCell.RowNumber = searchRow;
1053 foundCell.ColumnNumber = searchCol;
1054 return true;
1055 }
1056 }
1057 }
1058 }
1059 return false;
1060 }
1061
1062 private void BeginFind()
1063 {
1064 lock(syncRoot)
1065 {
1066 this.findButton.Enabled = false;
1067 this.findButton.Text = "Searching...";
1068 this.findTextBox.Enabled = false;
1069 this.searchTreeCheckBox.Visible = false;
1070 this.findStopButton.Visible = true;
1071 this.refreshButton.Enabled = false;
1072 this.searching = true;
1073 this.stopSearch = false;
1074 this.SetCursor();
1075 }
1076 }
1077
1078 private void FindStatus(object status)
1079 {
1080 lock(syncRoot)
1081 {
1082 this.dataGrid.CaptionText = "Searching... " + (string) status;
1083 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
1084 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
1085 }
1086 }
1087
1088 private void EndFind(object result)
1089 {
1090 lock(syncRoot)
1091 {
1092 this.searching = false;
1093 this.refreshButton.Enabled = true;
1094 this.findStopButton.Visible = false;
1095 this.searchTreeCheckBox.Visible = true;
1096 this.findTextBox.Enabled = true;
1097 this.findButton.Text = "Find";
1098 this.findButton.Enabled = true;
1099 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
1100 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
1101 this.dataGrid.CaptionText = this.CurrentNodePath;
1102 if(result != null)
1103 {
1104 string nodePath = (string) ((object[]) result)[0];
1105 DataGridCell foundCell = (DataGridCell) ((object[]) result)[1];
1106 this.GoTo(nodePath, foundCell);
1107 this.dataGrid.Focus();
1108 this.continueSearchPath = nodePath;
1109 this.continueSearchCell = foundCell;
1110 if(this.searchTreeCheckBox.Checked) this.searchTreeCheckBox.Text = "Continue";
1111 }
1112 else
1113 {
1114 this.continueSearchRoot = null;
1115 this.continueSearchPath = null;
1116 this.searchTreeCheckBox.Text = "In Subtree";
1117 }
1118 this.SetCursor();
1119 }
1120 }
1121
1122 private void SetCursor()
1123 {
1124 if(this.mouseOverGridLink)
1125 {
1126 Keys modKeys = Control.ModifierKeys;
1127 if((modKeys & (Keys.Shift | Keys.Control)) == 0)
1128 {
1129 this.Cursor = Cursors.Hand;
1130 return;
1131 }
1132 }
1133 if(this.tablesLoading == null || this.tablesLoading.Count > 0 || this.searching)
1134 {
1135 this.Cursor = Cursors.AppStarting;
1136 return;
1137 }
1138 this.Cursor = Cursors.Arrow;
1139 }
1140
1141 #endregion
1142
1143 #region EventHandlers
1144
1145 private void Inventory_Load(object sender, System.EventArgs e)
1146 {
1147 this.RefreshData();
1148 }
1149 private void refreshButton_Click(object sender, System.EventArgs e)
1150 {
1151 this.RefreshData();
1152 }
1153 private void Inventory_MouseDown(object sender, MouseEventArgs e)
1154 {
1155 if(e.Button == MouseButtons.XButton1) this.HistoryBack();
1156 else if(e.Button == MouseButtons.XButton2) this.HistoryForward();
1157 }
1158 private void Inventory_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1159 {
1160 this.SetCursor();
1161 if(e.KeyCode == Keys.F3) this.Find();
1162 else if(e.KeyCode == Keys.F && (e.Modifiers | Keys.Control) != 0) this.findTextBox.Focus();
1163 else if(e.KeyCode == Keys.BrowserBack) this.HistoryBack();
1164 else if(e.KeyCode == Keys.BrowserForward) this.HistoryForward();
1165 else return;
1166 e.Handled = true;
1167 }
1168 private void treeView_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1169 {
1170 this.Inventory_KeyDown(sender, e);
1171 }
1172 private void dataGrid_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1173 {
1174 this.Inventory_KeyDown(sender, e);
1175 }
1176 private void Inventory_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1177 {
1178 this.SetCursor();
1179 }
1180 private void treeView_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1181 {
1182 this.Inventory_KeyDown(sender, e);
1183 }
1184 private void dataGrid_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1185 {
1186 this.Inventory_KeyDown(sender, e);
1187 }
1188 private void treeView_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
1189 {
1190 this.GoTo(e.Node.FullPath, anyCell);
1191 }
1192 private void backButton_Click(object sender, System.EventArgs e)
1193 {
1194 this.HistoryBack();
1195 }
1196 private void forwardButton_Click(object sender, System.EventArgs e)
1197 {
1198 this.HistoryForward();
1199 }
1200 private void findTextBox_TextChanged(object sender, System.EventArgs e)
1201 {
1202 this.findButton.Enabled = this.findTextBox.Text.Length > 0 &&
1203 this.tablesLoading != null && this.treeView.SelectedNode != null && !searching;
1204 this.searchTreeCheckBox.Text = "In Subtree";
1205 this.continueSearchRoot = null;
1206 }
1207 private void findButton_Click(object sender, System.EventArgs e)
1208 {
1209 this.Find();
1210 }
1211 private void findTextBox_Enter(object sender, System.EventArgs e)
1212 {
1213 findTextBox.SelectAll();
1214 }
1215 private void findStopButton_Click(object sender, System.EventArgs e)
1216 {
1217 this.stopSearch = true;
1218 }
1219
1220 private void searchTreeCheckBox_CheckedChanged(object sender, System.EventArgs e)
1221 {
1222 if(!searchTreeCheckBox.Checked && searchTreeCheckBox.Text == "Continue")
1223 {
1224 this.searchTreeCheckBox.Text = "In Subtree";
1225 }
1226 }
1227
1228 #endregion
1229
1230 }
1231}
diff --git a/src/tools/Dtf/Inventory/Inventory.csproj b/src/tools/Dtf/Inventory/Inventory.csproj
new file mode 100644
index 00000000..57bae907
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Inventory.csproj
@@ -0,0 +1,42 @@
1
2<!-- 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. -->
3
4
5<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <PropertyGroup>
7 <ProjectGuid>{51480F8E-B80F-42DC-91E7-3542C1F12F8C}</ProjectGuid>
8 <OutputType>WinExe</OutputType>
9 <RootNamespace>WixToolset.Dtf.Tools.Inventory</RootNamespace>
10 <AssemblyName>Inventory</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 <ApplicationIcon>Inventory.ico</ApplicationIcon>
13 <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <Compile Include="components.cs" />
18 <Compile Include="Features.cs" />
19 <Compile Include="IInventoryDataProvider.cs" />
20 <Compile Include="Inventory.cs">
21 <SubType>Form</SubType>
22 </Compile>
23 <Compile Include="msiutils.cs" />
24 <Compile Include="patches.cs" />
25 <Compile Include="products.cs" />
26 </ItemGroup>
27
28 <ItemGroup>
29 <Content Include="Inventory.ico" />
30 </ItemGroup>
31
32 <ItemGroup>
33 <Reference Include="System" />
34 <Reference Include="System.Data" />
35 <Reference Include="System.Drawing" />
36 <Reference Include="System.Windows.Forms" />
37 <Reference Include="System.Xml" />
38 <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
39 </ItemGroup>
40
41 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
42</Project>
diff --git a/src/tools/Dtf/Inventory/Inventory.ico b/src/tools/Dtf/Inventory/Inventory.ico
new file mode 100644
index 00000000..d5757f7a
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Inventory.ico
Binary files differ
diff --git a/src/tools/Dtf/Inventory/Inventory.resx b/src/tools/Dtf/Inventory/Inventory.resx
new file mode 100644
index 00000000..9aeb4d2c
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Inventory.resx
@@ -0,0 +1,265 @@
1<?xml version="1.0" encoding="utf-8"?>
2<root>
3 <!--
4 Microsoft ResX Schema
5
6 Version 1.3
7
8 The primary goals of this format is to allow a simple XML format
9 that is mostly human readable. The generation and parsing of the
10 various data types are done through the TypeConverter classes
11 associated with the data types.
12
13 Example:
14
15 ... ado.net/XML headers & schema ...
16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 <resheader name="version">1.3</resheader>
18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 <data name="Name1">this is my long string</data>
21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 [base64 mime encoded serialized .NET Framework object]
24 </data>
25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 [base64 mime encoded string representing a byte array form of the .NET Framework object]
27 </data>
28
29 There are any number of "resheader" rows that contain simple
30 name/value pairs.
31
32 Each data row contains a name, and value. The row also contains a
33 type or mimetype. Type corresponds to a .NET class that support
34 text/value conversion through the TypeConverter architecture.
35 Classes that don't support this are serialized and stored with the
36 mimetype set.
37
38 The mimetype is used forserialized objects, and tells the
39 ResXResourceReader how to depersist the object. This is currently not
40 extensible. For a given mimetype the value must be set accordingly:
41
42 Note - application/x-microsoft.net.object.binary.base64 is the format
43 that the ResXResourceWriter will generate, however the reader can
44 read any of the formats listed below.
45
46 mimetype: application/x-microsoft.net.object.binary.base64
47 value : The object must be serialized with
48 : System.Serialization.Formatters.Binary.BinaryFormatter
49 : and then encoded with base64 encoding.
50
51 mimetype: application/x-microsoft.net.object.soap.base64
52 value : The object must be serialized with
53 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
54 : and then encoded with base64 encoding.
55
56 mimetype: application/x-microsoft.net.object.bytearray.base64
57 value : The object must be serialized into a byte array
58 : using a System.ComponentModel.TypeConverter
59 : and then encoded with base64 encoding.
60 -->
61 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
62 <xsd:element name="root" msdata:IsDataSet="true">
63 <xsd:complexType>
64 <xsd:choice maxOccurs="unbounded">
65 <xsd:element name="data">
66 <xsd:complexType>
67 <xsd:sequence>
68 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
69 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
70 </xsd:sequence>
71 <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
72 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
73 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
74 </xsd:complexType>
75 </xsd:element>
76 <xsd:element name="resheader">
77 <xsd:complexType>
78 <xsd:sequence>
79 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
80 </xsd:sequence>
81 <xsd:attribute name="name" type="xsd:string" use="required" />
82 </xsd:complexType>
83 </xsd:element>
84 </xsd:choice>
85 </xsd:complexType>
86 </xsd:element>
87 </xsd:schema>
88 <resheader name="resmimetype">
89 <value>text/microsoft-resx</value>
90 </resheader>
91 <resheader name="version">
92 <value>1.3</value>
93 </resheader>
94 <resheader name="reader">
95 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
96 </resheader>
97 <resheader name="writer">
98 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
99 </resheader>
100 <data name="dataGrid.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
101 <value>False</value>
102 </data>
103 <data name="dataGrid.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
104 <value>Private</value>
105 </data>
106 <data name="dataGrid.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
107 <value>Private</value>
108 </data>
109 <data name="treeView.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
110 <value>Private</value>
111 </data>
112 <data name="treeView.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
113 <value>Private</value>
114 </data>
115 <data name="treeView.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
116 <value>False</value>
117 </data>
118 <data name="toolPanel.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
119 <value>False</value>
120 </data>
121 <data name="toolPanel.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
122 <value>True</value>
123 </data>
124 <data name="toolPanel.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
125 <value>True</value>
126 </data>
127 <data name="toolPanel.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
128 <value>Private</value>
129 </data>
130 <data name="toolPanel.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
131 <value>Private</value>
132 </data>
133 <data name="toolPanel.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
134 <value>8, 8</value>
135 </data>
136 <data name="findStopButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
137 <value>False</value>
138 </data>
139 <data name="findStopButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
140 <value>Private</value>
141 </data>
142 <data name="findStopButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
143 <value>Private</value>
144 </data>
145 <data name="findButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
146 <value>False</value>
147 </data>
148 <data name="findButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
149 <value>Private</value>
150 </data>
151 <data name="findButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
152 <value>Private</value>
153 </data>
154 <data name="searchTreeCheckBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
155 <value>False</value>
156 </data>
157 <data name="searchTreeCheckBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
158 <value>Private</value>
159 </data>
160 <data name="searchTreeCheckBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
161 <value>Private</value>
162 </data>
163 <data name="findTextBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
164 <value>Private</value>
165 </data>
166 <data name="findTextBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
167 <value>False</value>
168 </data>
169 <data name="findTextBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
170 <value>Private</value>
171 </data>
172 <data name="refreshButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
173 <value>False</value>
174 </data>
175 <data name="refreshButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
176 <value>Private</value>
177 </data>
178 <data name="refreshButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
179 <value>Private</value>
180 </data>
181 <data name="forwardButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
182 <value>False</value>
183 </data>
184 <data name="forwardButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
185 <value>Private</value>
186 </data>
187 <data name="forwardButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
188 <value>Private</value>
189 </data>
190 <data name="backButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
191 <value>False</value>
192 </data>
193 <data name="backButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
194 <value>Private</value>
195 </data>
196 <data name="backButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
197 <value>Private</value>
198 </data>
199 <data name="dataPanel.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
200 <value>False</value>
201 </data>
202 <data name="dataPanel.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
203 <value>True</value>
204 </data>
205 <data name="dataPanel.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
206 <value>True</value>
207 </data>
208 <data name="dataPanel.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
209 <value>Private</value>
210 </data>
211 <data name="dataPanel.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
212 <value>Private</value>
213 </data>
214 <data name="dataPanel.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
215 <value>8, 8</value>
216 </data>
217 <data name="splitter.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
218 <value>False</value>
219 </data>
220 <data name="splitter.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
221 <value>Private</value>
222 </data>
223 <data name="splitter.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
224 <value>Private</value>
225 </data>
226 <data name="gridLinkTip.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
227 <value>Private</value>
228 </data>
229 <data name="gridLinkTip.Location" type="System.Drawing.Point, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
230 <value>17, 17</value>
231 </data>
232 <data name="gridLinkTip.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
233 <value>Private</value>
234 </data>
235 <data name="$this.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
236 <value>False</value>
237 </data>
238 <data name="$this.Language" type="System.Globalization.CultureInfo, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
239 <value>(Default)</value>
240 </data>
241 <data name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
242 <value>False</value>
243 </data>
244 <data name="$this.Localizable" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
245 <value>False</value>
246 </data>
247 <data name="$this.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
248 <value>8, 8</value>
249 </data>
250 <data name="$this.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
251 <value>True</value>
252 </data>
253 <data name="$this.TrayHeight" type="System.Int32, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
254 <value>80</value>
255 </data>
256 <data name="$this.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
257 <value>True</value>
258 </data>
259 <data name="$this.Name">
260 <value>Inventory</value>
261 </data>
262 <data name="$this.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
263 <value>Private</value>
264 </data>
265</root> \ No newline at end of file
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}
diff --git a/src/tools/Dtf/Inventory/msiutils.cs b/src/tools/Dtf/Inventory/msiutils.cs
new file mode 100644
index 00000000..189d28a9
--- /dev/null
+++ b/src/tools/Dtf/Inventory/msiutils.cs
@@ -0,0 +1,46 @@
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.Collections;
5using WixToolset.Dtf.WindowsInstaller;
6
7
8namespace WixToolset.Dtf.Tools.Inventory
9{
10 public class MsiUtils
11 {
12 private static Hashtable productCodesToNames = new Hashtable();
13 private static Hashtable productNamesToCodes = new Hashtable();
14
15 public static string GetProductName(string productCode)
16 {
17 string productName = (string) productCodesToNames[productCode];
18 if(productName == null)
19 {
20 productName = new ProductInstallation(productCode).ProductName;
21 productName = productName.Replace('\\', ' ');
22 if(productNamesToCodes.Contains(productName))
23 {
24 string modifiedProductName = null;
25 for(int i = 2; i < Int32.MaxValue; i++)
26 {
27 modifiedProductName = productName + " [" + i + "]";
28 if(!productNamesToCodes.Contains(modifiedProductName)) break;
29 }
30 productName = modifiedProductName;
31 }
32 productCodesToNames[productCode] = productName;
33 productNamesToCodes[productName] = productCode;
34 }
35 return productName;
36 }
37
38 // Assumes GetProductName() has already been called for this product.
39 public static string GetProductCode(string productName)
40 {
41 return (string) productNamesToCodes[productName];
42 }
43
44 private MsiUtils() { }
45 }
46}
diff --git a/src/tools/Dtf/Inventory/patches.cs b/src/tools/Dtf/Inventory/patches.cs
new file mode 100644
index 00000000..ca96a97d
--- /dev/null
+++ b/src/tools/Dtf/Inventory/patches.cs
@@ -0,0 +1,227 @@
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.Security;
6using System.Data;
7using System.Collections;
8using System.Globalization;
9using System.Windows.Forms;
10using WixToolset.Dtf.WindowsInstaller;
11
12namespace WixToolset.Dtf.Tools.Inventory
13{
14 /// <summary>
15 /// Provides inventory data about patches installed on the system.
16 /// </summary>
17 public class PatchesInventory : IInventoryDataProvider
18 {
19 public PatchesInventory()
20 {
21 }
22
23 public string Description
24 {
25 get { return "Installed patches"; }
26 }
27
28 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
29 {
30 ArrayList nodes = new ArrayList();
31 statusCallback(nodes.Count, @"Products\...\Patches");
32 foreach (ProductInstallation product in ProductInstallation.AllProducts)
33 {
34 string productName = MsiUtils.GetProductName(product.ProductCode);
35
36 bool addedRoot = false;
37 foreach (PatchInstallation productPatch in PatchInstallation.GetPatches(null, product.ProductCode, null, UserContexts.All, PatchStates.Applied))
38 {
39 if (!addedRoot) nodes.Add(String.Format(@"Products\{0}\Patches", productName));
40 nodes.Add(String.Format(@"Products\{0}\Patches\{1}", productName, productPatch.PatchCode));
41 }
42 }
43
44 statusCallback(nodes.Count, "Patches");
45
46 string[] allPatches = GetAllPatchesList();
47 if(allPatches.Length > 0)
48 {
49 nodes.Add("Patches");
50 foreach(string patchCode in allPatches)
51 {
52 nodes.Add(String.Format(@"Patches\{0}", patchCode));
53 nodes.Add(String.Format(@"Patches\{0}\Patched Products", patchCode));
54 }
55 statusCallback(nodes.Count, String.Empty);
56 }
57 return (string[]) nodes.ToArray(typeof(string));
58 }
59
60 public bool IsNodeSearchable(string searchRoot, string searchNode)
61 {
62 return true;
63 }
64
65 public DataView GetData(string nodePath)
66 {
67 string[] path = nodePath.Split('\\');
68
69 if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches")
70 {
71 return this.GetProductPatchData(path[1]);
72 }
73 else if(path.Length == 4 && path[0] == "Products" && path[2] == "Patches")
74 {
75 return this.GetPatchData(path[3]);
76 }
77 else if(path.Length == 1 && path[0] == "Patches")
78 {
79 return this.GetAllPatchesData();
80 }
81 else if(path.Length == 2 && path[0] == "Patches")
82 {
83 return this.GetPatchData(path[1]);
84 }
85 else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products")
86 {
87 return this.GetPatchTargetData(path[1]);
88 }
89 return null;
90 }
91
92 private string[] GetAllPatchesList()
93 {
94 ArrayList patchList = new ArrayList();
95 foreach(PatchInstallation patch in PatchInstallation.AllPatches)
96 {
97 if(!patchList.Contains(patch.PatchCode))
98 {
99 patchList.Add(patch.PatchCode);
100 }
101 }
102 string[] patchArray = (string[]) patchList.ToArray(typeof(string));
103 Array.Sort(patchArray, 0, patchArray.Length, StringComparer.Ordinal);
104 return patchArray;
105 }
106
107 private DataView GetAllPatchesData()
108 {
109 DataTable table = new DataTable("Patches");
110 table.Locale = CultureInfo.InvariantCulture;
111 table.Columns.Add("PatchesPatchCode", typeof(string));
112
113 foreach(string patchCode in GetAllPatchesList())
114 {
115 table.Rows.Add(new object[] { patchCode });
116 }
117 return new DataView(table, "", "PatchesPatchCode ASC", DataViewRowState.CurrentRows);
118 }
119
120 private DataView GetProductPatchData(string productCode)
121 {
122 DataTable table = new DataTable("ProductPatches");
123 table.Locale = CultureInfo.InvariantCulture;
124 table.Columns.Add("ProductPatchesPatchCode", typeof(string));
125
126 foreach(PatchInstallation patch in PatchInstallation.GetPatches(null, productCode, null, UserContexts.All, PatchStates.Applied))
127 {
128 table.Rows.Add(new object[] { patch.PatchCode });
129 }
130 return new DataView(table, "", "ProductPatchesPatchCode ASC", DataViewRowState.CurrentRows);
131 }
132
133 private DataView GetPatchData(string patchCode)
134 {
135 DataTable table = new DataTable("PatchProperties");
136 table.Locale = CultureInfo.InvariantCulture;
137 table.Columns.Add("PatchPropertiesProperty", typeof(string));
138 table.Columns.Add("PatchPropertiesValue", typeof(string));
139
140 table.Rows.Add(new object[] { "PatchCode", patchCode });
141
142 PatchInstallation patch = new PatchInstallation(patchCode, null);
143
144 string localPackage = null;
145 foreach(string property in new string[]
146 {
147 "InstallDate",
148 "LocalPackage",
149 "State",
150 "Transforms",
151 "Uninstallable",
152 })
153 {
154 try
155 {
156 string value = patch[property];
157 table.Rows.Add(new object[] { property, (value != null ? value : "") });
158 if(property == "LocalPackage") localPackage = value;
159 }
160 catch(InstallerException iex)
161 {
162 table.Rows.Add(new object[] { property, iex.Message });
163 }
164 catch(ArgumentException) { }
165 }
166
167 if(localPackage != null)
168 {
169 try
170 {
171 using(SummaryInfo patchSummaryInfo = new SummaryInfo(localPackage, false))
172 {
173 table.Rows.Add(new object[] { "Title", patchSummaryInfo.Title });
174 table.Rows.Add(new object[] { "Subject", patchSummaryInfo.Subject });
175 table.Rows.Add(new object[] { "Author", patchSummaryInfo.Author });
176 table.Rows.Add(new object[] { "Comments", patchSummaryInfo.Comments });
177 table.Rows.Add(new object[] { "TargetProductCodes", patchSummaryInfo.Template });
178 string obsoletedPatchCodes = patchSummaryInfo.RevisionNumber.Substring(patchSummaryInfo.RevisionNumber.IndexOf('}') + 1);
179 table.Rows.Add(new object[] { "ObsoletedPatchCodes", obsoletedPatchCodes });
180 table.Rows.Add(new object[] { "TransformNames", patchSummaryInfo.LastSavedBy });
181 }
182 }
183 catch(InstallerException) { }
184 catch(IOException) { }
185 catch(SecurityException) { }
186 }
187 return new DataView(table, "", "PatchPropertiesProperty ASC", DataViewRowState.CurrentRows);
188 }
189
190 private DataView GetPatchTargetData(string patchCode)
191 {
192 DataTable table = new DataTable("PatchTargets");
193 table.Locale = CultureInfo.InvariantCulture;
194 table.Columns.Add("PatchTargetsProductName", typeof(string));
195 table.Columns.Add("PatchTargetsProductCode", typeof(string));
196
197 foreach (PatchInstallation patch in PatchInstallation.GetPatches(patchCode, null, null, UserContexts.All, PatchStates.Applied))
198 {
199 if(patch.PatchCode == patchCode)
200 {
201 string productName = MsiUtils.GetProductName(patch.ProductCode);
202 table.Rows.Add(new object[] { productName, patch.ProductCode });
203 }
204 }
205 return new DataView(table, "", "PatchTargetsProductName ASC", DataViewRowState.CurrentRows);
206 }
207
208 public string GetLink(string nodePath, DataRow row)
209 {
210 string[] path = nodePath.Split('\\');
211
212 if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches")
213 {
214 return String.Format(@"Patches\{0}", row["ProductPatchesPatchCode"]);
215 }
216 else if(path.Length == 1 && path[0] == "Patches")
217 {
218 return String.Format(@"Patches\{0}", row["PatchesPatchCode"]);
219 }
220 else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products")
221 {
222 return String.Format(@"Products\{0}", MsiUtils.GetProductCode((string) row["PatchTargetsProductCode"]));
223 }
224 return null;
225 }
226 }
227}
diff --git a/src/tools/Dtf/Inventory/products.cs b/src/tools/Dtf/Inventory/products.cs
new file mode 100644
index 00000000..635e5439
--- /dev/null
+++ b/src/tools/Dtf/Inventory/products.cs
@@ -0,0 +1,145 @@
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.Data;
5using System.Globalization;
6using System.Collections;
7using System.Windows.Forms;
8using WixToolset.Dtf.WindowsInstaller;
9
10namespace WixToolset.Dtf.Tools.Inventory
11{
12 /// <summary>
13 /// Provides inventory data about products installed or advertised on the system.
14 /// </summary>
15 public class ProductsInventory : IInventoryDataProvider
16 {
17 public ProductsInventory()
18 {
19 }
20
21 public string Description
22 {
23 get { return "Installed products"; }
24 }
25
26 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
27 {
28 statusCallback(0, "Products");
29 ArrayList nodes = new ArrayList();
30 nodes.Add("Products");
31 foreach(ProductInstallation product in ProductInstallation.AllProducts)
32 {
33 nodes.Add("Products\\" + MsiUtils.GetProductName(product.ProductCode));
34 }
35 statusCallback(nodes.Count, String.Empty);
36 return (string[]) nodes.ToArray(typeof(string));
37 }
38
39 public bool IsNodeSearchable(string searchRoot, string searchNode)
40 {
41 return true;
42 }
43
44 public DataView GetData(string nodePath)
45 {
46 string[] path = nodePath.Split('\\');
47
48 if(path.Length == 1 && path[0] == "Products")
49 {
50 return this.GetAllProductsData();
51 }
52 else if(path.Length == 2 && path[0] == "Products")
53 {
54 return this.GetProductData(MsiUtils.GetProductCode(path[1]));
55 }
56 return null;
57 }
58
59 private DataView GetAllProductsData()
60 {
61 DataTable table = new DataTable("Products");
62 table.Locale = CultureInfo.InvariantCulture;
63 table.Columns.Add("ProductsProductName", typeof(string));
64 table.Columns.Add("ProductsProductCode", typeof(string));
65
66 foreach (ProductInstallation product in ProductInstallation.AllProducts)
67 {
68 string productName = MsiUtils.GetProductName(product.ProductCode);
69 table.Rows.Add(new object[] { productName, product.ProductCode });
70 }
71 return new DataView(table, "", "ProductsProductName ASC", DataViewRowState.CurrentRows);
72 }
73
74 private DataView GetProductData(string productCode)
75 {
76 DataTable table = new DataTable("ProductProperties");
77 table.Locale = CultureInfo.InvariantCulture;
78 table.Columns.Add("ProductPropertiesProperty", typeof(string));
79 table.Columns.Add("ProductPropertiesValue", typeof(string));
80
81 // Add a fake "ProductCode" install property, just for display convenience.
82 table.Rows.Add(new object[] { "ProductCode", productCode });
83
84 ProductInstallation product = new ProductInstallation(productCode);
85
86 foreach(string property in new string[]
87 {
88 "AssignmentType",
89 "DiskPrompt",
90 "HelpLink",
91 "HelpTelephone",
92 "InstalledProductName",
93 "InstallDate",
94 "InstallLocation",
95 "InstallSource",
96 "Language",
97 "LastUsedSource",
98 "LastUsedType",
99 "LocalPackage",
100 "MediaPackagePath",
101 "PackageCode",
102 "PackageName",
103 "ProductIcon",
104 "ProductID",
105 "ProductName",
106 "Publisher",
107 "RegCompany",
108 "RegOwner",
109 "State",
110 "transforms",
111 "Uninstallable",
112 "UrlInfoAbout",
113 "UrlUpdateInfo",
114 "Version",
115 "VersionMinor",
116 "VersionMajor",
117 "VersionString"
118 })
119 {
120 try
121 {
122 string value = product[property];
123 table.Rows.Add(new object[] { property, (value != null ? value : "") });
124 }
125 catch(InstallerException iex)
126 {
127 table.Rows.Add(new object[] { property, iex.Message });
128 }
129 catch(ArgumentException) { }
130 }
131 return new DataView(table, "", "ProductPropertiesProperty ASC", DataViewRowState.CurrentRows);
132 }
133
134 public string GetLink(string nodePath, DataRow row)
135 {
136 string[] path = nodePath.Split('\\');
137
138 if(path.Length == 1 && path[0] == "Products")
139 {
140 return String.Format(@"Products\{0}", MsiUtils.GetProductName((string) row["ProductsProductCode"]));
141 }
142 return null;
143 }
144 }
145}
diff --git a/src/tools/Dtf/Inventory/xp.manifest b/src/tools/Dtf/Inventory/xp.manifest
new file mode 100644
index 00000000..34d61fea
--- /dev/null
+++ b/src/tools/Dtf/Inventory/xp.manifest
@@ -0,0 +1,15 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
2<!-- 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. -->
3
4
5<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 <dependency>
7 <dependentAssembly>
8 <assemblyIdentity type="win32"
9 name="Microsoft.Windows.Common-Controls"
10 version="6.0.0.0" language="*"
11 processorArchitecture="X86"
12 publicKeyToken="6595b64144ccf1df" />
13 </dependentAssembly>
14 </dependency>
15</assembly>