/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmVSSetupHelper.h" #include "cmSystemTools.h" #ifndef VSSetupConstants #define VSSetupConstants /* clang-format off */ const IID IID_ISetupConfiguration = { 0x42843719, 0xDB4C, 0x46C2, { 0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B } }; const IID IID_ISetupConfiguration2 = { 0x26AAB78C, 0x4A60, 0x49D6, { 0xAF, 0x3B, 0x3C, 0x35, 0xBC, 0x93, 0x36, 0x5D } }; const IID IID_ISetupPackageReference = { 0xda8d8a16, 0xb2b6, 0x4487, { 0xa2, 0xf1, 0x59, 0x4c, 0xcc, 0xcd, 0x6b, 0xf5 } }; const IID IID_ISetupHelper = { 0x42b21b78, 0x6192, 0x463e, { 0x87, 0xbf, 0xd5, 0x77, 0x83, 0x8f, 0x1d, 0x5c } }; const IID IID_IEnumSetupInstances = { 0x6380BCFF, 0x41D3, 0x4B2E, { 0x8B, 0x2E, 0xBF, 0x8A, 0x68, 0x10, 0xC8, 0x48 } }; const IID IID_ISetupInstance2 = { 0x89143C9A, 0x05AF, 0x49B0, { 0xB7, 0x17, 0x72, 0xE2, 0x18, 0xA2, 0x18, 0x5C } }; const IID IID_ISetupInstance = { 0xB41463C3, 0x8866, 0x43B5, { 0xBC, 0x33, 0x2B, 0x06, 0x76, 0xF7, 0xF4, 0x2E } }; const CLSID CLSID_SetupConfiguration = { 0x177F0C4A, 0x1CD3, 0x4DE7, { 0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D } }; /* clang-format on */ #endif const WCHAR* VCToolsetComponent = L"Microsoft.VisualStudio.Component.VC.Tools.x86.x64"; const WCHAR* Win10SDKComponent = L"Microsoft.VisualStudio.Component.Windows10SDK"; const WCHAR* Win81SDKComponent = L"Microsoft.VisualStudio.Component.Windows81SDK"; const WCHAR* ComponentType = L"Component"; cmVSSetupAPIHelper::cmVSSetupAPIHelper() : setupConfig(NULL) , setupConfig2(NULL) , setupHelper(NULL) , initializationFailure(false) { comInitialized = CoInitializeEx(NULL, 0); if (SUCCEEDED(comInitialized)) { Initialize(); } else { initializationFailure = true; } } cmVSSetupAPIHelper::~cmVSSetupAPIHelper() { setupHelper = NULL; setupConfig2 = NULL; setupConfig = NULL; if (SUCCEEDED(comInitialized)) CoUninitialize(); } bool cmVSSetupAPIHelper::IsVS2017Installed() { return this->EnumerateAndChooseVSInstance(); } bool cmVSSetupAPIHelper::IsWin10SDKInstalled() { return (this->EnumerateAndChooseVSInstance() && chosenInstanceInfo.IsWin10SDKInstalled); } bool cmVSSetupAPIHelper::IsWin81SDKInstalled() { return (this->EnumerateAndChooseVSInstance() && chosenInstanceInfo.IsWin81SDKInstalled); } bool cmVSSetupAPIHelper::CheckInstalledComponent( SmartCOMPtr package, bool& bVCToolset, bool& bWin10SDK, bool& bWin81SDK) { bool ret = false; bVCToolset = bWin10SDK = bWin81SDK = false; SmartBSTR bstrId; if (FAILED(package->GetId(&bstrId))) { return ret; } SmartBSTR bstrType; if (FAILED(package->GetType(&bstrType))) { return ret; } std::wstring id = std::wstring(bstrId); std::wstring type = std::wstring(bstrType); if (id.compare(VCToolsetComponent) == 0 && type.compare(ComponentType) == 0) { bVCToolset = true; ret = true; } // Checks for any version of Win10 SDK. The version is appended at the end of // the // component name ex: Microsoft.VisualStudio.Component.Windows10SDK.10240 if (id.find(Win10SDKComponent) != std::wstring::npos && type.compare(ComponentType) == 0) { bWin10SDK = true; ret = true; } if (id.compare(Win81SDKComponent) == 0 && type.compare(ComponentType) == 0) { bWin81SDK = true; ret = true; } return ret; } // Gather additional info such as if VCToolset, WinSDKs are installed, location // of VS and version information. bool cmVSSetupAPIHelper::GetVSInstanceInfo( SmartCOMPtr pInstance, VSInstanceInfo& vsInstanceInfo) { bool isVCToolSetInstalled = false; if (pInstance == NULL) return false; SmartBSTR bstrId; if (SUCCEEDED(pInstance->GetInstanceId(&bstrId))) { vsInstanceInfo.InstanceId = std::wstring(bstrId); } else { return false; } InstanceState state; if (FAILED(pInstance->GetState(&state))) { return false; } ULONGLONG ullVersion = 0; SmartBSTR bstrVersion; if (FAILED(pInstance->GetInstallationVersion(&bstrVersion))) { return false; } else { vsInstanceInfo.Version = std::wstring(bstrVersion); if (FAILED(setupHelper->ParseVersion(bstrVersion, &ullVersion))) { vsInstanceInfo.ullVersion = 0; } else { vsInstanceInfo.ullVersion = ullVersion; } } // Reboot may have been required before the installation path was created. SmartBSTR bstrInstallationPath; if ((eLocal & state) == eLocal) { if (FAILED(pInstance->GetInstallationPath(&bstrInstallationPath))) { return false; } else { vsInstanceInfo.VSInstallLocation = std::wstring(bstrInstallationPath); } } // Reboot may have been required before the product package was registered // (last). if ((eRegistered & state) == eRegistered) { SmartCOMPtr product; if (FAILED(pInstance->GetProduct(&product)) || !product) { return false; } LPSAFEARRAY lpsaPackages; if (FAILED(pInstance->GetPackages(&lpsaPackages)) || lpsaPackages == NULL) { return false; } int lower = lpsaPackages->rgsabound[0].lLbound; int upper = lpsaPackages->rgsabound[0].cElements + lower; IUnknown** ppData = (IUnknown**)lpsaPackages->pvData; for (int i = lower; i < upper; i++) { SmartCOMPtr package = NULL; if (FAILED(ppData[i]->QueryInterface(IID_ISetupPackageReference, (void**)&package)) || package == NULL) continue; bool vcToolsetInstalled = false, win10SDKInstalled = false, win81SDkInstalled = false; bool ret = CheckInstalledComponent(package, vcToolsetInstalled, win10SDKInstalled, win81SDkInstalled); if (ret) { isVCToolSetInstalled |= vcToolsetInstalled; vsInstanceInfo.IsWin10SDKInstalled |= win10SDKInstalled; vsInstanceInfo.IsWin81SDKInstalled |= win81SDkInstalled; } } SafeArrayDestroy(lpsaPackages); } return isVCToolSetInstalled; } bool cmVSSetupAPIHelper::GetVSInstanceInfo(std::string& vsInstallLocation) { vsInstallLocation = ""; bool isInstalled = this->EnumerateAndChooseVSInstance(); if (isInstalled) { std::string str(chosenInstanceInfo.VSInstallLocation.begin(), chosenInstanceInfo.VSInstallLocation.end()); vsInstallLocation = str; } return isInstalled; } bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance() { bool isVSInstanceExists = false; if (chosenInstanceInfo.VSInstallLocation.compare(L"") != 0) { return true; } if (initializationFailure || setupConfig == NULL || setupConfig2 == NULL || setupHelper == NULL) return false; std::string envVSCommonToolsDir; // FIXME: When we support VS versions beyond 2017, the version // to choose will be passed in by the caller. We need to map that // to a per-version name of this environment variable. if (cmSystemTools::GetEnv("VS150COMNTOOLS", envVSCommonToolsDir)) { cmSystemTools::ConvertToUnixSlashes(envVSCommonToolsDir); } // FIXME: If the environment variable value changes between runs // of CMake within a given build tree the results are not defined. // Instead we should save a CMAKE_GENERATOR_INSTANCE value in the cache // (similar to CMAKE_GENERATOR_TOOLSET) to hold it persistently. // Unfortunately doing so will require refactoring elsewhere in // order to make sure the value is available in time to create // the generator. std::vector vecVSInstances; SmartCOMPtr enumInstances = NULL; if (FAILED( setupConfig2->EnumInstances((IEnumSetupInstances**)&enumInstances)) || !enumInstances) { return false; } SmartCOMPtr instance; while (SUCCEEDED(enumInstances->Next(1, &instance, NULL)) && instance) { SmartCOMPtr instance2 = NULL; if (FAILED( instance->QueryInterface(IID_ISetupInstance2, (void**)&instance2)) || !instance2) { instance = NULL; continue; } VSInstanceInfo instanceInfo; bool isInstalled = GetVSInstanceInfo(instance2, instanceInfo); instance = instance2 = NULL; if (isInstalled) { if (!envVSCommonToolsDir.empty()) { std::string currentVSLocation(instanceInfo.VSInstallLocation.begin(), instanceInfo.VSInstallLocation.end()); cmSystemTools::ConvertToUnixSlashes(currentVSLocation); currentVSLocation += "/Common7/Tools"; if (cmSystemTools::ComparePath(currentVSLocation, envVSCommonToolsDir)) { chosenInstanceInfo = instanceInfo; return true; } } vecVSInstances.push_back(instanceInfo); } } if (vecVSInstances.size() > 0) { isVSInstanceExists = true; int index = ChooseVSInstance(vecVSInstances); chosenInstanceInfo = vecVSInstances[index]; } return isVSInstanceExists; } int cmVSSetupAPIHelper::ChooseVSInstance( const std::vector& vecVSInstances) { if (vecVSInstances.size() == 0) return -1; if (vecVSInstances.size() == 1) return 0; unsigned int chosenIndex = 0; for (unsigned int i = 1; i < vecVSInstances.size(); i++) { // If the current has Win10 SDK but not the chosen one, then choose the // current VS instance if (!vecVSInstances[chosenIndex].IsWin10SDKInstalled && vecVSInstances[i].IsWin10SDKInstalled) { chosenIndex = i; continue; } // If the chosen one has Win10 SDK but the current one is not, then look at // the next VS instance even the current // instance version may be higher if (vecVSInstances[chosenIndex].IsWin10SDKInstalled && !vecVSInstances[i].IsWin10SDKInstalled) { continue; } // If both chosen one and current one doesn't have Win10 SDK but the // current one has Win8.1 SDK installed, // then choose the current one if (!vecVSInstances[chosenIndex].IsWin10SDKInstalled && !vecVSInstances[i].IsWin10SDKInstalled && !vecVSInstances[chosenIndex].IsWin81SDKInstalled && vecVSInstances[i].IsWin81SDKInstalled) { chosenIndex = i; continue; } // If there is no difference in WinSDKs then look for the highest version // of installed VS if ((vecVSInstances[chosenIndex].IsWin10SDKInstalled == vecVSInstances[i].IsWin10SDKInstalled) && (vecVSInstances[chosenIndex].IsWin81SDKInstalled == vecVSInstances[i].IsWin81SDKInstalled) && vecVSInstances[chosenIndex].Version < vecVSInstances[i].Version) { chosenIndex = i; continue; } } return chosenIndex; } bool cmVSSetupAPIHelper::Initialize() { if (initializationFailure) return false; if (FAILED(comInitialized)) { initializationFailure = true; return false; } if (FAILED(setupConfig.CoCreateInstance(CLSID_SetupConfiguration, NULL, IID_ISetupConfiguration, CLSCTX_INPROC_SERVER)) || setupConfig == NULL) { initializationFailure = true; return false; } if (FAILED(setupConfig.QueryInterface(IID_ISetupConfiguration2, (void**)&setupConfig2)) || setupConfig2 == NULL) { initializationFailure = true; return false; } if (FAILED( setupConfig.QueryInterface(IID_ISetupHelper, (void**)&setupHelper)) || setupHelper == NULL) { initializationFailure = true; return false; } initializationFailure = false; return true; }