windows_version/
lib.rs

1#![doc = include_str!("../readme.md")]
2#![cfg(windows)]
3#![cfg_attr(not(test), no_std)]
4#![allow(non_snake_case, non_camel_case_types, clippy::upper_case_acronyms)]
5
6mod bindings;
7use bindings::*;
8
9/// Operating system version information.
10#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
11pub struct OsVersion {
12    /// The major version number of the operating system.
13    pub major: u32,
14
15    /// The minor version number of the operating system.
16    pub minor: u32,
17
18    /// The major version number of the latest service pack installed on the system.
19    pub pack: u32,
20
21    /// The build number of the operating system.
22    pub build: u32,
23}
24
25impl OsVersion {
26    /// Creates a new `OsVersion` with the given values.
27    pub const fn new(major: u32, minor: u32, pack: u32, build: u32) -> Self {
28        Self {
29            major,
30            minor,
31            pack,
32            build,
33        }
34    }
35
36    /// Gets the version information of the currently running operating system.
37    #[cfg(not(test))]
38    pub fn current() -> Self {
39        let mut info = OSVERSIONINFOEXW::new();
40
41        unsafe {
42            RtlGetVersion(&mut info as *mut _ as *mut _);
43        }
44
45        Self {
46            major: info.dwMajorVersion,
47            minor: info.dwMinorVersion,
48            pack: info.wServicePackMajor as u32,
49            build: info.dwBuildNumber,
50        }
51    }
52
53    /// Hook used for testing `ge`.
54    #[cfg(test)]
55    fn current() -> Self {
56        test::test_current()
57    }
58}
59
60/// Determines if the currently running operating system is a Windows Server release.
61pub fn is_server() -> bool {
62    let mut info = OSVERSIONINFOEXW::new();
63
64    unsafe {
65        RtlGetVersion(&mut info as *mut _ as *mut _);
66    }
67
68    info.wProductType as u32 != VER_NT_WORKSTATION
69}
70
71/// Gets the revision number of the operating system.
72pub fn revision() -> u32 {
73    let mut value = [0; 4];
74    let mut len = 4;
75
76    let result = unsafe {
77        RegGetValueA(
78            HKEY_LOCAL_MACHINE,
79            b"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\0".as_ptr(),
80            b"UBR\0".as_ptr(),
81            RRF_RT_REG_DWORD,
82            core::ptr::null_mut(),
83            value.as_mut_ptr() as _,
84            &mut len,
85        )
86    };
87
88    if result == 0 {
89        u32::from_le_bytes(value)
90    } else {
91        0
92    }
93}
94
95impl OSVERSIONINFOEXW {
96    fn new() -> Self {
97        Self {
98            dwOSVersionInfoSize: core::mem::size_of::<Self>() as u32,
99            ..Default::default()
100        }
101    }
102}
103
104#[cfg(test)]
105#[allow(clippy::nonminimal_bool)] // explicit logic is intentionally being tested
106mod test {
107    use super::*;
108    use std::sync::RwLock;
109
110    static TEST_CURRENT: RwLock<OsVersion> = RwLock::new(OsVersion::new(0, 0, 0, 0));
111
112    pub fn test_current() -> OsVersion {
113        *TEST_CURRENT.read().unwrap()
114    }
115
116    fn set_current(version: OsVersion) {
117        *TEST_CURRENT.write().unwrap() = version;
118    }
119
120    #[test]
121    fn test() {
122        assert_eq!(OsVersion::current(), OsVersion::new(0, 0, 0, 0));
123
124        set_current(OsVersion::new(1, 2, 3, 4));
125        assert_eq!(OsVersion::current(), OsVersion::new(1, 2, 3, 4));
126
127        set_current(OsVersion::new(10, 0, 0, 0));
128        assert!(OsVersion::current() >= OsVersion::new(9, 0, 0, 0));
129        assert!(OsVersion::current() >= OsVersion::new(10, 0, 0, 0));
130        assert!(!(OsVersion::current() >= OsVersion::new(11, 0, 0, 0)));
131
132        set_current(OsVersion::new(10, 100, 0, 0));
133        assert!(OsVersion::current() >= OsVersion::new(10, 99, 0, 0));
134        assert!(OsVersion::current() >= OsVersion::new(10, 100, 0, 0));
135        assert!(!(OsVersion::current() >= OsVersion::new(10, 101, 0, 0)));
136
137        set_current(OsVersion::new(10, 100, 1000, 0));
138        assert!(OsVersion::current() >= OsVersion::new(10, 100, 999, 0));
139        assert!(OsVersion::current() >= OsVersion::new(10, 100, 1000, 0));
140        assert!(!(OsVersion::current() >= OsVersion::new(10, 100, 1001, 0)));
141
142        set_current(OsVersion::new(10, 100, 1_000, 10_000));
143        assert!(OsVersion::current() >= OsVersion::new(10, 100, 1_000, 9_999));
144        assert!(OsVersion::current() >= OsVersion::new(10, 100, 1_000, 10_000));
145        assert!(!(OsVersion::current() >= OsVersion::new(10, 100, 1_000, 10_001)));
146    }
147
148    // These tests just ensure the queries succeed without validating the results.
149    #[test]
150    fn test_uncertain() {
151        is_server();
152        assert_ne!(revision(), 0);
153    }
154}