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.

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