1 // Copyright (c) 2016 Ryan Prichard
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to
5 // deal in the Software without restriction, including without limitation the
6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 // sell copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 #include "WindowsVersion.h"
30 #include "DebugClient.h"
32 #include "StringBuilder.h"
33 #include "StringUtil.h"
34 #include "WinptyAssert.h"
35 #include "WinptyException.h"
39 typedef std::tuple<DWORD, DWORD> Version;
41 // This function can only return a version up to 6.2 unless the executable is
42 // manifested for a newer version of Windows. See the MSDN documentation for
44 OSVERSIONINFOEX getWindowsVersionInfo() {
45 // Allow use of deprecated functions (i.e. GetVersionEx). We need to use
46 // GetVersionEx for the old MinGW toolchain and with MSVC when it targets XP.
47 // Having two code paths makes code harder to test, and it's not obvious how
48 // to detect the presence of a new enough SDK. (Including ntverp.h and
49 // examining VER_PRODUCTBUILD apparently works, but even then, MinGW-w64 and
50 // MSVC seem to use different version numbers.)
53 #pragma warning(disable:4996)
55 OSVERSIONINFOEX info = {};
56 info.dwOSVersionInfoSize = sizeof(info);
57 const auto success = GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&info));
58 ASSERT(success && "GetVersionEx failed");
65 Version getWindowsVersion() {
66 const auto info = getWindowsVersionInfo();
67 return Version(info.dwMajorVersion, info.dwMinorVersion);
70 struct ModuleNotFound : WinptyException {
71 virtual const wchar_t *what() const WINPTY_NOEXCEPT override {
72 return L"ModuleNotFound";
76 // Throws WinptyException on error.
77 std::wstring getSystemDirectory() {
78 wchar_t systemDirectory[MAX_PATH];
79 const UINT size = GetSystemDirectoryW(systemDirectory, MAX_PATH);
81 throwWindowsError(L"GetSystemDirectory failed");
82 } else if (size >= MAX_PATH) {
84 L"GetSystemDirectory: path is longer than MAX_PATH");
86 return systemDirectory;
89 #define GET_VERSION_DLL_API(name) \
90 const auto p ## name = \
91 reinterpret_cast<decltype(name)*>( \
92 versionDll.proc(#name)); \
93 if (p ## name == nullptr) { \
94 throwWinptyException(L ## #name L" is missing"); \
97 // Throws WinptyException on error.
98 VS_FIXEDFILEINFO getFixedFileInfo(const std::wstring &path) {
99 // version.dll is not a conventional KnownDll, so if we link to it, there's
100 // a danger of accidentally loading a malicious DLL. In a more typical
101 // application, perhaps we'd guard against this security issue by
102 // controlling which directories this code runs in (e.g. *not* the
103 // "Downloads" directory), but that's harder for the winpty library.
105 (getSystemDirectory() + L"\\version.dll").c_str(),
106 OsModule::LoadErrorBehavior::Throw);
107 GET_VERSION_DLL_API(GetFileVersionInfoSizeW);
108 GET_VERSION_DLL_API(GetFileVersionInfoW);
109 GET_VERSION_DLL_API(VerQueryValueW);
110 DWORD size = pGetFileVersionInfoSizeW(path.c_str(), nullptr);
112 // I see ERROR_FILE_NOT_FOUND on Win7 and
113 // ERROR_RESOURCE_DATA_NOT_FOUND on WinXP.
114 if (GetLastError() == ERROR_FILE_NOT_FOUND ||
115 GetLastError() == ERROR_RESOURCE_DATA_NOT_FOUND) {
116 throw ModuleNotFound();
119 (L"GetFileVersionInfoSizeW failed on " + path).c_str());
122 std::unique_ptr<char[]> versionBuffer(new char[size]);
123 if (!pGetFileVersionInfoW(path.c_str(), 0, size, versionBuffer.get())) {
124 throwWindowsError((L"GetFileVersionInfoW failed on " + path).c_str());
126 VS_FIXEDFILEINFO *versionInfo = nullptr;
127 UINT versionInfoSize = 0;
128 if (!pVerQueryValueW(
129 versionBuffer.get(), L"\\",
130 reinterpret_cast<void**>(&versionInfo), &versionInfoSize) ||
131 versionInfo == nullptr ||
132 versionInfoSize != sizeof(VS_FIXEDFILEINFO) ||
133 versionInfo->dwSignature != 0xFEEF04BD) {
134 throwWinptyException((L"VerQueryValueW failed on " + path).c_str());
139 uint64_t productVersionFromInfo(const VS_FIXEDFILEINFO &info) {
140 return (static_cast<uint64_t>(info.dwProductVersionMS) << 32) |
141 (static_cast<uint64_t>(info.dwProductVersionLS));
144 uint64_t fileVersionFromInfo(const VS_FIXEDFILEINFO &info) {
145 return (static_cast<uint64_t>(info.dwFileVersionMS) << 32) |
146 (static_cast<uint64_t>(info.dwFileVersionLS));
149 std::string versionToString(uint64_t version) {
151 b << ((uint16_t)(version >> 48));
153 b << ((uint16_t)(version >> 32));
155 b << ((uint16_t)(version >> 16));
157 b << ((uint16_t)(version >> 0));
158 return b.str_moved();
161 } // anonymous namespace
163 // Returns true for Windows Vista (or Windows Server 2008) or newer.
164 bool isAtLeastWindowsVista() {
165 return getWindowsVersion() >= Version(6, 0);
168 // Returns true for Windows 7 (or Windows Server 2008 R2) or newer.
169 bool isAtLeastWindows7() {
170 return getWindowsVersion() >= Version(6, 1);
173 // Returns true for Windows 8 (or Windows Server 2012) or newer.
174 bool isAtLeastWindows8() {
175 return getWindowsVersion() >= Version(6, 2);
178 #define WINPTY_IA32 1
181 #if defined(_M_IX86) || defined(__i386__)
182 #define WINPTY_ARCH WINPTY_IA32
183 #elif defined(_M_X64) || defined(__x86_64__)
184 #define WINPTY_ARCH WINPTY_X64
187 typedef BOOL WINAPI IsWow64Process_t(HANDLE hProcess, PBOOL Wow64Process);
189 void dumpWindowsVersion() {
190 if (!isTracingEnabled()) {
193 const auto info = getWindowsVersionInfo();
195 b << info.dwMajorVersion << '.' << info.dwMinorVersion
196 << '.' << info.dwBuildNumber << ' '
197 << "SP" << info.wServicePackMajor << '.' << info.wServicePackMinor
199 switch (info.wProductType) {
200 case VER_NT_WORKSTATION: b << "Client"; break;
201 case VER_NT_DOMAIN_CONTROLLER: b << "DomainController"; break;
202 case VER_NT_SERVER: b << "Server"; break;
204 b << "product=" << info.wProductType; break;
207 #if WINPTY_ARCH == WINPTY_IA32
209 OsModule kernel32(L"kernel32.dll");
210 IsWow64Process_t *pIsWow64Process =
211 reinterpret_cast<IsWow64Process_t*>(
212 kernel32.proc("IsWow64Process"));
213 if (pIsWow64Process != nullptr) {
215 const BOOL success = pIsWow64Process(GetCurrentProcess(), &result);
218 } else if (success && result) {
222 b << " WOW64:missingapi";
224 #elif WINPTY_ARCH == WINPTY_X64
227 const auto dllVersion = [](const wchar_t *dllPath) -> std::string {
229 const auto info = getFixedFileInfo(dllPath);
230 StringBuilder fb(64);
231 fb << utf8FromWide(dllPath) << ':';
232 fb << "F:" << versionToString(fileVersionFromInfo(info)) << '/'
233 << "P:" << versionToString(productVersionFromInfo(info));
234 return fb.str_moved();
235 } catch (const ModuleNotFound&) {
236 return utf8FromWide(dllPath) + ":none";
237 } catch (const WinptyException &e) {
238 trace("Error getting %s version: %s",
239 utf8FromWide(dllPath).c_str(), utf8FromWide(e.what()).c_str());
240 return utf8FromWide(dllPath) + ":error";
243 b << ' ' << dllVersion(L"kernel32.dll");
244 // ConEmu provides a DLL that hooks many Windows APIs, especially console
245 // APIs. Its existence and version number could be useful in debugging.
246 #if WINPTY_ARCH == WINPTY_IA32
247 b << ' ' << dllVersion(L"ConEmuHk.dll");
248 #elif WINPTY_ARCH == WINPTY_X64
249 b << ' ' << dllVersion(L"ConEmuHk64.dll");
251 trace("Windows version: %s", b.c_str());