blob: dd7c1fcb7236708820f52dc2014bdbab90e89e8f [file] [log] [blame]
//
// Helper library for location Visual Studio installations
// using the COM-based query API.
//
// Copyright (c) Microsoft Corporation
// Licensed to PSF under a contributor agreement
//
// Version history
// 2017-05: Initial contribution (Steve Dower)
#include <Windows.h>
#include <Strsafe.h>
#include "external\include\Setup.Configuration.h"
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "version.lib")
#pragma comment(lib, "Microsoft.VisualStudio.Setup.Configuration.Native.lib")
#include <Python.h>
static PyObject *error_from_hr(HRESULT hr)
{
if (FAILED(hr))
PyErr_Format(PyExc_OSError, "Error %08x", hr);
assert(PyErr_Occurred());
return nullptr;
}
static PyObject *get_install_name(ISetupInstance2 *inst)
{
HRESULT hr;
BSTR name;
PyObject *str = nullptr;
if (FAILED(hr = inst->GetDisplayName(LOCALE_USER_DEFAULT, &name)))
goto error;
str = PyUnicode_FromWideChar(name, SysStringLen(name));
SysFreeString(name);
return str;
error:
return error_from_hr(hr);
}
static PyObject *get_install_version(ISetupInstance *inst)
{
HRESULT hr;
BSTR ver;
PyObject *str = nullptr;
if (FAILED(hr = inst->GetInstallationVersion(&ver)))
goto error;
str = PyUnicode_FromWideChar(ver, SysStringLen(ver));
SysFreeString(ver);
return str;
error:
return error_from_hr(hr);
}
static PyObject *get_install_path(ISetupInstance *inst)
{
HRESULT hr;
BSTR path;
PyObject *str = nullptr;
if (FAILED(hr = inst->GetInstallationPath(&path)))
goto error;
str = PyUnicode_FromWideChar(path, SysStringLen(path));
SysFreeString(path);
return str;
error:
return error_from_hr(hr);
}
static PyObject *get_installed_packages(ISetupInstance2 *inst)
{
HRESULT hr;
PyObject *res = nullptr;
LPSAFEARRAY sa_packages = nullptr;
LONG ub = 0;
IUnknown **packages = nullptr;
PyObject *str = nullptr;
if (FAILED(hr = inst->GetPackages(&sa_packages)) ||
FAILED(hr = SafeArrayAccessData(sa_packages, (void**)&packages)) ||
FAILED(SafeArrayGetUBound(sa_packages, 1, &ub)) ||
!(res = PyList_New(0)))
goto error;
for (LONG i = 0; i < ub; ++i) {
ISetupPackageReference *package = nullptr;
BSTR id = nullptr;
PyObject *str = nullptr;
if (FAILED(hr = packages[i]->QueryInterface(&package)) ||
FAILED(hr = package->GetId(&id)))
goto iter_error;
str = PyUnicode_FromWideChar(id, SysStringLen(id));
SysFreeString(id);
if (!str || PyList_Append(res, str) < 0)
goto iter_error;
Py_CLEAR(str);
package->Release();
continue;
iter_error:
if (package) package->Release();
Py_XDECREF(str);
goto error;
}
SafeArrayUnaccessData(sa_packages);
SafeArrayDestroy(sa_packages);
return res;
error:
if (sa_packages && packages) SafeArrayUnaccessData(sa_packages);
if (sa_packages) SafeArrayDestroy(sa_packages);
Py_XDECREF(res);
return error_from_hr(hr);
}
static PyObject *find_all_instances()
{
ISetupConfiguration *sc = nullptr;
ISetupConfiguration2 *sc2 = nullptr;
IEnumSetupInstances *enm = nullptr;
ISetupInstance *inst = nullptr;
ISetupInstance2 *inst2 = nullptr;
PyObject *res = nullptr;
ULONG fetched;
HRESULT hr;
if (!(res = PyList_New(0)))
goto error;
if (FAILED(hr = CoCreateInstance(
__uuidof(SetupConfiguration),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(ISetupConfiguration),
(LPVOID*)&sc
)) && hr != REGDB_E_CLASSNOTREG)
goto error;
// If the class is not registered, there are no VS instances installed
if (hr == REGDB_E_CLASSNOTREG)
return res;
if (FAILED(hr = sc->QueryInterface(&sc2)) ||
FAILED(hr = sc2->EnumAllInstances(&enm)))
goto error;
while (SUCCEEDED(enm->Next(1, &inst, &fetched)) && fetched) {
PyObject *name = nullptr;
PyObject *version = nullptr;
PyObject *path = nullptr;
PyObject *packages = nullptr;
PyObject *tuple = nullptr;
if (FAILED(hr = inst->QueryInterface(&inst2)) ||
!(name = get_install_name(inst2)) ||
!(version = get_install_version(inst)) ||
!(path = get_install_path(inst)) ||
!(packages = get_installed_packages(inst2)) ||
!(tuple = PyTuple_Pack(4, name, version, path, packages)) ||
PyList_Append(res, tuple) < 0)
goto iter_error;
Py_DECREF(tuple);
Py_DECREF(packages);
Py_DECREF(path);
Py_DECREF(version);
Py_DECREF(name);
continue;
iter_error:
if (inst2) inst2->Release();
Py_XDECREF(tuple);
Py_XDECREF(packages);
Py_XDECREF(path);
Py_XDECREF(version);
Py_XDECREF(name);
goto error;
}
enm->Release();
sc2->Release();
sc->Release();
return res;
error:
if (enm) enm->Release();
if (sc2) sc2->Release();
if (sc) sc->Release();
Py_XDECREF(res);
return error_from_hr(hr);
}
PyDoc_STRVAR(findvs_findall_doc, "findall()\
\
Finds all installed versions of Visual Studio.\
\
This function will initialize COM temporarily. To avoid impact on other parts\
of your application, use a new thread to make this call.");
static PyObject *findvs_findall(PyObject *self, PyObject *args, PyObject *kwargs)
{
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (hr == RPC_E_CHANGED_MODE)
hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
return error_from_hr(hr);
PyObject *res = find_all_instances();
CoUninitialize();
return res;
}
// List of functions to add to findvs in exec_findvs().
static PyMethodDef findvs_functions[] = {
{ "findall", (PyCFunction)findvs_findall, METH_VARARGS | METH_KEYWORDS, findvs_findall_doc },
{ NULL, NULL, 0, NULL }
};
// Initialize findvs. May be called multiple times, so avoid
// using static state.
static int exec_findvs(PyObject *module)
{
PyModule_AddFunctions(module, findvs_functions);
return 0; // success
}
PyDoc_STRVAR(findvs_doc, "The _findvs helper module");
static PyModuleDef_Slot findvs_slots[] = {
{ Py_mod_exec, exec_findvs },
{ 0, NULL }
};
static PyModuleDef findvs_def = {
PyModuleDef_HEAD_INIT,
"_findvs",
findvs_doc,
0, // m_size
NULL, // m_methods
findvs_slots,
NULL, // m_traverse
NULL, // m_clear
NULL, // m_free
};
extern "C" {
PyMODINIT_FUNC PyInit__findvs(void)
{
return PyModuleDef_Init(&findvs_def);
}
}