aboutsummaryrefslogtreecommitdiff
path: root/src/ext/VisualStudio/ca/vsca.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext/VisualStudio/ca/vsca.cpp')
-rw-r--r--src/ext/VisualStudio/ca/vsca.cpp510
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
5typedef HRESULT (WINAPI *PFN_PROCESS_INSTANCE)(
6 __in_opt ISetupInstance* pInstance,
7 __in DWORD64 qwVersion,
8 __in BOOL fComplete
9 );
10
11struct VS_INSTANCE
12{
13 DWORD64 qwMinVersion;
14 DWORD64 qwMaxVersion;
15 PFN_PROCESS_INSTANCE pfnProcessInstance;
16};
17
18struct VS_COMPONENT_PROPERTY
19{
20 LPCWSTR pwzComponent;
21 LPCWSTR pwzProperty;
22};
23
24static HRESULT InstanceInProducts(
25 __in ISetupInstance* pInstance,
26 __in DWORD cProducts,
27 __in LPCWSTR* rgwzProducts
28 );
29
30static HRESULT InstanceIsGreater(
31 __in_opt ISetupInstance* pPreviousInstance,
32 __in DWORD64 qwPreviousVersion,
33 __in ISetupInstance* pCurrentInstance,
34 __in DWORD64 qwCurrentVersion
35 );
36
37static HRESULT ProcessInstance(
38 __in ISetupInstance* pInstance,
39 __in LPCWSTR wzProperty,
40 __in DWORD cComponents,
41 __in VS_COMPONENT_PROPERTY* rgComponents
42 );
43
44static HRESULT ProcessVS2017(
45 __in_opt ISetupInstance* pInstance,
46 __in DWORD64 qwVersion,
47 __in BOOL fComplete
48 );
49
50static HRESULT ProcessVS2019(
51 __in_opt ISetupInstance* pInstance,
52 __in DWORD64 qwVersion,
53 __in BOOL fComplete
54 );
55
56static HRESULT SetPropertyForComponent(
57 __in DWORD cComponents,
58 __in VS_COMPONENT_PROPERTY* rgComponents,
59 __in LPCWSTR wzComponent
60 );
61
62static 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*******************************************************************/
72extern "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
164LExit:
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
184static 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
224LExit:
225 ReleaseBSTR(bstrId);
226 ReleaseObject(pProduct);
227 ReleaseObject(pInstance2);
228
229 return hr;
230}
231
232static 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
256LExit:
257 return hr;
258}
259
260static 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
320LExit:
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
340static 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
405LExit:
406 if (fComplete)
407 {
408 ReleaseObject(pLatest);
409 }
410
411 return hr;
412}
413
414static 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
479LExit:
480 if (fComplete)
481 {
482 ReleaseObject(pLatest);
483 }
484
485 return hr;
486}
487
488static 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
508LExit:
509 return hr;
510}