cfun/
c_winapi.rs

1#[cfg(all(feature = "winapi", target_os = "windows"))]
2use windows::Win32::{
3    Foundation::CloseHandle,
4    System::Diagnostics::ToolHelp::{
5        CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W,
6        TH32CS_SNAPPROCESS,
7    },
8};
9
10/// get process id by process name on windows
11#[cfg(all(feature = "winapi", target_os = "windows"))]
12pub fn get_process_id_by_name(name: &str) -> Option<u32> {
13    unsafe {
14        let h_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
15        let Ok(h_snapshot) = h_snapshot else {
16            return None;
17        };
18        let mut pe = PROCESSENTRY32W {
19            dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
20            ..Default::default()
21        };
22        let ret = Process32FirstW(h_snapshot, &mut pe);
23        loop {
24            if ret.is_err() {
25                break;
26            }
27            let end_pos = pe.szExeFile.iter().position(|p| *p == 0);
28            let Some(end_pos) = end_pos else {
29                break;
30            };
31            let process_name = String::from_utf16_lossy(&pe.szExeFile[0..end_pos]);
32            if process_name == name {
33                let _ = CloseHandle(h_snapshot);
34                return Some(pe.th32ProcessID);
35            }
36            let ret = Process32NextW(h_snapshot, &mut pe);
37            if ret.is_err() {
38                break;
39            }
40        }
41        let _ = CloseHandle(h_snapshot);
42    }
43    None
44}
45
46/// service status
47#[cfg(all(feature = "winapi", target_os = "windows"))]
48#[derive(Debug)]
49pub enum ServStatus {
50    Uninstalled,
51    Running,
52    Paused,
53    Stoped,
54    StartPending,
55    StopPending,
56    PausePending,
57    ContinuePending,
58    Unknow,
59}
60
61/// query system service status
62#[cfg(all(feature = "winapi", target_os = "windows"))]
63pub fn sc_query(service_name: &str) -> Result<ServStatus, windows::core::Error> {
64    use windows::{
65        core::PWSTR,
66        Win32::System::Services::{
67            CloseServiceHandle, OpenSCManagerW, OpenServiceW, QueryServiceStatus,
68            SC_MANAGER_CONNECT, SERVICE_CONTINUE_PENDING, SERVICE_PAUSED, SERVICE_PAUSE_PENDING,
69            SERVICE_QUERY_STATUS, SERVICE_RUNNING, SERVICE_START_PENDING, SERVICE_STATUS,
70            SERVICE_STOPPED, SERVICE_STOP_PENDING,
71        },
72    };
73
74    unsafe {
75        let h_sc = OpenSCManagerW(None, None, SC_MANAGER_CONNECT)?;
76        let mut service_name: Vec<u16> = service_name.encode_utf16().chain([0]).collect();
77        let service_name = PWSTR::from_raw(service_name.as_mut_ptr());
78
79        let h_service = OpenServiceW(h_sc, service_name, SERVICE_QUERY_STATUS);
80        let Ok(h_service) = h_service else {
81            let err = h_service.unwrap_err();
82            if err.code().0 == 0x80070424u32 as i32 {
83                return Ok(ServStatus::Uninstalled);
84            }
85            return Err(err);
86        };
87        let mut s_status = SERVICE_STATUS::default();
88        QueryServiceStatus(h_service, &mut s_status)?;
89
90        let _ = CloseServiceHandle(h_service);
91        let _ = CloseServiceHandle(h_sc);
92
93        let status = match s_status.dwCurrentState {
94            SERVICE_RUNNING => ServStatus::Running,
95            SERVICE_PAUSED => ServStatus::Paused,
96            SERVICE_STOPPED => ServStatus::Stoped,
97            SERVICE_CONTINUE_PENDING => ServStatus::ContinuePending,
98            SERVICE_START_PENDING => ServStatus::StartPending,
99            SERVICE_PAUSE_PENDING => ServStatus::PausePending,
100            SERVICE_STOP_PENDING => ServStatus::StopPending,
101            _ => ServStatus::Unknow,
102        };
103        Ok(status)
104    }
105}
106
107/// run the program with administrator privileges
108#[cfg(all(feature = "winapi", target_os = "windows"))]
109pub fn runas(
110    exe_path: &str,
111    args: Option<Vec<String>>,
112    wait: bool,
113) -> Result<(), windows::core::Error> {
114    use windows::{
115        core::{w, PCWSTR},
116        Win32::{
117            System::{
118                Com::{
119                    CoInitializeEx, CoUninitialize, COINIT_APARTMENTTHREADED,
120                    COINIT_DISABLE_OLE1DDE,
121                },
122                Threading::{WaitForSingleObject, INFINITE},
123            },
124            UI::{
125                Shell::{
126                    ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SEE_MASK_UNICODE, SHELLEXECUTEINFOW,
127                },
128                WindowsAndMessaging::SW_SHOWNORMAL,
129            },
130        },
131    };
132
133    unsafe {
134        // 初始化 COM
135        let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
136
137        // 设置 SHELLEXECUTEINFOW 结构体
138        let mut sei: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW::default();
139        sei.cbSize = std::mem::size_of::<SHELLEXECUTEINFOW>() as u32;
140        sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_UNICODE;
141        sei.lpVerb = w!("runas");
142        // 明确调用 cmd.exe 而不是直接调用 .cmd 文件
143        sei.lpFile = PCWSTR::from_raw(w!("cmd.exe").as_ptr());
144        // 如果想让窗口执行完毕后保持打开状态,可以用 /k;如果希望运行完毕后关闭,则用 /c
145        let shell_switch = "/C"; // 或者使用 "/c" 根据需求选择
146
147        // // 对脚本路径加引号(只做一次包装即可)
148        let script_path_quoted = format!("\"{}\"", exe_path);
149        // 对各参数也做一次包装,防止因空格被拆分
150        let args_str = if let Some(args) = args {
151            args.into_iter()
152                .map(|arg| format!("\"{}\"", arg))
153                .collect::<Vec<_>>()
154                .join(" ")
155        } else {
156            String::new()
157        };
158
159        // 构造完整的参数字符串,注意各部分只引号包装一次
160        // 例如:/k "D:\Desktop\test.cmd" "C:\Program Files\7-Zip"
161        let params_str = format!("{} \"{} {}\"", shell_switch, script_path_quoted, args_str);
162
163        // 将参数转换为 UTF-16 并确保其生命周期足够长(这里简单用 leak,生产代码中建议用更安全的方式管理内存)
164        let mut params_w: Vec<u16> = params_str.encode_utf16().collect();
165        params_w.push(0);
166        sei.lpParameters = PCWSTR::from_raw(params_w.leak().as_mut_ptr());
167        sei.nShow = SW_SHOWNORMAL.0;
168
169        ShellExecuteExW(&mut sei)?;
170        if wait {
171            // 如果需要等待进程结束
172            WaitForSingleObject(sei.hProcess, INFINITE);
173        }
174        CoUninitialize();
175        Ok(())
176    }
177}
178
179#[cfg(all(feature = "winapi", target_os = "windows"))]
180pub fn current_is_admin() -> Result<bool, windows::core::Error> {
181    use windows::Win32::{
182        Foundation::HANDLE,
183        Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY},
184        System::Threading::{GetCurrentProcess, OpenProcessToken},
185    };
186    unsafe {
187        let mut token_handle = HANDLE::default();
188        OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle)?;
189        let mut token_info = TOKEN_ELEVATION::default();
190
191        let mut return_len = 0;
192        GetTokenInformation(
193            token_handle,
194            TokenElevation,
195            Some(&mut token_info as *mut _ as *mut std::ffi::c_void),
196            std::mem::size_of::<TOKEN_ELEVATION>() as u32,
197            &mut return_len,
198        )?;
199
200        CloseHandle(token_handle)?;
201
202        return Ok(token_info.TokenIsElevated == 1);
203    }
204}
205/// allow cmd show color and so on...
206#[cfg(all(feature = "winapi", target_os = "windows"))]
207pub fn enable_virtual_terminal() -> Result<(), windows::core::Error> {
208    use windows::Win32::System::Console::{
209        GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE,
210        ENABLE_VIRTUAL_TERMINAL_PROCESSING, STD_OUTPUT_HANDLE,
211    };
212    unsafe {
213        let handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
214        let mut mode = CONSOLE_MODE::default();
215        let _ = GetConsoleMode(handle, &mut mode)?;
216        let _ = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)?;
217        Ok(())
218    }
219}
220
221/// install msi package
222#[cfg(all(feature = "winapi", target_os = "windows"))]
223pub fn msi_install(path: &str, args: Option<&str>) -> Result<(), windows::core::Error> {
224    use windows::{
225        core::PCWSTR, Win32::System::ApplicationInstallationAndServicing::MsiInstallProductW,
226    };
227
228    let msi_path_wide: Vec<u16> = path.encode_utf16().chain([0]).collect();
229    let msi_args = if args.is_some() {
230        let args = args.unwrap();
231        let args: Vec<u16> = args.encode_utf16().chain([0]).collect();
232        args
233    } else {
234        Vec::new()
235    };
236
237    let msi_args = if msi_args.is_empty() {
238        PCWSTR::null()
239    } else {
240        PCWSTR::from_raw(msi_args.as_ptr())
241    };
242
243    let result = unsafe {
244        MsiInstallProductW(
245            PCWSTR(msi_path_wide.as_ptr()),
246            msi_args, // 空命令行参数
247        )
248    };
249    if result != 0 {
250        return Err(windows::core::Error::from_win32());
251    }
252    Ok(())
253}
254
255/// get the installed msi package version
256#[cfg(all(feature = "winapi", target_os = "windows"))]
257pub fn msi_get_version(name: &str) -> Result<String, windows::core::Error> {
258    use windows::{
259        core::{HRESULT, PWSTR},
260        Win32::System::ApplicationInstallationAndServicing::{
261            MsiEnumProductsW, MsiGetProductInfoW, INSTALLPROPERTY_PRODUCTNAME,
262            INSTALLPROPERTY_VERSIONSTRING,
263        },
264    };
265
266    unsafe {
267        let mut prod_code: Vec<u16> = Vec::new();
268        prod_code.resize(0xff, 0);
269        let prod_code = PWSTR::from_raw(prod_code.as_mut_ptr());
270        let mut idx = 0;
271        let mut ret = MsiEnumProductsW(idx, prod_code);
272        loop {
273            if ret != 0 {
274                break;
275            }
276            idx += 1;
277            let mut buf: Vec<u16> = Vec::new();
278            buf.resize(0xff, 0);
279            let prod_name = PWSTR::from_raw(buf.as_mut_ptr());
280            let mut len = 0xff;
281            let name_ret = MsiGetProductInfoW(
282                prod_code,
283                INSTALLPROPERTY_PRODUCTNAME,
284                Some(prod_name),
285                Some(&mut len),
286            );
287            if name_ret != 0 {
288                continue;
289            }
290            let prod_name = prod_name.to_string().unwrap_or_default();
291            if prod_name == name {
292                let mut buf: Vec<u16> = Vec::new();
293                buf.resize(0xff, 0);
294                let buf = PWSTR::from_raw(buf.as_mut_ptr());
295                let mut len = 0xff;
296                let ret = MsiGetProductInfoW(
297                    prod_code,
298                    INSTALLPROPERTY_VERSIONSTRING,
299                    Some(buf),
300                    Some(&mut len),
301                );
302                if ret != 0 {
303                    return Err(windows::core::Error::from_win32());
304                }
305                let version = buf.to_string().unwrap_or_default();
306                return Ok(version);
307            }
308            ret = MsiEnumProductsW(idx, prod_code);
309        }
310        Err(windows::core::Error::from_hresult(HRESULT::from_win32(1)))
311    }
312}
313
314#[test]
315#[cfg(all(feature = "winapi", target_os = "windows"))]
316fn test_get_process_id_by_name() {
317    let ret = get_process_id_by_name("Notepad.exe");
318    println!("{:?}", ret);
319}
320
321#[test]
322#[cfg(all(feature = "winapi", target_os = "windows"))]
323fn test_sc_query() {
324    let ret = sc_query("slmd");
325    println!("{:?}", ret);
326}