Skip to content

Instantly share code, notes, and snippets.

@ileonte
Last active January 4, 2025 20:48
Show Gist options
  • Save ileonte/7f564af15619b554745883cb9b99a149 to your computer and use it in GitHub Desktop.
Save ileonte/7f564af15619b554745883cb9b99a149 to your computer and use it in GitHub Desktop.
instance_id=0a087eb5
installation_name=VisualStudio/14.37.32822
installation_path=C:\MSVC
installation_version=14.37.32822
display_name=Visual Studio Community 2022
display_desc=Powerful IDE, free for students, open-source contributors, and individuals
sdk_version=10.0.22000.0
sdk_path=C:\MSVC\Windows Kits\10
// cl /nologo /LD /EHsc /MT /std:c++17 /Fe:vs-setup-shim.dll vs-setup-shim.cpp /link /nologo Ole32.lib OleAut32.lib Advapi32.lib Pathcch.lib /DEF:vs-setup-shim.def
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <combaseapi.h>
#include <PathCch.h>
#include <stdio.h>
#include <assert.h>
#include <io.h>
#include <wchar.h>
// Ole32.lib;OleAut32.lib;Advapi32.lib;Pathcch.lib;
static HMODULE this_module;
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.setup.configuration?view=visualstudiosdk-2022
// https://github.com/Kitware/CMake/blob/master/Utilities/cmvssetup/Setup.Configuration.h
struct __declspec(uuid("B41463C3-8866-43B5-BC33-2B0676F7F42E") novtable) ISetupInstance;
struct __declspec(uuid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848") novtable) IEnumSetupInstances;
struct __declspec(uuid("42843719-DB4C-46C2-8E7C-64F1816EFD5B") novtable) ISetupConfiguration;
struct __declspec(uuid("177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D")) SetupConfiguration;
static constexpr GUID IID_ISetupInstance = __uuidof(ISetupInstance);
static constexpr GUID IID_IEnumSetupInstances = __uuidof(IEnumSetupInstances);
static constexpr GUID IID_ISetupConfiguration = __uuidof(ISetupConfiguration);
static constexpr GUID CLSID_SetupConfiguration = __uuidof(SetupConfiguration);
struct setup_instance {
BSTR instance_id;
FILETIME install_date;
BSTR name;
BSTR path;
BSTR version;
BSTR display_name;
BSTR display_desc;
BSTR sdk_version;
BSTR sdk_path;
operator bool () const {
return (instance_id != nullptr)
&& (name != nullptr)
&& (path != nullptr)
&& (version != nullptr)
&& (display_name != nullptr)
&& (display_desc != nullptr)
&& (sdk_version != nullptr)
&& (sdk_path != nullptr);
}
};
static constexpr const long max_slots = 16; // should be enough for everybody
static setup_instance slots[max_slots];
static long slot_count = 0;
static wchar_t *config_data = nullptr;
static int config_size = 0;
struct __declspec(novtable uuid("B41463C3-8866-43B5-BC33-2B0676F7F42E")) ISetupInstance : public IUnknown {
virtual __declspec(nothrow) HRESULT __stdcall GetInstanceId(_Out_ BSTR *pbstrInstanceId) = 0;
virtual __declspec(nothrow) HRESULT __stdcall GetInstallDate(_Out_ LPFILETIME pInstallDate) = 0;
virtual __declspec(nothrow) HRESULT __stdcall GetInstallationName(_Out_ BSTR *pbstrInstallationName) = 0;
virtual __declspec(nothrow) HRESULT __stdcall GetInstallationPath(_Out_ BSTR *pbstrInstallationPath) = 0;
virtual __declspec(nothrow) HRESULT __stdcall GetInstallationVersion(_Out_ BSTR *pbstrInstallationVersion) = 0;
virtual __declspec(nothrow) HRESULT __stdcall GetDisplayName(_In_ LCID lcid, _Out_ BSTR *pbstrDisplayName) = 0;
virtual __declspec(nothrow) HRESULT __stdcall GetDescription(_In_ LCID lcid, _Out_ BSTR *pbstrDescription) = 0;
virtual __declspec(nothrow) HRESULT __stdcall ResolvePath(_In_opt_z_ LPCOLESTR pwszRelativePath, _Out_ BSTR *pbstrAbsolutePath) = 0;
};
struct SetupInstance : public ISetupInstance {
virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pintf) override {
if (iid == IID_IUnknown || iid == IID_ISetupInstance) {
*pintf = (ISetupInstance*)this;
AddRef();
return S_OK;
}
return E_NOTIMPL;
}
virtual ULONG __stdcall AddRef() override { return InterlockedIncrement(&ref_count_); }
virtual ULONG __stdcall Release() override {
auto count = InterlockedDecrement(&ref_count_);
if (!count) delete this;
return count;
}
virtual HRESULT __stdcall GetInstanceId(BSTR *pid) override { *pid = SysAllocString(inst_->instance_id); return S_OK; }
virtual HRESULT __stdcall GetInstallDate(LPFILETIME pdate) override { *pdate = inst_->install_date; return S_OK; }
virtual HRESULT __stdcall GetInstallationName(BSTR *pname) override { *pname = SysAllocString(inst_->name); return S_OK; }
virtual HRESULT __stdcall GetInstallationPath(BSTR *ppath) override { *ppath = SysAllocString(inst_->path); return S_OK; }
virtual HRESULT __stdcall GetInstallationVersion(BSTR *pver) override { *pver = SysAllocString(inst_->version); return S_OK; }
virtual HRESULT __stdcall GetDisplayName(LCID, BSTR *pname) override { *pname = SysAllocString(inst_->display_name); return S_OK; }
virtual HRESULT __stdcall GetDescription(LCID, BSTR *pdesc) override { *pdesc = SysAllocString(inst_->display_desc); return S_OK; }
virtual HRESULT __stdcall ResolvePath(LPCOLESTR rel, BSTR *pout) override {
auto len = UINT(wcslen(rel) + wcslen(inst_->path) + 2);
auto ret = SysAllocStringLen(nullptr, len);
if (!ret) return E_OUTOFMEMORY;
auto hr = PathCchCombineEx(ret, len, inst_->path, rel, PATHCCH_ALLOW_LONG_PATHS);
if (!SUCCEEDED(hr)) {
SysFreeString(ret);
*pout = nullptr;
return hr;
}
*pout = ret;
return S_OK;
}
SetupInstance(setup_instance const *inst) : inst_(inst), ref_count_(0) {}
virtual ~SetupInstance() { inst_ = nullptr; }
private:
setup_instance const *inst_ = nullptr;
ULONG ref_count_ = 0;
};
struct __declspec(novtable uuid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848")) IEnumSetupInstances : public IUnknown {
virtual __declspec(nothrow) HRESULT __stdcall Next(
_In_ ULONG celt,
_Out_writes_to_(celt, *pceltFetched) ISetupInstance **rgelt,
_Out_opt_ _Deref_out_range_(0, celt) ULONG *pceltFetched) = 0;
virtual __declspec(nothrow) HRESULT __stdcall Skip(_In_ ULONG celt) = 0;
virtual __declspec(nothrow) HRESULT __stdcall Reset() = 0;
virtual __declspec(nothrow) HRESULT __stdcall Clone(_Deref_out_opt_ IEnumSetupInstances **ppenum) = 0;
};
struct EnumSetupInstances : public IEnumSetupInstances {
virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pintf) override {
if (iid == IID_IUnknown || iid == IID_IEnumSetupInstances) {
*pintf = (IEnumSetupInstances*)this;
AddRef();
return S_OK;
}
return E_NOTIMPL;
}
virtual ULONG __stdcall AddRef() override { return InterlockedIncrement(&ref_count_); }
virtual ULONG __stdcall Release() override {
auto count = InterlockedDecrement(&ref_count_);
if (!count) delete this;
return count;
}
virtual HRESULT __stdcall Next(ULONG celt, ISetupInstance **rgelt, ULONG *pceltFetched) override {
if (pceltFetched) *pceltFetched = 0;
for (ULONG i = 0; i < celt; i++) {
if (next_ >= slot_count) break;
rgelt[i] = new SetupInstance(slots + next_);
rgelt[i]->AddRef();
next_ += 1;
if (pceltFetched) *pceltFetched += 1;
}
return S_OK;
}
virtual HRESULT __stdcall Skip(ULONG celt) override {
for (ULONG i = 0; i < celt; i++) {
if (next_ >= slot_count) break;
next_ += 1;
}
return S_OK;
}
virtual HRESULT __stdcall Reset() override {
next_ = 0;
return S_OK;
}
virtual HRESULT __stdcall Clone(IEnumSetupInstances **ppenum) override {
auto ret = new EnumSetupInstances();
ret->next_ = next_;
ret->AddRef();
*ppenum = ret;
return S_OK;
}
private:
ULONG ref_count_ = 0;
int next_ = 0;
};
struct __declspec(novtable uuid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")) ISetupConfiguration : public IUnknown {
virtual __declspec(nothrow) HRESULT __stdcall EnumInstances(_Out_ IEnumSetupInstances **ppEnumInstances) = 0;
virtual __declspec(nothrow) HRESULT __stdcall GetInstanceForCurrentProcess(_Out_ ISetupInstance **ppInstance) = 0;
virtual __declspec(nothrow) HRESULT __stdcall GetInstanceForPath(_In_z_ LPCWSTR wzPath, _Out_ ISetupInstance **ppInstance) = 0;
};
struct __declspec(uuid("177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D")) SetupConfiguration : public ISetupConfiguration {
virtual HRESULT __stdcall QueryInterface(REFIID iid, void **pintf) override {
if (iid == IID_IUnknown || iid == IID_ISetupConfiguration) {
*pintf = (ISetupConfiguration *)this;
AddRef();
return S_OK;
}
return E_NOTIMPL;
}
virtual ULONG __stdcall AddRef() override { return InterlockedIncrement(&ref_count_); }
virtual ULONG __stdcall Release() override {
auto count = InterlockedDecrement(&ref_count_);
if (!count) delete this;
return count;
}
virtual HRESULT __stdcall EnumInstances(IEnumSetupInstances **ppEnumInstances) override {
*ppEnumInstances = new EnumSetupInstances();
(*ppEnumInstances)->AddRef();
return S_OK;
}
virtual HRESULT __stdcall GetInstanceForCurrentProcess(ISetupInstance **ppInstance) override {
*ppInstance = new SetupInstance(slots);
(*ppInstance)->AddRef();
return S_OK;
}
virtual HRESULT __stdcall GetInstanceForPath(LPCWSTR wzPath, ISetupInstance **ppInstance) override {
*ppInstance = new SetupInstance(slots);
(*ppInstance)->AddRef();
return S_OK;
}
private:
ULONG ref_count_ = 0;
};
struct SetupConfigurationClass : public IClassFactory {
virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pintf) override {
if (iid == IID_IUnknown || iid == IID_IClassFactory) {
*pintf = this;
AddRef();
return S_OK;
}
return E_NOTIMPL;
}
virtual ULONG __stdcall AddRef() override { return 1; }
virtual ULONG __stdcall Release() override { return 1; }
virtual HRESULT __stdcall CreateInstance(IUnknown* pUnkOuter, REFIID iid, void** ppv) override {
*ppv = nullptr;
if (iid == IID_ISetupConfiguration) {
auto inst = new SetupConfiguration();
if (!SUCCEEDED(inst->QueryInterface(iid, ppv))) return E_NOINTERFACE;
return S_OK;
}
return E_NOINTERFACE;
}
virtual HRESULT __stdcall LockServer(BOOL fLock) override { return E_FAIL; }
};
static bool line_empty(wchar_t const *line) {
while (*line) {
if (!iswspace(*line)) return false;
line++;
}
return true;
}
static bool parse_line(wchar_t const *line, setup_instance *inst) {
static constexpr const wchar_t id_marker[] = L"instance_id=";
static constexpr const wchar_t iname_marker[] = L"installation_name=";
static constexpr const wchar_t ipath_marker[] = L"installation_path=";
static constexpr const wchar_t ver_marker[] = L"installation_version=";
static constexpr const wchar_t dname_marker[] = L"display_name=";
static constexpr const wchar_t ddesc_marker[] = L"display_desc=";
static constexpr const wchar_t sdk_ver_marker[] = L"sdk_version=";
static constexpr const wchar_t sdk_path_marker[] = L"sdk_path=";
#define MLEN(marker) (sizeof((marker)) / sizeof((marker)[0]) - 1)
if (!_wcsnicmp(line, id_marker, MLEN(id_marker))) {
inst->instance_id = BSTR(line + MLEN(id_marker));
return !line_empty(inst->instance_id);
} else if (!_wcsnicmp(line, iname_marker, MLEN(iname_marker))) {
inst->name = BSTR(line + MLEN(iname_marker));
return !line_empty(inst->name);
} else if (!_wcsnicmp(line, ipath_marker, MLEN(ipath_marker))) {
inst->path = BSTR(line + MLEN(ipath_marker));
return !line_empty(inst->path);
} else if (!_wcsnicmp(line, ver_marker, MLEN(ver_marker))) {
inst->version = BSTR(line + MLEN(ver_marker));
return !line_empty(inst->version);
} else if (!_wcsnicmp(line, dname_marker, MLEN(dname_marker))) {
inst->display_name = BSTR(line + MLEN(dname_marker));
return !line_empty(inst->display_name);
} else if (!_wcsnicmp(line, ddesc_marker, MLEN(ddesc_marker))) {
inst->display_desc = BSTR(line + MLEN(ddesc_marker));
return !line_empty(inst->display_desc);
} else if (!_wcsnicmp(line, sdk_ver_marker, MLEN(sdk_ver_marker))) {
inst->sdk_version = BSTR(line + MLEN(sdk_ver_marker));
return !line_empty(inst->sdk_version);
} else if (!_wcsnicmp(line, sdk_path_marker, MLEN(sdk_path_marker))) {
inst->sdk_path = BSTR(line + MLEN(sdk_path_marker));
return !line_empty(inst->sdk_path);
} else {
return false;
}
#undef MLEN
}
static bool add_entry(int line, setup_instance *inst) {
if (slot_count >= max_slots) {
printf("Too many entries!\n");
return false;
}
if (!*inst) {
printf("Incomplete entry at line %d\n", line);
return false;
}
slots[slot_count++] = *inst;
memset(inst, 0, sizeof(*inst));
return true;
}
static bool load_config(HMODULE hModule) {
wchar_t path[1024];
auto len = GetModuleFileNameW(hModule, path, sizeof(path) / sizeof(path[0]));
PathCchRemoveFileSpec(path, len);
auto hr = PathCchCombineEx(path, sizeof(path) / sizeof(path[0]), path, L"installs.txt", PATHCCH_ALLOW_LONG_PATHS);
if (!SUCCEEDED(hr)) {
printf("PathCchCombineEx(): %08lx\n", hr);
return false;
}
FILE *f;
auto open_result = _wfopen_s(&f, path, L"rt");
if (open_result || !f) {
printf("_wfopen_s('%S'): %08x\n", path, open_result);
return false;
}
FILETIME ft;
GetFileTime((HANDLE)_get_osfhandle(_fileno(f)), &ft, nullptr, nullptr);
static constexpr const int alloc_inc_size = 2048;
auto remaining = config_size;
while (!feof(f)) {
if (!remaining) {
auto new_data = (wchar_t *)realloc(config_data, (config_size + alloc_inc_size) * sizeof(wchar_t));
if (!new_data) {
fclose(f);
return false;
}
config_data = new_data;
remaining = alloc_inc_size;
memset(config_data + config_size, 0, remaining * sizeof(wchar_t));
}
auto ptr = config_data + config_size;
if (!fgetws(ptr, remaining, f)) break;
int count = 0;
while ((count < remaining) && ptr[count] && (ptr[count++] != L'\n')) {}
config_size += count;
remaining -= count;
}
if (!remaining) {
auto new_data = (wchar_t *)realloc(config_data, (config_size + 1) * sizeof(wchar_t));
if (!new_data) {
fclose(f);
return false;
}
config_data = new_data;
config_data[config_size++] = 0;
}
fclose(f);
int pos = 0;
setup_instance current_instance;
bool parsing_entry = false;
int entry_line_start = 0;
int line_count = 0;
memset(&current_instance, 0, sizeof(current_instance));
while (pos < config_size) {
auto line = config_data + pos;
auto line_found = false;
while (!line_found && (pos < config_size)) {
switch (config_data[pos]) {
case L'\r': {
config_data[pos++] = 0;
break;
}
case L'\n': {
config_data[pos++] = 0;
line_found = true;
break;
}
case 0: {
line_found = true;
break;
}
default: {
pos++;
break;
}
}
}
if (pos >= config_size) line_found = (line <= config_data + config_size - 1);
if (!line_found) break;
line_count += 1;
if (line_empty(line)) {
if (!parsing_entry) continue;
parsing_entry = false;
current_instance.install_date = ft;
if (!add_entry(entry_line_start, &current_instance)) return false;
}
else {
if (!parse_line(line, &current_instance)) {
printf("Failed to parse line %d: '%S'\n", line_count, line);
return false;
}
if (!parsing_entry) {
parsing_entry = true;
entry_line_start = line_count;
}
}
}
if (parsing_entry) {
current_instance.install_date = ft;
if (!add_entry(entry_line_start, &current_instance)) return false;
}
return (slot_count > 0);
}
static bool unload_config() {
memset(slots, 0, sizeof(slots));
free(config_data);
config_data = nullptr;
config_size = 0;
return true;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
this_module = hModule;
return load_config(hModule) ? TRUE : FALSE;
}
case DLL_PROCESS_DETACH: return unload_config() ? TRUE : FALSE;
}
return TRUE;
}
static auto cls = SetupConfigurationClass();
_Check_return_ STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID *ppv) {
if (rclsid != CLSID_SetupConfiguration) {
*ppv = nullptr;
return E_NOTIMPL;
}
*ppv = &cls;
return S_OK;
}
static void print_status(LSTATUS status) {
wchar_t msg[128];
memset(msg, 0, sizeof(msg));
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, status, 0, msg, sizeof(msg) / sizeof(msg[0]), nullptr);
wchar_t fmt[512];
swprintf_s(fmt, L"STATUS(%08lx): %s", status, msg);
OutputDebugStringW(fmt);
}
_Check_return_ STDAPI DllRegisterServer() {
wchar_t path[1024];
auto len = GetModuleFileNameW(this_module, path, sizeof(path) / sizeof(path[0]));
wchar_t szKey[64];
auto clsid = CLSID_SetupConfiguration;
swprintf_s(szKey, L"CLSID\\{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
clsid.Data1, clsid.Data2, clsid.Data3,
clsid.Data4[0], clsid.Data4[1], clsid.Data4[2], clsid.Data4[3],
clsid.Data4[4], clsid.Data4[5], clsid.Data4[6], clsid.Data4[7]);
HKEY classKey, procKey;
auto ret = RegCreateKeyExW(HKEY_CLASSES_ROOT, szKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &classKey, NULL);
if (ret != ERROR_SUCCESS) {
print_status(ret);
return E_FAIL;
}
ret = RegCreateKeyExW(classKey, L"InprocServer32", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &procKey, NULL);
if (ret != ERROR_SUCCESS) {
print_status(ret);
return E_FAIL;
}
RegSetValueExW(procKey, nullptr, 0, REG_SZ, (BYTE *)path, DWORD((wcslen(path) + 1) * sizeof(wchar_t)));
RegSetValueExW(procKey, L"ThreadingModel", 0, REG_SZ, (BYTE *)L"Both", 5 * sizeof(wchar_t));
RegCloseKey(procKey);
RegCloseKey(classKey);
for (long i = 0; i < slot_count; i++) {
wchar_t key_name[128];
wchar_t path[4096];
HKEY key;
auto ver = slots[i].sdk_version;
auto path_raw = slots[i].sdk_path;
swprintf_s(path, L"%s", path_raw);
PathCchAddBackslashEx(path, sizeof(path) / sizeof(path[0]), nullptr, nullptr);
swprintf_s(key_name, L"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots\\%s", ver);
RegCreateKeyExW(HKEY_LOCAL_MACHINE, key_name, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &key, NULL);
RegCloseKey(key);
swprintf_s(key_name, L"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots");
RegCreateKeyExW(HKEY_LOCAL_MACHINE, key_name, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &key, NULL);
RegSetValueExW(key, L"KitsRoot10", 0, REG_SZ, (BYTE *)path, DWORD((wcslen(path) + 1) * sizeof(wchar_t)));
RegCloseKey(key);
swprintf_s(key_name, L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows Kits\\Installed Roots\\%s", ver);
RegCreateKeyExW(HKEY_LOCAL_MACHINE, key_name, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &key, NULL);
RegCloseKey(key);
swprintf_s(key_name, L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows Kits\\Installed Roots");
RegCreateKeyExW(HKEY_LOCAL_MACHINE, key_name, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &key, NULL);
RegSetValueExW(key, L"KitsRoot10", 0, REG_SZ, (BYTE *)path, DWORD((wcslen(path) + 1) * sizeof(wchar_t)));
RegCloseKey(key);
}
return S_OK;
}
_Check_return_ STDAPI DllUnregisterServer() {
wchar_t szKey[64];
auto clsid = CLSID_SetupConfiguration;
swprintf_s(szKey, L"CLSID\\{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\\InprocServer32",
clsid.Data1, clsid.Data2, clsid.Data3,
clsid.Data4[0], clsid.Data4[1], clsid.Data4[2], clsid.Data4[3],
clsid.Data4[4], clsid.Data4[5], clsid.Data4[6], clsid.Data4[7]);
RegDeleteKeyW(HKEY_CLASSES_ROOT, szKey);
swprintf_s(szKey, L"CLSID\\{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
clsid.Data1, clsid.Data2, clsid.Data3,
clsid.Data4[0], clsid.Data4[1], clsid.Data4[2], clsid.Data4[3],
clsid.Data4[4], clsid.Data4[5], clsid.Data4[6], clsid.Data4[7]);
RegDeleteKeyW(HKEY_CLASSES_ROOT, szKey);
return S_OK;
}
EXPORTS
DllGetClassObject PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment