1 /**
2  * When compiling on Windows with the Microsoft toolchain, try to detect the Visual Studio setup.
3  *
4  * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/link.d, _vsoptions.d)
8  * Documentation:  https://dlang.org/phobos/dmd_vsoptions.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/vsoptions.d
10  */
11 
12 module dmd.vsoptions;
13 
14 version (Windows):
15 import core.stdc.ctype;
16 import core.stdc.stdlib;
17 import core.stdc.string;
18 import core.sys.windows.winbase;
19 import core.sys.windows.windef;
20 import core.sys.windows.winreg;
21 
22 import dmd.env;
23 import dmd.root.file;
24 import dmd.root.filename;
25 import dmd.root.outbuffer;
26 import dmd.root.rmem;
27 
28 struct VSOptions
29 {
30     // evaluated once at startup, reflecting the result of vcvarsall.bat
31     //  from the current environment or the latest Visual Studio installation
32     const(char)* WindowsSdkDir;
33     const(char)* WindowsSdkVersion;
34     const(char)* UCRTSdkDir;
35     const(char)* UCRTVersion;
36     const(char)* VSInstallDir;
37     const(char)* VCInstallDir;
38     const(char)* VCToolsInstallDir; // used by VS 2017+
39 
40     /**
41      * fill member variables from environment or registry
42      */
43     void initialize()
44     {
45         detectWindowsSDK();
46         detectUCRT();
47         detectVSInstallDir();
48         detectVCInstallDir();
49         detectVCToolsInstallDir();
50     }
51 
52     /**
53      * retrieve the name of the default C runtime library
54      * Params:
55      *   x64 = target architecture (x86 if false)
56      * Returns:
57      *   name of the default C runtime library
58      */
59     const(char)* defaultRuntimeLibrary(bool x64)
60     {
61         if (VCInstallDir is null)
62         {
63             detectVCInstallDir();
64             detectVCToolsInstallDir();
65         }
66         if (getVCLibDir(x64))
67             return "libcmt";
68         else
69             return "msvcrt120"; // mingw replacement
70     }
71 
72     /**
73      * retrieve options to be passed to the Microsoft linker
74      * Params:
75      *   x64 = target architecture (x86 if false)
76      * Returns:
77      *   allocated string of options to add to the linker command line
78      */
79     const(char)* linkOptions(bool x64)
80     {
81         OutBuffer cmdbuf;
82         if (auto vclibdir = getVCLibDir(x64))
83         {
84             cmdbuf.writestring(" /LIBPATH:\"");
85             cmdbuf.writestring(vclibdir);
86             cmdbuf.writeByte('\"');
87 
88             if (FileName.exists(FileName.combine(vclibdir, "legacy_stdio_definitions.lib")))
89             {
90                 // VS2015 or later use UCRT
91                 cmdbuf.writestring(" legacy_stdio_definitions.lib");
92                 if (auto p = getUCRTLibPath(x64))
93                 {
94                     cmdbuf.writestring(" /LIBPATH:\"");
95                     cmdbuf.writestring(p);
96                     cmdbuf.writeByte('\"');
97                 }
98             }
99         }
100         if (auto p = getSDKLibPath(x64))
101         {
102             cmdbuf.writestring(" /LIBPATH:\"");
103             cmdbuf.writestring(p);
104             cmdbuf.writeByte('\"');
105         }
106         if (auto p = getenv("DXSDK_DIR"))
107         {
108             // support for old DX SDK installations
109             cmdbuf.writestring(" /LIBPATH:\"");
110             cmdbuf.writestring(p);
111             cmdbuf.writestring(x64 ? `\Lib\x64"` : `\Lib\x86"`);
112         }
113         return cmdbuf.extractChars();
114     }
115 
116     /**
117      * retrieve path to the Microsoft linker executable
118      * also modifies PATH environment variable if necessary to find conditionally loaded DLLs
119      * Params:
120      *   x64 = target architecture (x86 if false)
121      * Returns:
122      *   absolute path to link.exe, just "link.exe" if not found
123      */
124     const(char)* linkerPath(bool x64)
125     {
126         const(char)* addpath;
127         if (auto p = getVCBinDir(x64, addpath))
128         {
129             OutBuffer cmdbuf;
130             cmdbuf.writestring(p);
131             cmdbuf.writestring(r"\link.exe");
132             if (addpath)
133             {
134                 // debug info needs DLLs from $(VSInstallDir)\Common7\IDE for most linker versions
135                 //  so prepend it too the PATH environment variable
136                 const path = getenv("PATH");
137                 const pathlen = strlen(path);
138                 const addpathlen = strlen(addpath);
139 
140                 const length = addpathlen + 1 + pathlen;
141                 char* npath = cast(char*)mem.xmalloc(length);
142                 memcpy(npath, addpath, addpathlen);
143                 npath[addpathlen] = ';';
144                 memcpy(npath + addpathlen + 1, path, pathlen);
145                 if (putenvRestorable("PATH", npath[0 .. length]))
146                     assert(0);
147                 mem.xfree(npath);
148             }
149             return cmdbuf.extractChars();
150         }
151 
152         // try lld-link.exe alongside dmd.exe
153         char[MAX_PATH + 1] dmdpath = void;
154         const len = GetModuleFileNameA(null, dmdpath.ptr, dmdpath.length);
155         if (len <= MAX_PATH)
156         {
157             auto lldpath = FileName.replaceName(dmdpath[0 .. len], "lld-link.exe");
158             if (FileName.exists(lldpath))
159                 return lldpath.ptr;
160         }
161 
162         // search PATH to avoid createProcess preferring "link.exe" from the dmd folder
163         if (auto p = FileName.searchPath(getenv("PATH"), "link.exe"[], false))
164             return p.ptr;
165         return "link.exe";
166     }
167 
168 private:
169     /**
170      * detect WindowsSdkDir and WindowsSDKVersion from environment or registry
171      */
172     void detectWindowsSDK()
173     {
174         if (WindowsSdkDir is null)
175             WindowsSdkDir = getenv("WindowsSdkDir");
176 
177         if (WindowsSdkDir is null)
178         {
179             WindowsSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10");
180             if (WindowsSdkDir && !findLatestSDKDir(FileName.combine(WindowsSdkDir, "Include"), r"um\windows.h"))
181                 WindowsSdkDir = null;
182         }
183         if (WindowsSdkDir is null)
184         {
185             WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows\v8.1", "InstallationFolder");
186             if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib")))
187                 WindowsSdkDir = null;
188         }
189         if (WindowsSdkDir is null)
190         {
191             WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows\v8.0", "InstallationFolder");
192             if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib")))
193                 WindowsSdkDir = null;
194         }
195         if (WindowsSdkDir is null)
196         {
197             WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows", "CurrentInstallationFolder");
198             if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib")))
199                 WindowsSdkDir = null;
200         }
201 
202         if (WindowsSdkVersion is null)
203             WindowsSdkVersion = getenv("WindowsSdkVersion");
204 
205         if (WindowsSdkVersion is null && WindowsSdkDir !is null)
206         {
207             const(char)* rootsDir = FileName.combine(WindowsSdkDir, "Include");
208             WindowsSdkVersion = findLatestSDKDir(rootsDir, r"um\windows.h");
209         }
210     }
211 
212     /**
213      * detect UCRTSdkDir and UCRTVersion from environment or registry
214      */
215     void detectUCRT()
216     {
217         if (UCRTSdkDir is null)
218             UCRTSdkDir = getenv("UniversalCRTSdkDir");
219 
220         if (UCRTSdkDir is null)
221             UCRTSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10");
222 
223         if (UCRTVersion is null)
224             UCRTVersion = getenv("UCRTVersion");
225 
226         if (UCRTVersion is null && UCRTSdkDir !is null)
227         {
228             const(char)* rootsDir = FileName.combine(UCRTSdkDir, "Lib");
229             UCRTVersion = findLatestSDKDir(rootsDir, r"ucrt\x86\libucrt.lib");
230         }
231     }
232 
233     /**
234      * detect VSInstallDir from environment or registry
235      */
236     void detectVSInstallDir()
237     {
238         if (VSInstallDir is null)
239             VSInstallDir = getenv("VSINSTALLDIR");
240 
241         if (VSInstallDir is null)
242             VSInstallDir = detectVSInstallDirViaCOM();
243 
244         if (VSInstallDir is null)
245             VSInstallDir = GetRegistryString(r"Microsoft\VisualStudio\SxS\VS7", "15.0"); // VS2017
246 
247         if (VSInstallDir is null)
248             foreach (const(char)* ver; ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"])
249             {
250                 VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir");
251                 if (VSInstallDir)
252                     break;
253             }
254     }
255 
256     /**
257      * detect VCInstallDir from environment or registry
258      */
259     void detectVCInstallDir()
260     {
261         if (VCInstallDir is null)
262             VCInstallDir = getenv("VCINSTALLDIR");
263 
264         if (VCInstallDir is null)
265             if (VSInstallDir && FileName.exists(FileName.combine(VSInstallDir, "VC")))
266                 VCInstallDir = FileName.combine(VSInstallDir, "VC");
267 
268         // detect from registry (build tools?)
269         if (VCInstallDir is null)
270             foreach (const(char)* ver; ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"])
271             {
272                 auto regPath = FileName.buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC");
273                 VCInstallDir = GetRegistryString(regPath, "ProductDir");
274                 if (VCInstallDir)
275                     break;
276             }
277     }
278 
279     /**
280      * detect VCToolsInstallDir from environment or registry (only used by VC 2017)
281      */
282     void detectVCToolsInstallDir()
283     {
284         if (VCToolsInstallDir is null)
285             VCToolsInstallDir = getenv("VCTOOLSINSTALLDIR");
286 
287         if (VCToolsInstallDir is null && VCInstallDir)
288         {
289             const(char)* defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
290             if (!FileName.exists(defverFile)) // file renamed with VS2019 Preview 2
291                 defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.v142.default.txt");
292             if (FileName.exists(defverFile))
293             {
294                 // VS 2017
295                 auto readResult = File.read(defverFile); // adds sentinel 0 at end of file
296                 if (readResult.success)
297                 {
298                     auto ver = cast(char*)readResult.buffer.data.ptr;
299                     // trim version number
300                     while (*ver && isspace(*ver))
301                         ver++;
302                     auto p = ver;
303                     while (*p == '.' || (*p >= '0' && *p <= '9'))
304                         p++;
305                     *p = 0;
306 
307                     if (ver && *ver)
308                         VCToolsInstallDir = FileName.buildPath(VCInstallDir, r"Tools\MSVC", ver);
309                 }
310             }
311         }
312     }
313 
314     /**
315      * get Visual C bin folder
316      * Params:
317      *   x64 = target architecture (x86 if false)
318      *   addpath = [out] path that needs to be added to the PATH environment variable
319      * Returns:
320      *   folder containing the VC executables
321      *
322      * Selects the binary path according to the host and target OS, but verifies
323      * that link.exe exists in that folder and falls back to 32-bit host/target if
324      * missing
325      * Note: differences for the linker binaries are small, they all
326      * allow cross compilation
327      */
328     const(char)* getVCBinDir(bool x64, out const(char)* addpath)
329     {
330         static const(char)* linkExists(const(char)* p)
331         {
332             auto lp = FileName.combine(p, "link.exe");
333             return FileName.exists(lp) ? p : null;
334         }
335 
336         const bool isHost64 = isWin64Host();
337         if (VCToolsInstallDir !is null)
338         {
339             if (isHost64)
340             {
341                 if (x64)
342                 {
343                     if (auto p = linkExists(FileName.combine(VCToolsInstallDir, r"bin\HostX64\x64")))
344                         return p;
345                     // in case of missing linker, prefer other host binaries over other target architecture
346                 }
347                 else
348                 {
349                     if (auto p = linkExists(FileName.combine(VCToolsInstallDir, r"bin\HostX64\x86")))
350                     {
351                         addpath = FileName.combine(VCToolsInstallDir, r"bin\HostX64\x64");
352                         return p;
353                     }
354                 }
355             }
356             if (x64)
357             {
358                 if (auto p = linkExists(FileName.combine(VCToolsInstallDir, r"bin\HostX86\x64")))
359                 {
360                     addpath = FileName.combine(VCToolsInstallDir, r"bin\HostX86\x86");
361                     return p;
362                 }
363             }
364             if (auto p = linkExists(FileName.combine(VCToolsInstallDir, r"bin\HostX86\x86")))
365                 return p;
366         }
367         if (VCInstallDir !is null)
368         {
369             if (isHost64)
370             {
371                 if (x64)
372                 {
373                     if (auto p = linkExists(FileName.combine(VCInstallDir, r"bin\amd64")))
374                         return p;
375                     // in case of missing linker, prefer other host binaries over other target architecture
376                 }
377                 else
378                 {
379                     if (auto p = linkExists(FileName.combine(VCInstallDir, r"bin\amd64_x86")))
380                     {
381                         addpath = FileName.combine(VCInstallDir, r"bin\amd64");
382                         return p;
383                     }
384                 }
385             }
386 
387             if (VSInstallDir)
388                 addpath = FileName.combine(VSInstallDir, r"Common7\IDE");
389             else
390                 addpath = FileName.combine(VCInstallDir, r"bin");
391 
392             if (x64)
393                 if (auto p = linkExists(FileName.combine(VCInstallDir, r"x86_amd64")))
394                     return p;
395 
396             if (auto p = linkExists(FileName.combine(VCInstallDir, r"bin\HostX86\x86")))
397                 return p;
398         }
399         return null;
400     }
401 
402     /**
403     * get Visual C Library folder
404     * Params:
405     *   x64 = target architecture (x86 if false)
406     * Returns:
407     *   folder containing the the VC runtime libraries
408     */
409     const(char)* getVCLibDir(bool x64)
410     {
411         if (VCToolsInstallDir !is null)
412             return FileName.combine(VCToolsInstallDir, x64 ? r"lib\x64" : r"lib\x86");
413         if (VCInstallDir !is null)
414             return FileName.combine(VCInstallDir, x64 ? r"lib\amd64" : "lib");
415         return null;
416     }
417 
418     /**
419      * get the path to the universal CRT libraries
420      * Params:
421      *   x64 = target architecture (x86 if false)
422      * Returns:
423      *   folder containing the universal CRT libraries
424      */
425     const(char)* getUCRTLibPath(bool x64)
426     {
427         if (UCRTSdkDir && UCRTVersion)
428            return FileName.buildPath(UCRTSdkDir, "Lib", UCRTVersion, x64 ? r"ucrt\x64" : r"ucrt\x86");
429         return null;
430     }
431 
432     /**
433      * get the path to the Windows SDK CRT libraries
434      * Params:
435      *   x64 = target architecture (x86 if false)
436      * Returns:
437      *   folder containing the Windows SDK libraries
438      */
439     const(char)* getSDKLibPath(bool x64)
440     {
441         if (WindowsSdkDir)
442         {
443             const(char)* arch = x64 ? "x64" : "x86";
444             auto sdk = FileName.combine(WindowsSdkDir, "lib");
445             if (WindowsSdkVersion &&
446                 FileName.exists(FileName.buildPath(sdk, WindowsSdkVersion, "um", arch, "kernel32.lib"))) // SDK 10.0
447                 return FileName.buildPath(sdk, WindowsSdkVersion, "um", arch);
448             else if (FileName.exists(FileName.buildPath(sdk, r"win8\um", arch, "kernel32.lib"))) // SDK 8.0
449                 return FileName.buildPath(sdk, r"win8\um", arch);
450             else if (FileName.exists(FileName.buildPath(sdk, r"winv6.3\um", arch, "kernel32.lib"))) // SDK 8.1
451                 return FileName.buildPath(sdk, r"winv6.3\um", arch);
452             else if (x64 && FileName.exists(FileName.buildPath(sdk, arch, "kernel32.lib"))) // SDK 7.1 or earlier
453                 return FileName.buildPath(sdk, arch);
454             else if (!x64 && FileName.exists(FileName.buildPath(sdk, "kernel32.lib"))) // SDK 7.1 or earlier
455                 return sdk;
456         }
457 
458         // try mingw fallback relative to phobos library folder that's part of LIB
459         if (auto p = FileName.searchPath(getenv("LIB"), r"mingw\kernel32.lib"[], false))
460             return FileName.path(p).ptr;
461 
462         return null;
463     }
464 
465     // iterate through subdirectories named by SDK version in baseDir and return the
466     //  one with the largest version that also contains the test file
467     static const(char)* findLatestSDKDir(const(char)* baseDir, const(char)* testfile)
468     {
469         auto allfiles = FileName.combine(baseDir, "*");
470         WIN32_FIND_DATAA fileinfo;
471         HANDLE h = FindFirstFileA(allfiles, &fileinfo);
472         if (h == INVALID_HANDLE_VALUE)
473             return null;
474 
475         char* res = null;
476         do
477         {
478             if (fileinfo.cFileName[0] >= '1' && fileinfo.cFileName[0] <= '9')
479                 if (res is null || strcmp(res, fileinfo.cFileName.ptr) < 0)
480                     if (FileName.exists(FileName.buildPath(baseDir, fileinfo.cFileName.ptr, testfile)))
481                     {
482                         const len = strlen(fileinfo.cFileName.ptr) + 1;
483                         res = cast(char*) memcpy(mem.xrealloc(res, len), fileinfo.cFileName.ptr, len);
484                     }
485         }
486         while(FindNextFileA(h, &fileinfo));
487 
488         if (!FindClose(h))
489             res = null;
490         return res;
491     }
492 
493     pragma(lib, "advapi32.lib");
494 
495     /**
496      * read a string from the 32-bit registry
497      * Params:
498      *  softwareKeyPath = path below HKLM\SOFTWARE
499      *  valueName       = name of the value to read
500      * Returns:
501      *  the registry value if it exists and has string type
502      */
503     const(char)* GetRegistryString(const(char)* softwareKeyPath, const(char)* valueName)
504     {
505         enum x64hive = false; // VS registry entries always in 32-bit hive
506 
507         version(Win64)
508             enum prefix = x64hive ? r"SOFTWARE\" : r"SOFTWARE\WOW6432Node\";
509         else
510             enum prefix = r"SOFTWARE\";
511 
512         char[260] regPath = void;
513         const len = strlen(softwareKeyPath);
514         assert(len + prefix.length < regPath.length);
515 
516         memcpy(regPath.ptr, prefix.ptr, prefix.length);
517         memcpy(regPath.ptr + prefix.length, softwareKeyPath, len + 1);
518 
519         enum KEY_WOW64_64KEY = 0x000100; // not defined in core.sys.windows.winnt due to restrictive version
520         enum KEY_WOW64_32KEY = 0x000200;
521         HKEY key;
522         LONG lRes = RegOpenKeyExA(HKEY_LOCAL_MACHINE, regPath.ptr, (x64hive ? KEY_WOW64_64KEY : KEY_WOW64_32KEY), KEY_READ, &key);
523         if (FAILED(lRes))
524             return null;
525         scope(exit) RegCloseKey(key);
526 
527         char[260] buf = void;
528         DWORD cnt = buf.length * char.sizeof;
529         DWORD type;
530         int hr = RegQueryValueExA(key, valueName, null, &type, cast(ubyte*) buf.ptr, &cnt);
531         if (hr == 0 && cnt > 0)
532             return buf.dup.ptr;
533         if (hr != ERROR_MORE_DATA || type != REG_SZ)
534             return null;
535 
536         scope char[] pbuf = new char[cnt + 1];
537         RegQueryValueExA(key, valueName, null, &type, cast(ubyte*) pbuf.ptr, &cnt);
538         return pbuf.ptr;
539     }
540 
541     /***
542      * get architecture of host OS
543      */
544     static bool isWin64Host()
545     {
546         version (Win64)
547         {
548             return true;
549         }
550         else
551         {
552             // running as a 32-bit process on a 64-bit host?
553             alias fnIsWow64Process = extern(Windows) BOOL function(HANDLE, PBOOL);
554             __gshared fnIsWow64Process pIsWow64Process;
555 
556             if (!pIsWow64Process)
557             {
558                 //IsWow64Process is not available on all supported versions of Windows.
559                 pIsWow64Process = cast(fnIsWow64Process) GetProcAddress(GetModuleHandleA("kernel32"), "IsWow64Process");
560                 if (!pIsWow64Process)
561                     return false;
562             }
563             BOOL bIsWow64 = FALSE;
564             if (!pIsWow64Process(GetCurrentProcess(), &bIsWow64))
565                 return false;
566 
567             return bIsWow64 != 0;
568         }
569     }
570 }
571 
572 ///////////////////////////////////////////////////////////////////////
573 // COM interfaces to find VS2017+ installations
574 import core.sys.windows.com;
575 import core.sys.windows.wtypes : BSTR;
576 import core.sys.windows.winnls : WideCharToMultiByte, CP_UTF8;
577 import core.sys.windows.oleauto : SysFreeString;
578 
579 pragma(lib, "ole32.lib");
580 pragma(lib, "oleaut32.lib");
581 
582 interface ISetupInstance : IUnknown
583 {
584     // static const GUID iid = uuid("B41463C3-8866-43B5-BC33-2B0676F7F42E");
585     static const GUID iid = { 0xB41463C3, 0x8866, 0x43B5, [ 0xBC, 0x33, 0x2B, 0x06, 0x76, 0xF7, 0xF4, 0x2E ] };
586 
587     int GetInstanceId(BSTR* pbstrInstanceId);
588     int GetInstallDate(LPFILETIME pInstallDate);
589     int GetInstallationName(BSTR* pbstrInstallationName);
590     int GetInstallationPath(BSTR* pbstrInstallationPath);
591     int GetInstallationVersion(BSTR* pbstrInstallationVersion);
592     int GetDisplayName(LCID lcid, BSTR* pbstrDisplayName);
593     int GetDescription(LCID lcid, BSTR* pbstrDescription);
594     int ResolvePath(LPCOLESTR pwszRelativePath, BSTR* pbstrAbsolutePath);
595 }
596 
597 interface IEnumSetupInstances : IUnknown
598 {
599     // static const GUID iid = uuid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848");
600 
601     int Next(ULONG celt, ISetupInstance* rgelt, ULONG* pceltFetched);
602     int Skip(ULONG celt);
603     int Reset();
604     int Clone(IEnumSetupInstances* ppenum);
605 }
606 
607 interface ISetupConfiguration : IUnknown
608 {
609     // static const GUID iid = uuid("42843719-DB4C-46C2-8E7C-64F1816EFD5B");
610     static const GUID iid = { 0x42843719, 0xDB4C, 0x46C2, [ 0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B ] };
611 
612     int EnumInstances(IEnumSetupInstances* ppEnumInstances) ;
613     int GetInstanceForCurrentProcess(ISetupInstance* ppInstance);
614     int GetInstanceForPath(LPCWSTR wzPath, ISetupInstance* ppInstance);
615 }
616 
617 const GUID iid_SetupConfiguration = { 0x177F0C4A, 0x1CD3, 0x4DE7, [ 0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D ] };
618 
619 const(char)* detectVSInstallDirViaCOM()
620 {
621     CoInitialize(null);
622     scope(exit) CoUninitialize();
623 
624     ISetupConfiguration setup;
625     IEnumSetupInstances instances;
626     ISetupInstance instance;
627     DWORD fetched;
628 
629     HRESULT hr = CoCreateInstance(&iid_SetupConfiguration, null, CLSCTX_ALL, &ISetupConfiguration.iid, cast(void**) &setup);
630     if (hr != S_OK || !setup)
631         return null;
632     scope(exit) setup.Release();
633 
634     if (setup.EnumInstances(&instances) != S_OK)
635         return null;
636     scope(exit) instances.Release();
637 
638     while (instances.Next(1, &instance, &fetched) == S_OK && fetched)
639     {
640         BSTR bstrInstallDir;
641         if (instance.GetInstallationPath(&bstrInstallDir) != S_OK)
642             continue;
643 
644         char[260] path;
645         int len = WideCharToMultiByte(CP_UTF8, 0, bstrInstallDir, -1, path.ptr, 260, null, null);
646         SysFreeString(bstrInstallDir);
647 
648         if (len > 0)
649             return path[0..len].idup.ptr;
650     }
651     return null;
652 }