use anyhow::{bail, Result};
use std::mem;
use winapi::{
shared::{
minwindef::{DWORD, FARPROC},
ntdef::{LPCSTR, NTSTATUS},
ntstatus::STATUS_SUCCESS,
},
um::{
libloaderapi::{GetModuleHandleA, GetProcAddress},
sysinfoapi::{GetSystemInfo, SYSTEM_INFO},
winnt::{PROCESSOR_ARCHITECTURE_AMD64, VER_NT_WORKSTATION, VER_SUITE_WH_SERVER},
winuser::{GetSystemMetrics, SM_SERVERR2},
},
};
#[cfg(target_arch = "x86")]
type OSVERSIONINFOEX = winapi::um::winnt::OSVERSIONINFOEXA;
#[cfg(not(target_arch = "x86"))]
type OSVERSIONINFOEX = winapi::um::winnt::OSVERSIONINFOEXW;
pub fn version_info() -> Result<OSVERSIONINFOEX> {
let rtl_get_version = get_proc_address(b"ntdll\0", b"RtlGetVersion\0")?;
type RtlGetVersion = unsafe extern "system" fn(&mut OSVERSIONINFOEX) -> NTSTATUS;
let rtl_get_version: RtlGetVersion = unsafe { mem::transmute(rtl_get_version) };
let mut info: OSVERSIONINFOEX = unsafe { mem::zeroed() };
info.dwOSVersionInfoSize = mem::size_of::<OSVERSIONINFOEX>() as DWORD;
if unsafe { rtl_get_version(&mut info) } == STATUS_SUCCESS {
Ok(info)
} else {
bail!("Failed to get version")
}
}
fn get_proc_address(module: &[u8], proc: &[u8]) -> Result<FARPROC> {
assert!(
*module.last().expect("Empty module name") == 0,
"Module name should be zero-terminated"
);
assert!(
*proc.last().expect("Empty procedure name") == 0,
"Procedure name should be zero-terminated"
);
let handle = unsafe { GetModuleHandleA(module.as_ptr() as LPCSTR) };
if handle.is_null() {
bail!(
"GetModuleHandleA({}) failed",
String::from_utf8_lossy(module)
);
}
unsafe { Ok(GetProcAddress(handle, proc.as_ptr() as LPCSTR)) }
}
pub fn edition(version_info: &OSVERSIONINFOEX) -> String {
match (
version_info.dwMajorVersion,
version_info.dwMinorVersion,
version_info.wProductType,
) {
(10, 0, VER_NT_WORKSTATION) => "10".to_string(),
(10, 0, _) => "server 2016".to_string(),
(6, 3, VER_NT_WORKSTATION) => "8.1".to_string(),
(6, 3, _) => "server 2012 r2".to_string(),
(6, 2, VER_NT_WORKSTATION) => "8".to_string(),
(6, 2, _) => "server 2012".to_string(),
(6, 1, VER_NT_WORKSTATION) => "7".to_string(),
(6, 1, _) => "server 2008 r2".to_string(),
(6, 0, VER_NT_WORKSTATION) => "vista".to_string(),
(6, 0, _) => "server 2008".to_string(),
(5, 1, _) => "xp".to_string(),
(5, 0, _) => "2000".to_string(),
(5, 2, _) if unsafe { GetSystemMetrics(SM_SERVERR2) } == 0 => {
let mut info: SYSTEM_INFO = unsafe { mem::zeroed() };
unsafe { GetSystemInfo(&mut info) };
if Into::<DWORD>::into(version_info.wSuiteMask) & VER_SUITE_WH_SERVER
== VER_SUITE_WH_SERVER
{
"home server".to_string()
} else if version_info.wProductType == VER_NT_WORKSTATION
&& unsafe { info.u.s().wProcessorArchitecture } == PROCESSOR_ARCHITECTURE_AMD64
{
"xp professional x64 edition".to_string()
} else {
"server 2003".to_string()
}
}
(major, minor, _) => format!("{}.{}", major, minor),
}
}