1 // spell-checker:ignore dword, minwindef, ntdef, ntdll, ntstatus, osversioninfoex, osversioninfoexa
2 // spell-checker:ignore osversioninfoexw, serverr, sysinfoapi, winnt, winuser, pbool, libloaderapi
3 // spell-checker:ignore lpcstr, processthreadsapi, farproc, lstatus, wchar, lpbyte, hkey, winerror
4 // spell-checker:ignore osstr, winreg
9 ffi
::{OsStr, OsString}
,
11 os
::windows
::ffi
::{OsStrExt, OsStringExt}
,
17 minwindef
::{DWORD, FARPROC, LPBYTE}
,
18 ntdef
::{LPCSTR, NTSTATUS}
,
19 ntstatus
::STATUS_SUCCESS
,
20 winerror
::ERROR_SUCCESS
,
23 libloaderapi
::{GetModuleHandleA, GetProcAddress}
,
24 sysinfoapi
::{GetSystemInfo, SYSTEM_INFO}
,
26 KEY_READ
, PROCESSOR_ARCHITECTURE_AMD64
, REG_SZ
, VER_NT_WORKSTATION
,
27 VER_SUITE_WH_SERVER
, WCHAR
,
29 winreg
::{RegOpenKeyExW, RegQueryValueExW, HKEY_LOCAL_MACHINE, LSTATUS}
,
30 winuser
::{GetSystemMetrics, SM_SERVERR2}
,
34 use crate::{Bitness, Info, Type, Version}
;
36 #[cfg(target_arch = "x86")]
37 type OSVERSIONINFOEX
= winapi
::um
::winnt
::OSVERSIONINFOEXA
;
39 #[cfg(not(target_arch = "x86"))]
40 type OSVERSIONINFOEX
= winapi
::um
::winnt
::OSVERSIONINFOEXW
;
42 pub fn get() -> Info
{
43 let (version
, edition
) = version();
45 os_type
: Type
::Windows
,
53 fn version() -> (Version
, Option
<String
>) {
54 match version_info() {
55 None
=> (Version
::Unknown
, None
),
58 v
.dwMajorVersion
as u64,
59 v
.dwMinorVersion
as u64,
60 v
.dwBuildNumber
as u64,
62 product_name(&v
).or_else(|| edition(&v
)),
67 #[cfg(target_pointer_width = "64")]
68 fn bitness() -> Bitness
{
69 // x64 program can only run on x64 Windows.
73 #[cfg(target_pointer_width = "32")]
74 fn bitness() -> Bitness
{
77 minwindef
::{BOOL, FALSE, PBOOL}
,
80 um
::processthreadsapi
::GetCurrentProcess
,
83 // IsWow64Process is not available on all supported versions of Windows. Use GetModuleHandle to
84 // get a handle to the DLL that contains the function and GetProcAddress to get a pointer to the
85 // function if available.
86 let is_wow_64
= match get_proc_address(b
"kernel32\0", b
"IsWow64Process\0") {
87 None
=> return Bitness
::Unknown
,
91 type IsWow64
= unsafe extern "system" fn(HANDLE
, PBOOL
) -> BOOL
;
92 let is_wow_64
: IsWow64
= unsafe { mem::transmute(is_wow_64) }
;
94 let mut result
= FALSE
;
95 if unsafe { is_wow_64(GetCurrentProcess(), &mut result) }
== 0 {
96 log
::error
!("IsWow64Process failed");
97 return Bitness
::Unknown
;
107 // Calls the Win32 API function RtlGetVersion to get the OS version information:
108 // https://msdn.microsoft.com/en-us/library/mt723418(v=vs.85).aspx
109 fn version_info() -> Option
<OSVERSIONINFOEX
> {
110 let rtl_get_version
= match get_proc_address(b
"ntdll\0", b
"RtlGetVersion\0") {
115 type RtlGetVersion
= unsafe extern "system" fn(&mut OSVERSIONINFOEX
) -> NTSTATUS
;
116 let rtl_get_version
: RtlGetVersion
= unsafe { mem::transmute(rtl_get_version) }
;
118 let mut info
: OSVERSIONINFOEX
= unsafe { mem::zeroed() }
;
119 info
.dwOSVersionInfoSize
= mem
::size_of
::<OSVERSIONINFOEX
>() as DWORD
;
121 if unsafe { rtl_get_version(&mut info) }
== STATUS_SUCCESS
{
128 fn product_name(info
: &OSVERSIONINFOEX
) -> Option
<String
> {
129 const REG_SUCCESS
: LSTATUS
= ERROR_SUCCESS
as LSTATUS
;
131 let sub_key
= to_wide("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
132 let mut key
= ptr
::null_mut();
133 if unsafe { RegOpenKeyExW(HKEY_LOCAL_MACHINE, sub_key.as_ptr(), 0, KEY_READ, &mut key) }
137 log
::error
!("RegOpenKeyExW(HKEY_LOCAL_MACHINE, ...) failed");
141 let is_win_11
= info
.dwMajorVersion
== 10 && info
.dwBuildNumber
>= 22000;
143 // Get size of the data.
144 let name
= to_wide(if is_win_11
{
149 let mut data_type
: DWORD
= 0;
150 let mut data_size
: DWORD
= 0;
161 || data_type
!= REG_SZ
163 || data_size
% 2 != 0
165 log
::error
!("RegQueryValueExW failed");
170 let mut data
= vec
![0u16; data_size
as usize / 2];
177 data
.as_mut_ptr() as LPBYTE
,
181 || data_size
as usize != data
.len() * 2
186 // If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been
187 // stored with the proper terminating null characters.
195 let value
= OsString
::from_wide(data
.as_slice())
200 Some(format
!("Windows 11 {}", value
))
206 fn to_wide(value
: &str) -> Vec
<WCHAR
> {
207 OsStr
::new(value
).encode_wide().chain(Some(0)).collect()
210 // Examines data in the OSVERSIONINFOEX structure to determine the Windows edition:
211 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
212 fn edition(version_info
: &OSVERSIONINFOEX
) -> Option
<String
> {
214 version_info
.dwMajorVersion
,
215 version_info
.dwMinorVersion
,
216 version_info
.wProductType
,
219 (10, 0, VER_NT_WORKSTATION
) => {
220 if version_info
.dwBuildNumber
>= 22000 {
226 (10, 0, _
) => Some("Windows Server 2016"),
227 // Windows Vista, 7, 8 and 8.1.
228 (6, 3, VER_NT_WORKSTATION
) => Some("Windows 8.1"),
229 (6, 3, _
) => Some("Windows Server 2012 R2"),
230 (6, 2, VER_NT_WORKSTATION
) => Some("Windows 8"),
231 (6, 2, _
) => Some("Windows Server 2012"),
232 (6, 1, VER_NT_WORKSTATION
) => Some("Windows 7"),
233 (6, 1, _
) => Some("Windows Server 2008 R2"),
234 (6, 0, VER_NT_WORKSTATION
) => Some("Windows Vista"),
235 (6, 0, _
) => Some("Windows Server 2008"),
236 // Windows 2000, Home Server, 2003 Server, 2003 R2 Server, XP and XP Professional x64.
237 (5, 1, _
) => Some("Windows XP"),
238 (5, 0, _
) => Some("Windows 2000"),
239 (5, 2, _
) if unsafe { GetSystemMetrics(SM_SERVERR2) }
== 0 => {
240 let mut info
: SYSTEM_INFO
= unsafe { mem::zeroed() }
;
241 unsafe { GetSystemInfo(&mut info) }
;
243 if Into
::<DWORD
>::into(version_info
.wSuiteMask
) & VER_SUITE_WH_SERVER
244 == VER_SUITE_WH_SERVER
246 Some("Windows Home Server")
247 } else if version_info
.wProductType
== VER_NT_WORKSTATION
248 && unsafe { info.u.s().wProcessorArchitecture }
== PROCESSOR_ARCHITECTURE_AMD64
250 Some("Windows XP Professional x64 Edition")
252 Some("Windows Server 2003")
260 fn get_proc_address(module
: &[u8], proc
: &[u8]) -> Option
<FARPROC
> {
262 *module
.last().expect("Empty module name") == 0,
263 "Module name should be zero-terminated"
266 *proc
.last().expect("Empty procedure name") == 0,
267 "Procedure name should be zero-terminated"
270 let handle
= unsafe { GetModuleHandleA(module.as_ptr() as LPCSTR) }
;
271 if handle
.is_null() {
273 "GetModuleHandleA({}) failed",
274 String
::from_utf8_lossy(module
)
279 unsafe { Some(GetProcAddress(handle, proc.as_ptr() as LPCSTR)) }
285 use pretty_assertions
::{assert_eq, assert_ne}
;
290 assert_eq
!(Type
::Windows
, info
.os_type());
294 fn get_version_info() {
295 let version
= version_info();
296 assert
!(version
.is_some());
302 (10, 0, 0, "Windows Server 2016"),
303 (6, 3, VER_NT_WORKSTATION
, "Windows 8.1"),
304 (6, 3, 0, "Windows Server 2012 R2"),
305 (6, 2, VER_NT_WORKSTATION
, "Windows 8"),
306 (6, 2, 0, "Windows Server 2012"),
307 (6, 1, VER_NT_WORKSTATION
, "Windows 7"),
308 (6, 1, 0, "Windows Server 2008 R2"),
309 (6, 0, VER_NT_WORKSTATION
, "Windows Vista"),
310 (6, 0, 0, "Windows Server 2008"),
311 (5, 1, 0, "Windows XP"),
312 (5, 1, 1, "Windows XP"),
313 (5, 1, 100, "Windows XP"),
314 (5, 0, 0, "Windows 2000"),
315 (5, 0, 1, "Windows 2000"),
316 (5, 0, 100, "Windows 2000"),
319 let mut info
= version_info().unwrap();
321 for &(major
, minor
, product_type
, expected_edition
) in &test_data
{
322 info
.dwMajorVersion
= major
;
323 info
.dwMinorVersion
= minor
;
324 info
.wProductType
= product_type
;
326 let edition
= edition(&info
).unwrap();
327 assert_eq
!(edition
, expected_edition
);
334 assert_ne
!(b
, Bitness
::Unknown
);
338 #[should_panic(expected = "Empty module name")]
339 fn empty_module_name() {
340 get_proc_address(b
"", b
"RtlGetVersion\0");
344 #[should_panic(expected = "Module name should be zero-terminated")]
345 fn non_zero_terminated_module_name() {
346 get_proc_address(b
"ntdll", b
"RtlGetVersion\0");
350 #[should_panic(expected = "Empty procedure name")]
351 fn empty_proc_name() {
352 get_proc_address(b
"ntdll\0", b
"");
356 #[should_panic(expected = "Procedure name should be zero-terminated")]
357 fn non_zero_terminated_proc_name() {
358 get_proc_address(b
"ntdll\0", b
"RtlGetVersion");
363 let address
= get_proc_address(b
"ntdll\0", b
"RtlGetVersion\0");
364 assert
!(address
.is_some());
368 fn get_product_name() {
369 let version
= version_info().expect("version_info() failed");
370 let edition
= product_name(&version
).expect("edition() failed");
371 assert
!(!edition
.is_empty());
377 ("", [0x0000].as_ref()),
378 ("U", &[0x0055, 0x0000]),
379 ("你好", &[0x4F60, 0x597D, 0x0000]),
382 for (s
, expected
) in &data
{
383 let wide
= to_wide(s
);
384 assert_eq
!(&wide
, expected
);