diff options
Diffstat (limited to 'src/ext/VisualStudio/ca/vsca.cpp')
-rw-r--r-- | src/ext/VisualStudio/ca/vsca.cpp | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/src/ext/VisualStudio/ca/vsca.cpp b/src/ext/VisualStudio/ca/vsca.cpp new file mode 100644 index 00000000..54a54a34 --- /dev/null +++ b/src/ext/VisualStudio/ca/vsca.cpp | |||
@@ -0,0 +1,510 @@ | |||
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 | #include "precomp.h" | ||
4 | |||
5 | typedef HRESULT (WINAPI *PFN_PROCESS_INSTANCE)( | ||
6 | __in_opt ISetupInstance* pInstance, | ||
7 | __in DWORD64 qwVersion, | ||
8 | __in BOOL fComplete | ||
9 | ); | ||
10 | |||
11 | struct VS_INSTANCE | ||
12 | { | ||
13 | DWORD64 qwMinVersion; | ||
14 | DWORD64 qwMaxVersion; | ||
15 | PFN_PROCESS_INSTANCE pfnProcessInstance; | ||
16 | }; | ||
17 | |||
18 | struct VS_COMPONENT_PROPERTY | ||
19 | { | ||
20 | LPCWSTR pwzComponent; | ||
21 | LPCWSTR pwzProperty; | ||
22 | }; | ||
23 | |||
24 | static HRESULT InstanceInProducts( | ||
25 | __in ISetupInstance* pInstance, | ||
26 | __in DWORD cProducts, | ||
27 | __in LPCWSTR* rgwzProducts | ||
28 | ); | ||
29 | |||
30 | static HRESULT InstanceIsGreater( | ||
31 | __in_opt ISetupInstance* pPreviousInstance, | ||
32 | __in DWORD64 qwPreviousVersion, | ||
33 | __in ISetupInstance* pCurrentInstance, | ||
34 | __in DWORD64 qwCurrentVersion | ||
35 | ); | ||
36 | |||
37 | static HRESULT ProcessInstance( | ||
38 | __in ISetupInstance* pInstance, | ||
39 | __in LPCWSTR wzProperty, | ||
40 | __in DWORD cComponents, | ||
41 | __in VS_COMPONENT_PROPERTY* rgComponents | ||
42 | ); | ||
43 | |||
44 | static HRESULT ProcessVS2017( | ||
45 | __in_opt ISetupInstance* pInstance, | ||
46 | __in DWORD64 qwVersion, | ||
47 | __in BOOL fComplete | ||
48 | ); | ||
49 | |||
50 | static HRESULT ProcessVS2019( | ||
51 | __in_opt ISetupInstance* pInstance, | ||
52 | __in DWORD64 qwVersion, | ||
53 | __in BOOL fComplete | ||
54 | ); | ||
55 | |||
56 | static HRESULT SetPropertyForComponent( | ||
57 | __in DWORD cComponents, | ||
58 | __in VS_COMPONENT_PROPERTY* rgComponents, | ||
59 | __in LPCWSTR wzComponent | ||
60 | ); | ||
61 | |||
62 | static VS_INSTANCE vrgInstances[] = | ||
63 | { | ||
64 | { FILEMAKEVERSION(15, 0, 0, 0), FILEMAKEVERSION(15, 0xffff, 0xffff, 0xffff), ProcessVS2017 }, | ||
65 | { FILEMAKEVERSION(16, 0, 0, 0), FILEMAKEVERSION(16, 0xffff, 0xffff, 0xffff), ProcessVS2019 }, | ||
66 | }; | ||
67 | |||
68 | /****************************************************************** | ||
69 | FindInstances - entry point for VS custom action to find instances | ||
70 | |||
71 | *******************************************************************/ | ||
72 | extern "C" UINT __stdcall FindInstances( | ||
73 | __in MSIHANDLE hInstall | ||
74 | ) | ||
75 | { | ||
76 | HRESULT hr = S_OK; | ||
77 | UINT er = ERROR_SUCCESS; | ||
78 | BOOL fComInitialized = FALSE; | ||
79 | ISetupConfiguration* pConfiguration = NULL; | ||
80 | ISetupHelper* pHelper = NULL; | ||
81 | IEnumSetupInstances* pEnumInstances = NULL; | ||
82 | ISetupInstance* rgpInstances[1] = {}; | ||
83 | ISetupInstance* pInstance = NULL; | ||
84 | ULONG cInstancesFetched = 0; | ||
85 | BSTR bstrVersion = NULL; | ||
86 | DWORD64 qwVersion = 0; | ||
87 | |||
88 | hr = WcaInitialize(hInstall, "VSFindInstances"); | ||
89 | ExitOnFailure(hr, "Failed to initialize custom action."); | ||
90 | |||
91 | hr = ::CoInitialize(NULL); | ||
92 | ExitOnFailure(hr, "Failed to initialize COM."); | ||
93 | |||
94 | fComInitialized = TRUE; | ||
95 | |||
96 | hr = ::CoCreateInstance(__uuidof(SetupConfiguration), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pConfiguration)); | ||
97 | if (REGDB_E_CLASSNOTREG != hr) | ||
98 | { | ||
99 | ExitOnFailure(hr, "Failed to initialize setup configuration class."); | ||
100 | } | ||
101 | else | ||
102 | { | ||
103 | WcaLog(LOGMSG_VERBOSE, "Setup configuration not registered; assuming no instances installed."); | ||
104 | |||
105 | hr = S_OK; | ||
106 | ExitFunction(); | ||
107 | } | ||
108 | |||
109 | hr = pConfiguration->QueryInterface(IID_PPV_ARGS(&pHelper)); | ||
110 | if (FAILED(hr)) | ||
111 | { | ||
112 | WcaLog(LOGMSG_VERBOSE, "Setup configuration helpers not implemented; assuming Visual Studio 2017."); | ||
113 | |||
114 | qwVersion = FILEMAKEVERSION(15, 0, 0, 0); | ||
115 | hr = S_OK; | ||
116 | } | ||
117 | |||
118 | hr = pConfiguration->EnumInstances(&pEnumInstances); | ||
119 | ExitOnFailure(hr, "Failed to get instance enumerator."); | ||
120 | |||
121 | do | ||
122 | { | ||
123 | hr = pEnumInstances->Next(1, rgpInstances, &cInstancesFetched); | ||
124 | if (SUCCEEDED(hr) && cInstancesFetched) | ||
125 | { | ||
126 | pInstance = rgpInstances[0]; | ||
127 | if (pInstance) | ||
128 | { | ||
129 | if (pHelper) | ||
130 | { | ||
131 | hr = pInstance->GetInstallationVersion(&bstrVersion); | ||
132 | ExitOnFailure(hr, "Failed to get installation version."); | ||
133 | |||
134 | hr = pHelper->ParseVersion(bstrVersion, &qwVersion); | ||
135 | ExitOnFailure(hr, "Failed to parse installation version."); | ||
136 | } | ||
137 | |||
138 | for (DWORD i = 0; i < countof(vrgInstances); ++i) | ||
139 | { | ||
140 | const VS_INSTANCE* pElem = &vrgInstances[i]; | ||
141 | |||
142 | if (pElem->qwMinVersion <= qwVersion && qwVersion <= pElem->qwMaxVersion) | ||
143 | { | ||
144 | hr = pElem->pfnProcessInstance(pInstance, qwVersion, FALSE); | ||
145 | ExitOnFailure(hr, "Failed to process instance."); | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | |||
150 | ReleaseNullBSTR(bstrVersion); | ||
151 | ReleaseNullObject(pInstance); | ||
152 | } | ||
153 | } while (SUCCEEDED(hr) && cInstancesFetched); | ||
154 | |||
155 | // Complete all registered processing functions. | ||
156 | for (DWORD i = 0; i < countof(vrgInstances); ++i) | ||
157 | { | ||
158 | const VS_INSTANCE* pElem = &vrgInstances[i]; | ||
159 | |||
160 | hr = pElem->pfnProcessInstance(NULL, 0, TRUE); | ||
161 | ExitOnFailure(hr, "Failed to process latest instance."); | ||
162 | } | ||
163 | |||
164 | LExit: | ||
165 | ReleaseBSTR(bstrVersion); | ||
166 | ReleaseObject(pInstance); | ||
167 | ReleaseObject(pEnumInstances); | ||
168 | ReleaseObject(pHelper); | ||
169 | ReleaseObject(pConfiguration); | ||
170 | |||
171 | if (fComInitialized) | ||
172 | { | ||
173 | ::CoUninitialize(); | ||
174 | } | ||
175 | |||
176 | if (FAILED(hr)) | ||
177 | { | ||
178 | er = ERROR_INSTALL_FAILURE; | ||
179 | } | ||
180 | |||
181 | return WcaFinalize(er); | ||
182 | } | ||
183 | |||
184 | static HRESULT InstanceInProducts( | ||
185 | __in ISetupInstance* pInstance, | ||
186 | __in DWORD cProducts, | ||
187 | __in LPCWSTR* rgwzProducts | ||
188 | ) | ||
189 | { | ||
190 | HRESULT hr = S_OK; | ||
191 | ISetupInstance2* pInstance2 = NULL; | ||
192 | ISetupPackageReference* pProduct = NULL; | ||
193 | BSTR bstrId = NULL; | ||
194 | |||
195 | hr = pInstance->QueryInterface(IID_PPV_ARGS(&pInstance2)); | ||
196 | if (FAILED(hr)) | ||
197 | { | ||
198 | // Older implementations shipped when only VS SKUs were supported. | ||
199 | WcaLog(LOGMSG_VERBOSE, "Could not query instance for product information; assuming supported product."); | ||
200 | |||
201 | hr = S_OK; | ||
202 | ExitFunction(); | ||
203 | } | ||
204 | |||
205 | hr = pInstance2->GetProduct(&pProduct); | ||
206 | ExitOnFailure(hr, "Failed to get product package reference."); | ||
207 | |||
208 | hr = pProduct->GetId(&bstrId); | ||
209 | ExitOnFailure(hr, "Failed to get product package ID."); | ||
210 | |||
211 | for (DWORD i = 0; i < cProducts; ++i) | ||
212 | { | ||
213 | const LPCWSTR wzProduct = rgwzProducts[i]; | ||
214 | |||
215 | if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, bstrId, -1, wzProduct, -1)) | ||
216 | { | ||
217 | hr = S_OK; | ||
218 | ExitFunction(); | ||
219 | } | ||
220 | } | ||
221 | |||
222 | hr = S_FALSE; | ||
223 | |||
224 | LExit: | ||
225 | ReleaseBSTR(bstrId); | ||
226 | ReleaseObject(pProduct); | ||
227 | ReleaseObject(pInstance2); | ||
228 | |||
229 | return hr; | ||
230 | } | ||
231 | |||
232 | static HRESULT InstanceIsGreater( | ||
233 | __in_opt ISetupInstance* pPreviousInstance, | ||
234 | __in DWORD64 qwPreviousVersion, | ||
235 | __in ISetupInstance* pCurrentInstance, | ||
236 | __in DWORD64 qwCurrentVersion | ||
237 | ) | ||
238 | { | ||
239 | HRESULT hr = S_OK; | ||
240 | FILETIME ftPreviousInstance = {}; | ||
241 | FILETIME ftCurrentInstance = {}; | ||
242 | |||
243 | if (qwPreviousVersion != qwCurrentVersion) | ||
244 | { | ||
245 | return qwPreviousVersion < qwCurrentVersion ? S_OK : S_FALSE; | ||
246 | } | ||
247 | |||
248 | hr = pPreviousInstance->GetInstallDate(&ftPreviousInstance); | ||
249 | ExitOnFailure(hr, "Failed to get previous install date."); | ||
250 | |||
251 | hr = pCurrentInstance->GetInstallDate(&ftCurrentInstance); | ||
252 | ExitOnFailure(hr, "Failed to get current install date."); | ||
253 | |||
254 | return 0 > ::CompareFileTime(&ftPreviousInstance, &ftCurrentInstance) ? S_OK : S_FALSE; | ||
255 | |||
256 | LExit: | ||
257 | return hr; | ||
258 | } | ||
259 | |||
260 | static HRESULT ProcessInstance( | ||
261 | __in ISetupInstance* pInstance, | ||
262 | __in LPCWSTR wzProperty, | ||
263 | __in DWORD cComponents, | ||
264 | __in VS_COMPONENT_PROPERTY* rgComponents | ||
265 | ) | ||
266 | { | ||
267 | HRESULT hr = S_OK; | ||
268 | ISetupInstance2* pInstance2 = NULL; | ||
269 | BSTR bstrPath = NULL; | ||
270 | LPSAFEARRAY psaPackages = NULL; | ||
271 | LONG lPackageIndex = 0; | ||
272 | LONG clMaxPackages = 0; | ||
273 | ISetupPackageReference** rgpPackages = NULL; | ||
274 | ISetupPackageReference* pPackage = NULL; | ||
275 | BSTR bstrPackageId = NULL; | ||
276 | |||
277 | hr = pInstance->GetInstallationPath(&bstrPath); | ||
278 | ExitOnFailure(hr, "Failed to get installation path."); | ||
279 | |||
280 | hr = WcaSetProperty(wzProperty, bstrPath); | ||
281 | ExitOnFailure(hr, "Failed to set installation path property: %ls", wzProperty); | ||
282 | |||
283 | hr = pInstance->QueryInterface(IID_PPV_ARGS(&pInstance2)); | ||
284 | if (FAILED(hr)) | ||
285 | { | ||
286 | // Older implementation did not expose installed components. | ||
287 | hr = S_OK; | ||
288 | ExitFunction(); | ||
289 | } | ||
290 | |||
291 | hr = pInstance2->GetPackages(&psaPackages); | ||
292 | ExitOnFailure(hr, "Failed to get packages from instance."); | ||
293 | |||
294 | hr = ::SafeArrayGetLBound(psaPackages, 1, &lPackageIndex); | ||
295 | ExitOnFailure(hr, "Failed to get lower bound of packages array."); | ||
296 | |||
297 | hr = ::SafeArrayGetUBound(psaPackages, 1, &clMaxPackages); | ||
298 | ExitOnFailure(hr, "Failed to get upper bound of packages array."); | ||
299 | |||
300 | // Faster access to single dimension SAFEARRAY elements. | ||
301 | hr = ::SafeArrayAccessData(psaPackages, reinterpret_cast<LPVOID*>(&rgpPackages)); | ||
302 | ExitOnFailure(hr, "Failed to access packages array.") | ||
303 | |||
304 | for (; lPackageIndex <= clMaxPackages; ++lPackageIndex) | ||
305 | { | ||
306 | pPackage = rgpPackages[lPackageIndex]; | ||
307 | |||
308 | if (pPackage) | ||
309 | { | ||
310 | hr = pPackage->GetId(&bstrPackageId); | ||
311 | ExitOnFailure(hr, "Failed to get package ID."); | ||
312 | |||
313 | hr = SetPropertyForComponent(cComponents, rgComponents, bstrPackageId); | ||
314 | ExitOnFailure(hr, "Failed to set property for component: %ls", bstrPackageId); | ||
315 | |||
316 | ReleaseNullBSTR(bstrPackageId); | ||
317 | } | ||
318 | } | ||
319 | |||
320 | LExit: | ||
321 | ReleaseBSTR(bstrPackageId); | ||
322 | |||
323 | if (rgpPackages) | ||
324 | { | ||
325 | ::SafeArrayUnaccessData(psaPackages); | ||
326 | } | ||
327 | |||
328 | if (psaPackages) | ||
329 | { | ||
330 | // This will Release() all objects in the array. | ||
331 | ::SafeArrayDestroy(psaPackages); | ||
332 | } | ||
333 | |||
334 | ReleaseObject(pInstance2); | ||
335 | ReleaseBSTR(bstrPath); | ||
336 | |||
337 | return hr; | ||
338 | } | ||
339 | |||
340 | static HRESULT ProcessVS2017( | ||
341 | __in_opt ISetupInstance* pInstance, | ||
342 | __in DWORD64 qwVersion, | ||
343 | __in BOOL fComplete | ||
344 | ) | ||
345 | { | ||
346 | static ISetupInstance* pLatest = NULL; | ||
347 | static DWORD64 qwLatest = 0; | ||
348 | |||
349 | static LPCWSTR rgwzProducts[] = | ||
350 | { | ||
351 | L"Microsoft.VisualStudio.Product.Community", | ||
352 | L"Microsoft.VisualStudio.Product.Professional", | ||
353 | L"Microsoft.VisualStudio.Product.Enterprise", | ||
354 | }; | ||
355 | |||
356 | // TODO: Consider making table-driven with these defaults per-version for easy customization. | ||
357 | static VS_COMPONENT_PROPERTY rgComponents[] = | ||
358 | { | ||
359 | { L"Microsoft.VisualStudio.Component.FSharp", L"VS2017_IDE_FSHARP_PROJECTSYSTEM_INSTALLED" }, | ||
360 | { L"Microsoft.VisualStudio.Component.Roslyn.LanguageServices", L"VS2017_IDE_VB_PROJECTSYSTEM_INSTALLED" }, | ||
361 | { L"Microsoft.VisualStudio.Component.Roslyn.LanguageServices", L"VS2017_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED" }, | ||
362 | { L"Microsoft.VisualStudio.Component.TestTools.Core", L"VS2017_IDE_VSTS_TESTSYSTEM_INSTALLED" }, | ||
363 | { L"Microsoft.VisualStudio.Component.VC.CoreIde", L"VS2017_IDE_VC_PROJECTSYSTEM_INSTALLED" }, | ||
364 | { L"Microsoft.VisualStudio.Component.Web", L"VS2017_IDE_VWD_PROJECTSYSTEM_INSTALLED" }, | ||
365 | { L"Microsoft.VisualStudio.PackageGroup.DslRuntime", L"VS2017_IDE_MODELING_PROJECTSYSTEM_INSTALLED" }, | ||
366 | }; | ||
367 | |||
368 | HRESULT hr = S_OK; | ||
369 | |||
370 | if (fComplete) | ||
371 | { | ||
372 | if (pLatest) | ||
373 | { | ||
374 | hr = ProcessInstance(pLatest, L"VS2017_ROOT_FOLDER", countof(rgComponents), rgComponents); | ||
375 | ExitOnFailure(hr, "Failed to process VS2017 instance."); | ||
376 | } | ||
377 | } | ||
378 | else if (pInstance) | ||
379 | { | ||
380 | hr = InstanceInProducts(pInstance, countof(rgwzProducts), rgwzProducts); | ||
381 | ExitOnFailure(hr, "Failed to compare product IDs."); | ||
382 | |||
383 | if (S_FALSE == hr) | ||
384 | { | ||
385 | ExitFunction(); | ||
386 | } | ||
387 | |||
388 | hr = InstanceIsGreater(pLatest, qwLatest, pInstance, qwVersion); | ||
389 | ExitOnFailure(hr, "Failed to compare instances."); | ||
390 | |||
391 | if (S_FALSE == hr) | ||
392 | { | ||
393 | ExitFunction(); | ||
394 | } | ||
395 | |||
396 | ReleaseNullObject(pLatest); | ||
397 | |||
398 | pLatest = pInstance; | ||
399 | qwLatest = qwVersion; | ||
400 | |||
401 | // Caller will do a final Release() otherwise. | ||
402 | pLatest->AddRef(); | ||
403 | } | ||
404 | |||
405 | LExit: | ||
406 | if (fComplete) | ||
407 | { | ||
408 | ReleaseObject(pLatest); | ||
409 | } | ||
410 | |||
411 | return hr; | ||
412 | } | ||
413 | |||
414 | static HRESULT ProcessVS2019( | ||
415 | __in_opt ISetupInstance* pInstance, | ||
416 | __in DWORD64 qwVersion, | ||
417 | __in BOOL fComplete | ||
418 | ) | ||
419 | { | ||
420 | static ISetupInstance* pLatest = NULL; | ||
421 | static DWORD64 qwLatest = 0; | ||
422 | |||
423 | static LPCWSTR rgwzProducts[] = | ||
424 | { | ||
425 | L"Microsoft.VisualStudio.Product.Community", | ||
426 | L"Microsoft.VisualStudio.Product.Professional", | ||
427 | L"Microsoft.VisualStudio.Product.Enterprise", | ||
428 | }; | ||
429 | |||
430 | // TODO: Consider making table-driven with these defaults per-version for easy customization. | ||
431 | static VS_COMPONENT_PROPERTY rgComponents[] = | ||
432 | { | ||
433 | { L"Microsoft.VisualStudio.Component.FSharp", L"VS2019_IDE_FSHARP_PROJECTSYSTEM_INSTALLED" }, | ||
434 | { L"Microsoft.VisualStudio.Component.Roslyn.LanguageServices", L"VS2019_IDE_VB_PROJECTSYSTEM_INSTALLED" }, | ||
435 | { L"Microsoft.VisualStudio.Component.Roslyn.LanguageServices", L"VS2019_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED" }, | ||
436 | { L"Microsoft.VisualStudio.PackageGroup.TestTools.Core", L"VS2019_IDE_VSTS_TESTSYSTEM_INSTALLED" }, | ||
437 | { L"Microsoft.VisualStudio.Component.VC.CoreIde", L"VS2019_IDE_VC_PROJECTSYSTEM_INSTALLED" }, | ||
438 | { L"Microsoft.VisualStudio.Component.Web", L"VS2019_IDE_VWD_PROJECTSYSTEM_INSTALLED" }, | ||
439 | { L"Microsoft.VisualStudio.PackageGroup.DslRuntime", L"VS2019_IDE_MODELING_PROJECTSYSTEM_INSTALLED" }, | ||
440 | }; | ||
441 | |||
442 | HRESULT hr = S_OK; | ||
443 | |||
444 | if (fComplete) | ||
445 | { | ||
446 | if (pLatest) | ||
447 | { | ||
448 | hr = ProcessInstance(pLatest, L"VS2019_ROOT_FOLDER", countof(rgComponents), rgComponents); | ||
449 | ExitOnFailure(hr, "Failed to process VS2019 instance."); | ||
450 | } | ||
451 | } | ||
452 | else if (pInstance) | ||
453 | { | ||
454 | hr = InstanceInProducts(pInstance, countof(rgwzProducts), rgwzProducts); | ||
455 | ExitOnFailure(hr, "Failed to compare product IDs."); | ||
456 | |||
457 | if (S_FALSE == hr) | ||
458 | { | ||
459 | ExitFunction(); | ||
460 | } | ||
461 | |||
462 | hr = InstanceIsGreater(pLatest, qwLatest, pInstance, qwVersion); | ||
463 | ExitOnFailure(hr, "Failed to compare instances."); | ||
464 | |||
465 | if (S_FALSE == hr) | ||
466 | { | ||
467 | ExitFunction(); | ||
468 | } | ||
469 | |||
470 | ReleaseNullObject(pLatest); | ||
471 | |||
472 | pLatest = pInstance; | ||
473 | qwLatest = qwVersion; | ||
474 | |||
475 | // Caller will do a final Release() otherwise. | ||
476 | pLatest->AddRef(); | ||
477 | } | ||
478 | |||
479 | LExit: | ||
480 | if (fComplete) | ||
481 | { | ||
482 | ReleaseObject(pLatest); | ||
483 | } | ||
484 | |||
485 | return hr; | ||
486 | } | ||
487 | |||
488 | static HRESULT SetPropertyForComponent( | ||
489 | __in DWORD cComponents, | ||
490 | __in VS_COMPONENT_PROPERTY* rgComponents, | ||
491 | __in LPCWSTR wzComponent | ||
492 | ) | ||
493 | { | ||
494 | HRESULT hr = S_OK; | ||
495 | |||
496 | // For small arrays, faster looping through than hashing. There may also be duplicates like with VS2017. | ||
497 | for (DWORD i = 0; i < cComponents; ++i) | ||
498 | { | ||
499 | const VS_COMPONENT_PROPERTY* pComponent = &rgComponents[i]; | ||
500 | |||
501 | if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pComponent->pwzComponent, -1, wzComponent, -1)) | ||
502 | { | ||
503 | hr = WcaSetIntProperty(pComponent->pwzProperty, 1); | ||
504 | ExitOnFailure(hr, "Failed to set property: %ls", pComponent->pwzProperty); | ||
505 | } | ||
506 | } | ||
507 | |||
508 | LExit: | ||
509 | return hr; | ||
510 | } | ||