1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmVSSetupHelper.h"
4
5 #include "cmsys/Encoding.hxx"
6 #include "cmsys/FStream.hxx"
7
8 #include "cmStringAlgorithms.h"
9 #include "cmSystemTools.h"
10
11 #ifndef VSSetupConstants
12 # define VSSetupConstants
13 /* clang-format off */
14 const IID IID_ISetupConfiguration = {
15 0x42843719, 0xDB4C, 0x46C2,
16 { 0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B }
17 };
18 const IID IID_ISetupConfiguration2 = {
19 0x26AAB78C, 0x4A60, 0x49D6,
20 { 0xAF, 0x3B, 0x3C, 0x35, 0xBC, 0x93, 0x36, 0x5D }
21 };
22 const IID IID_ISetupPackageReference = {
23 0xda8d8a16, 0xb2b6, 0x4487,
24 { 0xa2, 0xf1, 0x59, 0x4c, 0xcc, 0xcd, 0x6b, 0xf5 }
25 };
26 const IID IID_ISetupHelper = {
27 0x42b21b78, 0x6192, 0x463e,
28 { 0x87, 0xbf, 0xd5, 0x77, 0x83, 0x8f, 0x1d, 0x5c }
29 };
30 const IID IID_IEnumSetupInstances = {
31 0x6380BCFF, 0x41D3, 0x4B2E,
32 { 0x8B, 0x2E, 0xBF, 0x8A, 0x68, 0x10, 0xC8, 0x48 }
33 };
34 const IID IID_ISetupInstance2 = {
35 0x89143C9A, 0x05AF, 0x49B0,
36 { 0xB7, 0x17, 0x72, 0xE2, 0x18, 0xA2, 0x18, 0x5C }
37 };
38 const IID IID_ISetupInstance = {
39 0xB41463C3, 0x8866, 0x43B5,
40 { 0xBC, 0x33, 0x2B, 0x06, 0x76, 0xF7, 0xF4, 0x2E }
41 };
42 const CLSID CLSID_SetupConfiguration = {
43 0x177F0C4A, 0x1CD3, 0x4DE7,
44 { 0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D }
45 };
46 /* clang-format on */
47 #endif
48
49 const WCHAR* Win10SDKComponent =
50 L"Microsoft.VisualStudio.Component.Windows10SDK";
51 const WCHAR* Win81SDKComponent =
52 L"Microsoft.VisualStudio.Component.Windows81SDK";
53 const WCHAR* ComponentType = L"Component";
54
GetInstallLocation() const55 std::string VSInstanceInfo::GetInstallLocation() const
56 {
57 std::string loc = cmsys::Encoding::ToNarrow(this->VSInstallLocation);
58 cmSystemTools::ConvertToUnixSlashes(loc);
59 return loc;
60 }
61
cmVSSetupAPIHelper(unsigned int version)62 cmVSSetupAPIHelper::cmVSSetupAPIHelper(unsigned int version)
63 : Version(version)
64 , setupConfig(NULL)
65 , setupConfig2(NULL)
66 , setupHelper(NULL)
67 , initializationFailure(false)
68 {
69 comInitialized = CoInitializeEx(NULL, 0);
70 if (SUCCEEDED(comInitialized)) {
71 Initialize();
72 } else {
73 initializationFailure = true;
74 }
75 }
76
~cmVSSetupAPIHelper()77 cmVSSetupAPIHelper::~cmVSSetupAPIHelper()
78 {
79 setupHelper = NULL;
80 setupConfig2 = NULL;
81 setupConfig = NULL;
82 if (SUCCEEDED(comInitialized))
83 CoUninitialize();
84 }
85
SetVSInstance(std::string const & vsInstallLocation)86 bool cmVSSetupAPIHelper::SetVSInstance(std::string const& vsInstallLocation)
87 {
88 this->SpecifiedVSInstallLocation = vsInstallLocation;
89 cmSystemTools::ConvertToUnixSlashes(this->SpecifiedVSInstallLocation);
90 chosenInstanceInfo = VSInstanceInfo();
91 return this->EnumerateAndChooseVSInstance();
92 }
93
IsVSInstalled()94 bool cmVSSetupAPIHelper::IsVSInstalled()
95 {
96 return this->EnumerateAndChooseVSInstance();
97 }
98
IsWin10SDKInstalled()99 bool cmVSSetupAPIHelper::IsWin10SDKInstalled()
100 {
101 return (this->EnumerateAndChooseVSInstance() &&
102 chosenInstanceInfo.IsWin10SDKInstalled);
103 }
104
IsWin81SDKInstalled()105 bool cmVSSetupAPIHelper::IsWin81SDKInstalled()
106 {
107 return (this->EnumerateAndChooseVSInstance() &&
108 chosenInstanceInfo.IsWin81SDKInstalled);
109 }
110
CheckInstalledComponent(SmartCOMPtr<ISetupPackageReference> package,bool & bWin10SDK,bool & bWin81SDK)111 bool cmVSSetupAPIHelper::CheckInstalledComponent(
112 SmartCOMPtr<ISetupPackageReference> package, bool& bWin10SDK,
113 bool& bWin81SDK)
114 {
115 bool ret = false;
116 bWin10SDK = bWin81SDK = false;
117 SmartBSTR bstrId;
118 if (FAILED(package->GetId(&bstrId))) {
119 return ret;
120 }
121
122 SmartBSTR bstrType;
123 if (FAILED(package->GetType(&bstrType))) {
124 return ret;
125 }
126
127 std::wstring id = std::wstring(bstrId);
128 std::wstring type = std::wstring(bstrType);
129
130 // Checks for any version of Win10 SDK. The version is appended at the end of
131 // the
132 // component name ex: Microsoft.VisualStudio.Component.Windows10SDK.10240
133 if (id.find(Win10SDKComponent) != std::wstring::npos &&
134 type.compare(ComponentType) == 0) {
135 bWin10SDK = true;
136 ret = true;
137 }
138
139 if (id.compare(Win81SDKComponent) == 0 && type.compare(ComponentType) == 0) {
140 bWin81SDK = true;
141 ret = true;
142 }
143
144 return ret;
145 }
146
147 // Gather additional info such as if VCToolset, WinSDKs are installed, location
148 // of VS and version information.
GetVSInstanceInfo(SmartCOMPtr<ISetupInstance2> pInstance,VSInstanceInfo & vsInstanceInfo)149 bool cmVSSetupAPIHelper::GetVSInstanceInfo(
150 SmartCOMPtr<ISetupInstance2> pInstance, VSInstanceInfo& vsInstanceInfo)
151 {
152 if (pInstance == NULL)
153 return false;
154
155 SmartBSTR bstrId;
156 if (SUCCEEDED(pInstance->GetInstanceId(&bstrId))) {
157 vsInstanceInfo.InstanceId = std::wstring(bstrId);
158 } else {
159 return false;
160 }
161
162 InstanceState state;
163 if (FAILED(pInstance->GetState(&state))) {
164 return false;
165 }
166
167 ULONGLONG ullVersion = 0;
168 SmartBSTR bstrVersion;
169 if (FAILED(pInstance->GetInstallationVersion(&bstrVersion))) {
170 return false;
171 } else {
172 vsInstanceInfo.Version = std::wstring(bstrVersion);
173 if (FAILED(setupHelper->ParseVersion(bstrVersion, &ullVersion))) {
174 vsInstanceInfo.ullVersion = 0;
175 } else {
176 vsInstanceInfo.ullVersion = ullVersion;
177 }
178 }
179
180 // Reboot may have been required before the installation path was created.
181 SmartBSTR bstrInstallationPath;
182 if ((eLocal & state) == eLocal) {
183 if (FAILED(pInstance->GetInstallationPath(&bstrInstallationPath))) {
184 return false;
185 } else {
186 vsInstanceInfo.VSInstallLocation = std::wstring(bstrInstallationPath);
187 }
188 }
189
190 // Check if a compiler is installed with this instance.
191 {
192 std::string const vcRoot = vsInstanceInfo.GetInstallLocation();
193 std::string vcToolsVersionFile =
194 vcRoot + "/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt";
195 std::string vcToolsVersion;
196 cmsys::ifstream fin(vcToolsVersionFile.c_str());
197 if (!fin || !cmSystemTools::GetLineFromStream(fin, vcToolsVersion)) {
198 return false;
199 }
200 vcToolsVersion = cmTrimWhitespace(vcToolsVersion);
201 std::string const vcToolsDir = vcRoot + "/VC/Tools/MSVC/" + vcToolsVersion;
202 if (!cmSystemTools::FileIsDirectory(vcToolsDir)) {
203 return false;
204 }
205 vsInstanceInfo.VCToolsetVersion = vcToolsVersion;
206 }
207
208 // Reboot may have been required before the product package was registered
209 // (last).
210 if ((eRegistered & state) == eRegistered) {
211 SmartCOMPtr<ISetupPackageReference> product;
212 if (FAILED(pInstance->GetProduct(&product)) || !product) {
213 return false;
214 }
215
216 LPSAFEARRAY lpsaPackages;
217 if (FAILED(pInstance->GetPackages(&lpsaPackages)) ||
218 lpsaPackages == NULL) {
219 return false;
220 }
221
222 int lower = lpsaPackages->rgsabound[0].lLbound;
223 int upper = lpsaPackages->rgsabound[0].cElements + lower;
224
225 IUnknown** ppData = (IUnknown**)lpsaPackages->pvData;
226 for (int i = lower; i < upper; i++) {
227 SmartCOMPtr<ISetupPackageReference> package = NULL;
228 if (FAILED(ppData[i]->QueryInterface(IID_ISetupPackageReference,
229 (void**)&package)) ||
230 package == NULL)
231 continue;
232
233 bool win10SDKInstalled = false;
234 bool win81SDkInstalled = false;
235 bool ret =
236 CheckInstalledComponent(package, win10SDKInstalled, win81SDkInstalled);
237 if (ret) {
238 vsInstanceInfo.IsWin10SDKInstalled |= win10SDKInstalled;
239 vsInstanceInfo.IsWin81SDKInstalled |= win81SDkInstalled;
240 }
241 }
242
243 SafeArrayDestroy(lpsaPackages);
244 }
245
246 return true;
247 }
248
GetVSInstanceInfo(std::string & vsInstallLocation)249 bool cmVSSetupAPIHelper::GetVSInstanceInfo(std::string& vsInstallLocation)
250 {
251 vsInstallLocation.clear();
252 bool isInstalled = this->EnumerateAndChooseVSInstance();
253
254 if (isInstalled) {
255 vsInstallLocation = chosenInstanceInfo.GetInstallLocation();
256 }
257
258 return isInstalled;
259 }
260
GetVSInstanceVersion(std::string & vsInstanceVersion)261 bool cmVSSetupAPIHelper::GetVSInstanceVersion(std::string& vsInstanceVersion)
262 {
263 vsInstanceVersion.clear();
264 bool isInstalled = this->EnumerateAndChooseVSInstance();
265
266 if (isInstalled) {
267 vsInstanceVersion = cmsys::Encoding::ToNarrow(chosenInstanceInfo.Version);
268 }
269
270 return isInstalled;
271 }
272
GetVCToolsetVersion(std::string & vsToolsetVersion)273 bool cmVSSetupAPIHelper::GetVCToolsetVersion(std::string& vsToolsetVersion)
274 {
275 vsToolsetVersion.clear();
276 bool isInstalled = this->EnumerateAndChooseVSInstance();
277
278 if (isInstalled) {
279 vsToolsetVersion = chosenInstanceInfo.VCToolsetVersion;
280 }
281
282 return isInstalled && !vsToolsetVersion.empty();
283 }
284
IsEWDKEnabled()285 bool cmVSSetupAPIHelper::IsEWDKEnabled()
286 {
287 std::string envEnterpriseWDK, envDisableRegistryUse;
288 cmSystemTools::GetEnv("EnterpriseWDK", envEnterpriseWDK);
289 cmSystemTools::GetEnv("DisableRegistryUse", envDisableRegistryUse);
290 if (!cmSystemTools::Strucmp(envEnterpriseWDK.c_str(), "True") &&
291 !cmSystemTools::Strucmp(envDisableRegistryUse.c_str(), "True")) {
292 return true;
293 }
294
295 return false;
296 }
297
EnumerateAndChooseVSInstance()298 bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
299 {
300 bool isVSInstanceExists = false;
301 if (chosenInstanceInfo.VSInstallLocation.compare(L"") != 0) {
302 return true;
303 }
304
305 if (this->IsEWDKEnabled()) {
306 std::string envWindowsSdkDir81, envVSVersion, envVsInstallDir;
307
308 cmSystemTools::GetEnv("WindowsSdkDir_81", envWindowsSdkDir81);
309 cmSystemTools::GetEnv("VisualStudioVersion", envVSVersion);
310 cmSystemTools::GetEnv("VSINSTALLDIR", envVsInstallDir);
311 if (envVSVersion.empty() || envVsInstallDir.empty())
312 return false;
313
314 chosenInstanceInfo.VSInstallLocation =
315 std::wstring(envVsInstallDir.begin(), envVsInstallDir.end());
316 chosenInstanceInfo.Version =
317 std::wstring(envVSVersion.begin(), envVSVersion.end());
318 chosenInstanceInfo.VCToolsetVersion = envVSVersion;
319 chosenInstanceInfo.ullVersion = std::stoi(envVSVersion);
320 chosenInstanceInfo.IsWin10SDKInstalled = true;
321 chosenInstanceInfo.IsWin81SDKInstalled = !envWindowsSdkDir81.empty();
322 return true;
323 }
324
325 if (initializationFailure || setupConfig == NULL || setupConfig2 == NULL ||
326 setupHelper == NULL)
327 return false;
328
329 std::string envVSCommonToolsDir;
330 std::string envVSCommonToolsDirEnvName =
331 "VS" + std::to_string(this->Version) + "0COMNTOOLS";
332
333 if (cmSystemTools::GetEnv(envVSCommonToolsDirEnvName.c_str(),
334 envVSCommonToolsDir)) {
335 cmSystemTools::ConvertToUnixSlashes(envVSCommonToolsDir);
336 }
337
338 std::vector<VSInstanceInfo> vecVSInstances;
339 SmartCOMPtr<IEnumSetupInstances> enumInstances = NULL;
340 if (FAILED(
341 setupConfig2->EnumInstances((IEnumSetupInstances**)&enumInstances)) ||
342 !enumInstances) {
343 return false;
344 }
345
346 std::wstring const wantVersion = std::to_wstring(this->Version) + L'.';
347
348 SmartCOMPtr<ISetupInstance> instance;
349 while (SUCCEEDED(enumInstances->Next(1, &instance, NULL)) && instance) {
350 SmartCOMPtr<ISetupInstance2> instance2 = NULL;
351 if (FAILED(
352 instance->QueryInterface(IID_ISetupInstance2, (void**)&instance2)) ||
353 !instance2) {
354 instance = NULL;
355 continue;
356 }
357
358 VSInstanceInfo instanceInfo;
359 bool isInstalled = GetVSInstanceInfo(instance2, instanceInfo);
360 instance = instance2 = NULL;
361
362 if (isInstalled) {
363 // We are looking for a specific major version.
364 if (instanceInfo.Version.size() < wantVersion.size() ||
365 instanceInfo.Version.substr(0, wantVersion.size()) != wantVersion) {
366 continue;
367 }
368
369 if (!this->SpecifiedVSInstallLocation.empty()) {
370 // We are looking for a specific instance.
371 std::string currentVSLocation = instanceInfo.GetInstallLocation();
372 if (cmSystemTools::ComparePath(currentVSLocation,
373 this->SpecifiedVSInstallLocation)) {
374 chosenInstanceInfo = instanceInfo;
375 return true;
376 }
377 } else {
378 // We are not looking for a specific instance.
379 // If we've been given a hint then use it.
380 if (!envVSCommonToolsDir.empty()) {
381 std::string currentVSLocation =
382 cmStrCat(instanceInfo.GetInstallLocation(), "/Common7/Tools");
383 if (cmSystemTools::ComparePath(currentVSLocation,
384 envVSCommonToolsDir)) {
385 chosenInstanceInfo = instanceInfo;
386 return true;
387 }
388 }
389 // Otherwise, add this to the list of candidates.
390 vecVSInstances.push_back(instanceInfo);
391 }
392 }
393 }
394
395 if (vecVSInstances.size() > 0) {
396 isVSInstanceExists = true;
397 int index = ChooseVSInstance(vecVSInstances);
398 chosenInstanceInfo = vecVSInstances[index];
399 }
400
401 return isVSInstanceExists;
402 }
403
ChooseVSInstance(const std::vector<VSInstanceInfo> & vecVSInstances)404 int cmVSSetupAPIHelper::ChooseVSInstance(
405 const std::vector<VSInstanceInfo>& vecVSInstances)
406 {
407 if (vecVSInstances.size() == 0)
408 return -1;
409
410 if (vecVSInstances.size() == 1)
411 return 0;
412
413 unsigned int chosenIndex = 0;
414 for (unsigned int i = 1; i < vecVSInstances.size(); i++) {
415 // If the current has Win10 SDK but not the chosen one, then choose the
416 // current VS instance
417 if (!vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
418 vecVSInstances[i].IsWin10SDKInstalled) {
419 chosenIndex = i;
420 continue;
421 }
422
423 // If the chosen one has Win10 SDK but the current one is not, then look at
424 // the next VS instance even the current
425 // instance version may be higher
426 if (vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
427 !vecVSInstances[i].IsWin10SDKInstalled) {
428 continue;
429 }
430
431 // If both chosen one and current one doesn't have Win10 SDK but the
432 // current one has Win8.1 SDK installed,
433 // then choose the current one
434 if (!vecVSInstances[chosenIndex].IsWin10SDKInstalled &&
435 !vecVSInstances[i].IsWin10SDKInstalled &&
436 !vecVSInstances[chosenIndex].IsWin81SDKInstalled &&
437 vecVSInstances[i].IsWin81SDKInstalled) {
438 chosenIndex = i;
439 continue;
440 }
441
442 // If there is no difference in WinSDKs then look for the highest version
443 // of installed VS
444 if ((vecVSInstances[chosenIndex].IsWin10SDKInstalled ==
445 vecVSInstances[i].IsWin10SDKInstalled) &&
446 (vecVSInstances[chosenIndex].IsWin81SDKInstalled ==
447 vecVSInstances[i].IsWin81SDKInstalled) &&
448 vecVSInstances[chosenIndex].Version < vecVSInstances[i].Version) {
449 chosenIndex = i;
450 continue;
451 }
452 }
453
454 return chosenIndex;
455 }
456
Initialize()457 bool cmVSSetupAPIHelper::Initialize()
458 {
459 if (initializationFailure)
460 return false;
461
462 if (FAILED(comInitialized)) {
463 initializationFailure = true;
464 return false;
465 }
466
467 if (FAILED(setupConfig.CoCreateInstance(CLSID_SetupConfiguration, NULL,
468 IID_ISetupConfiguration,
469 CLSCTX_INPROC_SERVER)) ||
470 setupConfig == NULL) {
471 initializationFailure = true;
472 return false;
473 }
474
475 if (FAILED(setupConfig.QueryInterface(IID_ISetupConfiguration2,
476 (void**)&setupConfig2)) ||
477 setupConfig2 == NULL) {
478 initializationFailure = true;
479 return false;
480 }
481
482 if (FAILED(
483 setupConfig.QueryInterface(IID_ISetupHelper, (void**)&setupHelper)) ||
484 setupHelper == NULL) {
485 initializationFailure = true;
486 return false;
487 }
488
489 initializationFailure = false;
490 return true;
491 }
492