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