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