You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
6.7 KiB

  1. //
  2. // Helper library for location Visual Studio installations
  3. // using the COM-based query API.
  4. //
  5. // Copyright (c) Microsoft Corporation
  6. // Licensed to PSF under a contributor agreement
  7. //
  8. // Version history
  9. // 2017-05: Initial contribution (Steve Dower)
  10. #include <Windows.h>
  11. #include <Strsafe.h>
  12. #include "external\include\Setup.Configuration.h"
  13. #pragma comment(lib, "ole32.lib")
  14. #pragma comment(lib, "oleaut32.lib")
  15. #pragma comment(lib, "version.lib")
  16. #pragma comment(lib, "Microsoft.VisualStudio.Setup.Configuration.Native.lib")
  17. #include <Python.h>
  18. static PyObject *error_from_hr(HRESULT hr)
  19. {
  20. if (FAILED(hr))
  21. PyErr_Format(PyExc_OSError, "Error %08x", hr);
  22. assert(PyErr_Occurred());
  23. return nullptr;
  24. }
  25. static PyObject *get_install_name(ISetupInstance2 *inst)
  26. {
  27. HRESULT hr;
  28. BSTR name;
  29. PyObject *str = nullptr;
  30. if (FAILED(hr = inst->GetDisplayName(LOCALE_USER_DEFAULT, &name)))
  31. goto error;
  32. str = PyUnicode_FromWideChar(name, SysStringLen(name));
  33. SysFreeString(name);
  34. return str;
  35. error:
  36. return error_from_hr(hr);
  37. }
  38. static PyObject *get_install_version(ISetupInstance *inst)
  39. {
  40. HRESULT hr;
  41. BSTR ver;
  42. PyObject *str = nullptr;
  43. if (FAILED(hr = inst->GetInstallationVersion(&ver)))
  44. goto error;
  45. str = PyUnicode_FromWideChar(ver, SysStringLen(ver));
  46. SysFreeString(ver);
  47. return str;
  48. error:
  49. return error_from_hr(hr);
  50. }
  51. static PyObject *get_install_path(ISetupInstance *inst)
  52. {
  53. HRESULT hr;
  54. BSTR path;
  55. PyObject *str = nullptr;
  56. if (FAILED(hr = inst->GetInstallationPath(&path)))
  57. goto error;
  58. str = PyUnicode_FromWideChar(path, SysStringLen(path));
  59. SysFreeString(path);
  60. return str;
  61. error:
  62. return error_from_hr(hr);
  63. }
  64. static PyObject *get_installed_packages(ISetupInstance2 *inst)
  65. {
  66. HRESULT hr;
  67. PyObject *res = nullptr;
  68. LPSAFEARRAY sa_packages = nullptr;
  69. LONG ub = 0;
  70. IUnknown **packages = nullptr;
  71. PyObject *str = nullptr;
  72. if (FAILED(hr = inst->GetPackages(&sa_packages)) ||
  73. FAILED(hr = SafeArrayAccessData(sa_packages, (void**)&packages)) ||
  74. FAILED(SafeArrayGetUBound(sa_packages, 1, &ub)) ||
  75. !(res = PyList_New(0)))
  76. goto error;
  77. for (LONG i = 0; i < ub; ++i) {
  78. ISetupPackageReference *package = nullptr;
  79. BSTR id = nullptr;
  80. PyObject *str = nullptr;
  81. if (FAILED(hr = packages[i]->QueryInterface(&package)) ||
  82. FAILED(hr = package->GetId(&id)))
  83. goto iter_error;
  84. str = PyUnicode_FromWideChar(id, SysStringLen(id));
  85. SysFreeString(id);
  86. if (!str || PyList_Append(res, str) < 0)
  87. goto iter_error;
  88. Py_CLEAR(str);
  89. package->Release();
  90. continue;
  91. iter_error:
  92. if (package) package->Release();
  93. Py_XDECREF(str);
  94. goto error;
  95. }
  96. SafeArrayUnaccessData(sa_packages);
  97. SafeArrayDestroy(sa_packages);
  98. return res;
  99. error:
  100. if (sa_packages && packages) SafeArrayUnaccessData(sa_packages);
  101. if (sa_packages) SafeArrayDestroy(sa_packages);
  102. Py_XDECREF(res);
  103. return error_from_hr(hr);
  104. }
  105. static PyObject *find_all_instances()
  106. {
  107. ISetupConfiguration *sc = nullptr;
  108. ISetupConfiguration2 *sc2 = nullptr;
  109. IEnumSetupInstances *enm = nullptr;
  110. ISetupInstance *inst = nullptr;
  111. ISetupInstance2 *inst2 = nullptr;
  112. PyObject *res = nullptr;
  113. ULONG fetched;
  114. HRESULT hr;
  115. if (!(res = PyList_New(0)))
  116. goto error;
  117. if (FAILED(hr = CoCreateInstance(
  118. __uuidof(SetupConfiguration),
  119. NULL,
  120. CLSCTX_INPROC_SERVER,
  121. __uuidof(ISetupConfiguration),
  122. (LPVOID*)&sc
  123. )) && hr != REGDB_E_CLASSNOTREG)
  124. goto error;
  125. // If the class is not registered, there are no VS instances installed
  126. if (hr == REGDB_E_CLASSNOTREG)
  127. return res;
  128. if (FAILED(hr = sc->QueryInterface(&sc2)) ||
  129. FAILED(hr = sc2->EnumAllInstances(&enm)))
  130. goto error;
  131. while (SUCCEEDED(enm->Next(1, &inst, &fetched)) && fetched) {
  132. PyObject *name = nullptr;
  133. PyObject *version = nullptr;
  134. PyObject *path = nullptr;
  135. PyObject *packages = nullptr;
  136. PyObject *tuple = nullptr;
  137. if (FAILED(hr = inst->QueryInterface(&inst2)) ||
  138. !(name = get_install_name(inst2)) ||
  139. !(version = get_install_version(inst)) ||
  140. !(path = get_install_path(inst)) ||
  141. !(packages = get_installed_packages(inst2)) ||
  142. !(tuple = PyTuple_Pack(4, name, version, path, packages)) ||
  143. PyList_Append(res, tuple) < 0)
  144. goto iter_error;
  145. Py_DECREF(tuple);
  146. Py_DECREF(packages);
  147. Py_DECREF(path);
  148. Py_DECREF(version);
  149. Py_DECREF(name);
  150. continue;
  151. iter_error:
  152. if (inst2) inst2->Release();
  153. Py_XDECREF(tuple);
  154. Py_XDECREF(packages);
  155. Py_XDECREF(path);
  156. Py_XDECREF(version);
  157. Py_XDECREF(name);
  158. goto error;
  159. }
  160. enm->Release();
  161. sc2->Release();
  162. sc->Release();
  163. return res;
  164. error:
  165. if (enm) enm->Release();
  166. if (sc2) sc2->Release();
  167. if (sc) sc->Release();
  168. Py_XDECREF(res);
  169. return error_from_hr(hr);
  170. }
  171. PyDoc_STRVAR(findvs_findall_doc, "findall()\
  172. \
  173. Finds all installed versions of Visual Studio.\
  174. \
  175. This function will initialize COM temporarily. To avoid impact on other parts\
  176. of your application, use a new thread to make this call.");
  177. static PyObject *findvs_findall(PyObject *self, PyObject *args, PyObject *kwargs)
  178. {
  179. HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  180. if (hr == RPC_E_CHANGED_MODE)
  181. hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
  182. if (FAILED(hr))
  183. return error_from_hr(hr);
  184. PyObject *res = find_all_instances();
  185. CoUninitialize();
  186. return res;
  187. }
  188. // List of functions to add to findvs in exec_findvs().
  189. static PyMethodDef findvs_functions[] = {
  190. { "findall", (PyCFunction)findvs_findall, METH_VARARGS | METH_KEYWORDS, findvs_findall_doc },
  191. { NULL, NULL, 0, NULL }
  192. };
  193. // Initialize findvs. May be called multiple times, so avoid
  194. // using static state.
  195. static int exec_findvs(PyObject *module)
  196. {
  197. PyModule_AddFunctions(module, findvs_functions);
  198. return 0; // success
  199. }
  200. PyDoc_STRVAR(findvs_doc, "The _findvs helper module");
  201. static PyModuleDef_Slot findvs_slots[] = {
  202. { Py_mod_exec, exec_findvs },
  203. { 0, NULL }
  204. };
  205. static PyModuleDef findvs_def = {
  206. PyModuleDef_HEAD_INIT,
  207. "_findvs",
  208. findvs_doc,
  209. 0, // m_size
  210. NULL, // m_methods
  211. findvs_slots,
  212. NULL, // m_traverse
  213. NULL, // m_clear
  214. NULL, // m_free
  215. };
  216. extern "C" {
  217. PyMODINIT_FUNC PyInit__findvs(void)
  218. {
  219. return PyModuleDef_Init(&findvs_def);
  220. }
  221. }