diff options
Diffstat (limited to '')
-rw-r--r-- | src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs new file mode 100644 index 00000000..defbf64a --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs | |||
@@ -0,0 +1,413 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.WindowsInstaller | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | |||
11 | /// <summary> | ||
12 | /// The Patch object represents a unique instance of a patch that has been | ||
13 | /// registered or applied. | ||
14 | /// </summary> | ||
15 | public class PatchInstallation : Installation | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// Enumerates all patch installations on the system. | ||
19 | /// </summary> | ||
20 | /// <returns>Enumeration of patch objects.</returns> | ||
21 | /// <remarks><p> | ||
22 | /// Win32 MSI API: | ||
23 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumpatches.asp">MsiEnumPatches</a> | ||
24 | /// </p></remarks> | ||
25 | public static IEnumerable<PatchInstallation> AllPatches | ||
26 | { | ||
27 | get | ||
28 | { | ||
29 | return PatchInstallation.GetPatches(null, null, null, UserContexts.All, PatchStates.All); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | /// <summary> | ||
34 | /// Enumerates patch installations based on certain criteria. | ||
35 | /// </summary> | ||
36 | /// <param name="patchCode">PatchCode (GUID) of the patch to be enumerated. Only | ||
37 | /// instances of patches within the scope of the context specified by the | ||
38 | /// <paramref name="userSid"/> and <paramref name="context"/> parameters will be | ||
39 | /// enumerated. This parameter may be set to null to enumerate all patches in the specified | ||
40 | /// context.</param> | ||
41 | /// <param name="targetProductCode">ProductCode (GUID) product whose patches are to be | ||
42 | /// enumerated. If non-null, patch enumeration is restricted to instances of this product | ||
43 | /// within the specified context. If null, the patches for all products under the specified | ||
44 | /// context are enumerated.</param> | ||
45 | /// <param name="userSid">Specifies a security identifier (SID) that restricts the context | ||
46 | /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts | ||
47 | /// enumeration to the current user or any user in the system. The special SID string | ||
48 | /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter | ||
49 | /// can be set to null to restrict the enumeration scope to the current user. When | ||
50 | /// <paramref name="userSid"/> must be null.</param> | ||
51 | /// <param name="context">Specifies the user context.</param> | ||
52 | /// <param name="states">The <see cref="PatchStates"/> of patches to return.</param> | ||
53 | /// <remarks><p> | ||
54 | /// Win32 MSI APIs: | ||
55 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumpatchesex.asp">MsiEnumPatchesEx</a> | ||
56 | /// </p></remarks> | ||
57 | public static IEnumerable<PatchInstallation> GetPatches( | ||
58 | string patchCode, | ||
59 | string targetProductCode, | ||
60 | string userSid, | ||
61 | UserContexts context, | ||
62 | PatchStates states) | ||
63 | { | ||
64 | StringBuilder buf = new StringBuilder(40); | ||
65 | StringBuilder targetProductBuf = new StringBuilder(40); | ||
66 | UserContexts targetContext; | ||
67 | StringBuilder targetSidBuf = new StringBuilder(40); | ||
68 | for (uint i = 0; ; i++) | ||
69 | { | ||
70 | uint targetSidBufSize = (uint) targetSidBuf.Capacity; | ||
71 | uint ret = NativeMethods.MsiEnumPatchesEx( | ||
72 | targetProductCode, | ||
73 | userSid, | ||
74 | context, | ||
75 | (uint) states, | ||
76 | i, | ||
77 | buf, | ||
78 | targetProductBuf, | ||
79 | out targetContext, | ||
80 | targetSidBuf, | ||
81 | ref targetSidBufSize); | ||
82 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
83 | { | ||
84 | targetSidBuf.Capacity = (int) ++targetSidBufSize; | ||
85 | ret = NativeMethods.MsiEnumPatchesEx( | ||
86 | targetProductCode, | ||
87 | userSid, | ||
88 | context, | ||
89 | (uint) states, | ||
90 | i, | ||
91 | buf, | ||
92 | targetProductBuf, | ||
93 | out targetContext, | ||
94 | targetSidBuf, | ||
95 | ref targetSidBufSize); | ||
96 | } | ||
97 | |||
98 | if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) | ||
99 | { | ||
100 | break; | ||
101 | } | ||
102 | |||
103 | if (ret != 0) | ||
104 | { | ||
105 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
106 | } | ||
107 | |||
108 | string thisPatchCode = buf.ToString(); | ||
109 | if (patchCode == null || patchCode == thisPatchCode) | ||
110 | { | ||
111 | yield return new PatchInstallation( | ||
112 | buf.ToString(), | ||
113 | targetProductBuf.ToString(), | ||
114 | targetSidBuf.ToString(), | ||
115 | targetContext); | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | |||
120 | private string productCode; | ||
121 | |||
122 | /// <summary> | ||
123 | /// Creates a new object for accessing information about a patch installation on the current system. | ||
124 | /// </summary> | ||
125 | /// <param name="patchCode">Patch code (GUID) of the patch.</param> | ||
126 | /// <param name="productCode">ProductCode (GUID) the patch has been applied to. | ||
127 | /// This parameter may be null for patches that are registered only and not yet | ||
128 | /// applied to any product.</param> | ||
129 | /// <remarks><p> | ||
130 | /// All available user contexts will be queried. | ||
131 | /// </p></remarks> | ||
132 | public PatchInstallation(string patchCode, string productCode) | ||
133 | : this(patchCode, productCode, null, UserContexts.All) | ||
134 | { | ||
135 | } | ||
136 | |||
137 | /// <summary> | ||
138 | /// Creates a new object for accessing information about a patch installation on the current system. | ||
139 | /// </summary> | ||
140 | /// <param name="patchCode">Registered patch code (GUID) of the patch.</param> | ||
141 | /// <param name="productCode">ProductCode (GUID) the patch has been applied to. | ||
142 | /// This parameter may be null for patches that are registered only and not yet | ||
143 | /// applied to any product.</param> | ||
144 | /// <param name="userSid">The specific user, when working in a user context. This | ||
145 | /// parameter may be null to indicate the current user. The parameter must be null | ||
146 | /// when working in a machine context.</param> | ||
147 | /// <param name="context">The user context. The calling process must have administrative | ||
148 | /// privileges to get information for a product installed for a user other than the | ||
149 | /// current user.</param> | ||
150 | /// <remarks><p> | ||
151 | /// If the <paramref name="productCode"/> is null, the Patch object may | ||
152 | /// only be used to read and update the patch's SourceList information. | ||
153 | /// </p></remarks> | ||
154 | public PatchInstallation(string patchCode, string productCode, string userSid, UserContexts context) | ||
155 | : base(patchCode, userSid, context) | ||
156 | { | ||
157 | if (String.IsNullOrEmpty(patchCode)) | ||
158 | { | ||
159 | throw new ArgumentNullException("patchCode"); | ||
160 | } | ||
161 | |||
162 | this.productCode = productCode; | ||
163 | } | ||
164 | |||
165 | /// <summary> | ||
166 | /// Gets the patch code (GUID) of the patch. | ||
167 | /// </summary> | ||
168 | public string PatchCode | ||
169 | { | ||
170 | get | ||
171 | { | ||
172 | return this.InstallationCode; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | /// <summary> | ||
177 | /// Gets the ProductCode (GUID) of the product. | ||
178 | /// </summary> | ||
179 | public string ProductCode | ||
180 | { | ||
181 | get | ||
182 | { | ||
183 | return this.productCode; | ||
184 | } | ||
185 | } | ||
186 | |||
187 | /// <summary> | ||
188 | /// Gets a value indicating whether this patch is currently installed. | ||
189 | /// </summary> | ||
190 | public override bool IsInstalled | ||
191 | { | ||
192 | get | ||
193 | { | ||
194 | return (this.State & PatchStates.Applied) != 0; | ||
195 | } | ||
196 | } | ||
197 | |||
198 | /// <summary> | ||
199 | /// Gets a value indicating whether this patch is marked as obsolte. | ||
200 | /// </summary> | ||
201 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Obsoleted")] | ||
202 | public bool IsObsoleted | ||
203 | { | ||
204 | get | ||
205 | { | ||
206 | return (this.State & PatchStates.Obsoleted) != 0; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | /// <summary> | ||
211 | /// Gets a value indicating whether this patch is present but has been | ||
212 | /// superseded by a more recent installed patch. | ||
213 | /// </summary> | ||
214 | public bool IsSuperseded | ||
215 | { | ||
216 | get | ||
217 | { | ||
218 | return (this.State & PatchStates.Superseded) != 0; | ||
219 | } | ||
220 | } | ||
221 | |||
222 | internal override int InstallationType | ||
223 | { | ||
224 | get | ||
225 | { | ||
226 | const int MSICODE_PATCH = 0x40000000; | ||
227 | return MSICODE_PATCH; | ||
228 | } | ||
229 | } | ||
230 | |||
231 | /// <summary> | ||
232 | /// Gets the installation state of this instance of the patch. | ||
233 | /// </summary> | ||
234 | /// <exception cref="ArgumentException">An unknown patch was requested</exception> | ||
235 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
236 | public PatchStates State | ||
237 | { | ||
238 | get | ||
239 | { | ||
240 | string stateString = this["State"]; | ||
241 | return (PatchStates) Int32.Parse(stateString, CultureInfo.InvariantCulture.NumberFormat); | ||
242 | } | ||
243 | } | ||
244 | |||
245 | /// <summary> | ||
246 | /// Gets the cached patch file that the product uses. | ||
247 | /// </summary> | ||
248 | public string LocalPackage | ||
249 | { | ||
250 | get | ||
251 | { | ||
252 | return this["LocalPackage"]; | ||
253 | } | ||
254 | } | ||
255 | |||
256 | /// <summary> | ||
257 | /// Gets the set of patch transforms that the last patch | ||
258 | /// installation applied to the product. | ||
259 | /// </summary> | ||
260 | /// <remarks><p> | ||
261 | /// This value may not be available for per-user, non-managed applications | ||
262 | /// if the user is not logged on. | ||
263 | /// </p></remarks> | ||
264 | public string Transforms | ||
265 | { | ||
266 | get | ||
267 | { | ||
268 | // TODO: convert to IList<string>? | ||
269 | return this["Transforms"]; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | /// <summary> | ||
274 | /// Gets the date and time when the patch is applied to the product. | ||
275 | /// </summary> | ||
276 | public DateTime InstallDate | ||
277 | { | ||
278 | get | ||
279 | { | ||
280 | try | ||
281 | { | ||
282 | return DateTime.ParseExact( | ||
283 | this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture); | ||
284 | } | ||
285 | catch (FormatException) | ||
286 | { | ||
287 | return DateTime.MinValue; | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | |||
292 | /// <summary> | ||
293 | /// True patch is marked as possible to uninstall from the product. | ||
294 | /// </summary> | ||
295 | /// <remarks><p> | ||
296 | /// Even if this property is true, the installer can still block the | ||
297 | /// uninstallation if this patch is required by another patch that | ||
298 | /// cannot be uninstalled. | ||
299 | /// </p></remarks> | ||
300 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Uninstallable")] | ||
301 | public bool Uninstallable | ||
302 | { | ||
303 | get | ||
304 | { | ||
305 | return this["Uninstallable"] == "1"; | ||
306 | } | ||
307 | } | ||
308 | |||
309 | /// <summary> | ||
310 | /// Get the registered display name for the patch. | ||
311 | /// </summary> | ||
312 | public string DisplayName | ||
313 | { | ||
314 | get | ||
315 | { | ||
316 | return this["DisplayName"]; | ||
317 | } | ||
318 | } | ||
319 | |||
320 | /// <summary> | ||
321 | /// Gets the registered support information URL for the patch. | ||
322 | /// </summary> | ||
323 | public Uri MoreInfoUrl | ||
324 | { | ||
325 | get | ||
326 | { | ||
327 | string value = this["MoreInfoURL"]; | ||
328 | if (!String.IsNullOrEmpty(value)) | ||
329 | { | ||
330 | try | ||
331 | { | ||
332 | return new Uri(value); | ||
333 | } | ||
334 | catch (UriFormatException) { } | ||
335 | } | ||
336 | |||
337 | return null; | ||
338 | } | ||
339 | } | ||
340 | |||
341 | /// <summary> | ||
342 | /// Gets information about a specific patch installation. | ||
343 | /// </summary> | ||
344 | /// <param name="propertyName">The property being retrieved; see remarks for valid properties.</param> | ||
345 | /// <returns>The property value, or an empty string if the property is not set for the patch.</returns> | ||
346 | /// <exception cref="ArgumentOutOfRangeException">An unknown patch or property was requested</exception> | ||
347 | /// <exception cref="InstallerException">The installer configuration data is corrupt</exception> | ||
348 | /// <remarks><p> | ||
349 | /// Win32 MSI APIs: | ||
350 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchinfo.asp">MsiGetPatchInfo</a>, | ||
351 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchinfoex.asp">MsiGetPatchInfoEx</a> | ||
352 | /// </p></remarks> | ||
353 | public override string this[string propertyName] | ||
354 | { | ||
355 | get | ||
356 | { | ||
357 | StringBuilder buf = new StringBuilder(""); | ||
358 | uint bufSize = 0; | ||
359 | uint ret; | ||
360 | |||
361 | if (this.Context == UserContexts.UserManaged || | ||
362 | this.Context == UserContexts.UserUnmanaged || | ||
363 | this.Context == UserContexts.Machine) | ||
364 | { | ||
365 | ret = NativeMethods.MsiGetPatchInfoEx( | ||
366 | this.PatchCode, | ||
367 | this.ProductCode, | ||
368 | this.UserSid, | ||
369 | this.Context, | ||
370 | propertyName, | ||
371 | buf, | ||
372 | ref bufSize); | ||
373 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
374 | { | ||
375 | buf.Capacity = (int) ++bufSize; | ||
376 | ret = NativeMethods.MsiGetPatchInfoEx( | ||
377 | this.PatchCode, | ||
378 | this.ProductCode, | ||
379 | this.UserSid, | ||
380 | this.Context, | ||
381 | propertyName, | ||
382 | buf, | ||
383 | ref bufSize); | ||
384 | } | ||
385 | } | ||
386 | else | ||
387 | { | ||
388 | ret = NativeMethods.MsiGetPatchInfo( | ||
389 | this.PatchCode, | ||
390 | propertyName, | ||
391 | buf, | ||
392 | ref bufSize); | ||
393 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
394 | { | ||
395 | buf.Capacity = (int) ++bufSize; | ||
396 | ret = NativeMethods.MsiGetPatchInfo( | ||
397 | this.PatchCode, | ||
398 | propertyName, | ||
399 | buf, | ||
400 | ref bufSize); | ||
401 | } | ||
402 | } | ||
403 | |||
404 | if (ret != 0) | ||
405 | { | ||
406 | return null; | ||
407 | } | ||
408 | |||
409 | return buf.ToString(); | ||
410 | } | ||
411 | } | ||
412 | } | ||
413 | } | ||