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