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