hatchery_node/
windows_process.rs1pub const CREATE_NO_WINDOW: u32 = 0x0800_0000;
9pub const DETACHED_PROCESS: u32 = 0x0000_0008;
11pub const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
13
14pub const HEADLESS_SPAWN_FLAGS: u32 = CREATE_NO_WINDOW | DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
16
17pub fn hide_console(command: &mut std::process::Command) {
21 set_creation_flags(command, CREATE_NO_WINDOW);
22}
23
24pub fn hide_console_tokio(command: &mut tokio::process::Command) {
26 #[cfg(windows)]
27 {
28 hide_console(command.as_std_mut());
29 }
30 #[cfg(not(windows))]
31 {
32 let _ = command;
33 }
34}
35
36pub fn set_creation_flags(command: &mut std::process::Command, flags: u32) {
38 #[cfg(windows)]
39 {
40 use std::os::windows::process::CommandExt;
41 command.creation_flags(flags);
43 }
44 #[cfg(not(windows))]
45 {
46 let _ = (command, flags);
47 }
48}
49
50pub fn hide_console_detached(command: &mut std::process::Command) {
52 set_creation_flags(command, HEADLESS_SPAWN_FLAGS);
53}
54
55pub fn free_console() {
57 #[cfg(windows)]
58 {
59 unsafe {
61 let _ = windows_sys::Win32::System::Console::FreeConsole();
62 }
63 }
64}
65
66pub fn ignore_console_close_events() {
70 #[cfg(windows)]
71 {
72 unsafe extern "system" fn handler(ctrl: u32) -> i32 {
73 use windows_sys::Win32::System::Console::{
74 CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT,
75 };
76 match ctrl {
77 CTRL_CLOSE_EVENT | CTRL_LOGOFF_EVENT | CTRL_SHUTDOWN_EVENT => 1, _ => 0,
79 }
80 }
81 unsafe {
82 let _ = windows_sys::Win32::System::Console::SetConsoleCtrlHandler(Some(handler), 1);
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn hide_console_keeps_program_and_args() {
93 let mut c = std::process::Command::new("prog");
94 c.arg("a").arg("b");
95 hide_console(&mut c);
96 assert_eq!(c.get_program(), std::ffi::OsStr::new("prog"));
97 assert_eq!(c.get_args().collect::<Vec<_>>(), ["a", "b"]);
98 }
99
100 #[test]
101 fn create_no_window_constant() {
102 assert_eq!(CREATE_NO_WINDOW, 0x0800_0000);
103 assert_eq!(DETACHED_PROCESS, 0x0000_0008);
104 assert_ne!(HEADLESS_SPAWN_FLAGS & CREATE_NO_WINDOW, 0);
105 }
106}
107
108
109pub fn current_user_identity() -> Result<(String, String), String> {
112 #[cfg(windows)]
113 {
114 windows_current_user_identity()
115 }
116 #[cfg(not(windows))]
117 {
118 Err("current_user_identity is Windows-only".into())
119 }
120}
121
122#[cfg(windows)]
123fn windows_current_user_identity() -> Result<(String, String), String> {
124 use std::ffi::OsString;
125 use std::os::windows::ffi::OsStringExt;
126 use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, LocalFree, HANDLE};
127 use windows_sys::Win32::Security::{
128 GetTokenInformation, LookupAccountSidW, TokenUser, TOKEN_QUERY, TOKEN_USER,
129 };
130 use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
131 use windows_sys::Win32::Security::Authorization::ConvertSidToStringSidW;
132
133 unsafe {
134 let mut token: HANDLE = std::ptr::null_mut();
135 if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
136 return Err(format!("OpenProcessToken failed ({})", GetLastError()));
137 }
138 let mut needed: u32 = 0;
139 GetTokenInformation(token, TokenUser, std::ptr::null_mut(), 0, &mut needed);
140 let mut buf = vec![0u8; needed as usize];
141 if GetTokenInformation(
142 token,
143 TokenUser,
144 buf.as_mut_ptr() as *mut _,
145 needed,
146 &mut needed,
147 ) == 0
148 {
149 let err = GetLastError();
150 CloseHandle(token);
151 return Err(format!("GetTokenInformation TokenUser failed ({err})"));
152 }
153 let user = &*(buf.as_ptr() as *const TOKEN_USER);
154 let sid = user.User.Sid;
155
156 let mut sid_str: *mut u16 = std::ptr::null_mut();
157 if ConvertSidToStringSidW(sid, &mut sid_str) == 0 {
158 let err = GetLastError();
159 CloseHandle(token);
160 return Err(format!("ConvertSidToStringSidW failed ({err})"));
161 }
162 let sid_os = {
163 let mut len = 0usize;
164 while *sid_str.add(len) != 0 {
165 len += 1;
166 }
167 OsString::from_wide(std::slice::from_raw_parts(sid_str, len))
168 };
169 LocalFree(sid_str as *mut _);
170 let sid_string = sid_os.to_string_lossy().into_owned();
171
172 let mut name = vec![0u16; 256];
173 let mut domain = vec![0u16; 256];
174 let mut name_len = name.len() as u32;
175 let mut domain_len = domain.len() as u32;
176 let mut use_type = 0u32;
177 if LookupAccountSidW(
178 std::ptr::null(),
179 sid,
180 name.as_mut_ptr(),
181 &mut name_len,
182 domain.as_mut_ptr(),
183 &mut domain_len,
184 &mut use_type,
185 ) == 0
186 {
187 let err = GetLastError();
188 CloseHandle(token);
189 return Err(format!("LookupAccountSidW failed ({err})"));
190 }
191 CloseHandle(token);
192 let name_s = OsString::from_wide(&name[..name_len as usize])
193 .to_string_lossy()
194 .into_owned();
195 let domain_s = OsString::from_wide(&domain[..domain_len as usize])
196 .to_string_lossy()
197 .into_owned();
198 let logon = if domain_s.is_empty() {
199 name_s
200 } else {
201 format!("{domain_s}\\{name_s}")
202 };
203 Ok((sid_string, logon))
204 }
205}
206
207pub fn relaunch_elevated(args: &[String]) -> Result<i32, String> {
210 #[cfg(windows)]
211 {
212 windows_relaunch_elevated(args)
213 }
214 #[cfg(not(windows))]
215 {
216 let _ = args;
217 Err("relaunch_elevated is Windows-only".into())
218 }
219}
220
221#[cfg(windows)]
222fn windows_relaunch_elevated(args: &[String]) -> Result<i32, String> {
223 use std::ffi::OsStr;
224 use std::os::windows::ffi::OsStrExt;
225 use windows_sys::Win32::Foundation::{CloseHandle, WAIT_OBJECT_0};
226 use windows_sys::Win32::System::Threading::{
227 GetExitCodeProcess, WaitForSingleObject, INFINITE,
228 };
229 use windows_sys::Win32::UI::Shell::{
230 ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW,
231 };
232
233 let exe = std::env::current_exe().map_err(|e| e.to_string())?;
234 let mut params = String::new();
235 for a in args {
236 if !params.is_empty() {
237 params.push(' ');
238 }
239 if a.is_empty() || a.contains(char::is_whitespace) || a.contains('"') {
240 params.push('"');
241 params.push_str(&a.replace('"', "\"\""));
242 params.push('"');
243 } else {
244 params.push_str(a);
245 }
246 }
247 fn wide(s: &OsStr) -> Vec<u16> {
248 s.encode_wide().chain(std::iter::once(0)).collect()
249 }
250 let mut exe_w = wide(exe.as_os_str());
251 let mut params_w = wide(OsStr::new(¶ms));
252 let mut verb_w = wide(OsStr::new("runas"));
253 let mut dir_w = wide(
254 exe.parent()
255 .map(|p| p.as_os_str())
256 .unwrap_or_else(|| OsStr::new(".")),
257 );
258 unsafe {
259 let mut info: SHELLEXECUTEINFOW = std::mem::zeroed();
260 info.cbSize = std::mem::size_of::<SHELLEXECUTEINFOW>() as u32;
261 info.fMask = SEE_MASK_NOCLOSEPROCESS;
262 info.lpVerb = verb_w.as_mut_ptr();
263 info.lpFile = exe_w.as_mut_ptr();
264 info.lpParameters = params_w.as_mut_ptr();
265 info.lpDirectory = dir_w.as_mut_ptr();
266 info.nShow = 0; if ShellExecuteExW(&mut info) == 0 {
268 return Err(
269 "UAC elevation was declined or ShellExecuteExW failed (need Administrator for --system)"
270 .into(),
271 );
272 }
273 if info.hProcess.is_null() {
274 return Ok(0);
275 }
276 WaitForSingleObject(info.hProcess, INFINITE);
277 let mut code: u32 = 0;
278 GetExitCodeProcess(info.hProcess, &mut code);
279 CloseHandle(info.hProcess);
280 Ok(code as i32)
281 }
282}