use anyhow::Result;use std::ffi::OsStr;use std::os::windows::ffi::OsStrExt;use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;use windows_sys::Win32::Foundation::GetLastError;use windows_sys::Win32::Foundation::HLOCAL;use windows_sys::Win32::Foundation::LocalFree;use windows_sys::Win32::Security::Authorization::ConvertSidToStringSidW;use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW;use windows_sys::Win32::Security::CopySid;use windows_sys::Win32::Security::GetLengthSid;use windows_sys::Win32::Security::LookupAccountNameW;use windows_sys::Win32::Security::SID_NAME_USE;use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_ALLOCATE_BUFFER;use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_FROM_SYSTEM;use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_IGNORE_INSERTS;use windows_sys::Win32::System::Diagnostics::Debug::FormatMessageW;pub fn to_wide<S: AsRef<OsStr>>(s: S) -> Vec<u16> { let mut v: Vec<u16> = s.as_ref().encode_wide().collect(); v.push(0); v}/// Quote a single Windows command-line argument following the rules used by/// CommandLineToArgvW/CRT so that spaces, quotes, and backslashes are preserved./// Reference behavior matches Rust std::process::Command on Windows.#[cfg(target_os = "windows")]pub fn quote_windows_arg(arg: &str) -> String { let needs_quotes = arg.is_empty() || arg .chars() .any(|c| matches!(c, ' ' | '\t' | '\n' | '\r' | '"')); if !needs_quotes { return arg.to_string(); } let mut quoted = String::with_capacity(arg.len() + 2); quoted.push('"'); let mut backslashes = 0; for ch in arg.chars() { match ch { '\\' => { backslashes += 1; } '"' => { quoted.push_str(&"\\".repeat(backslashes * 2 + 1)); quoted.push('"'); backslashes = 0; } _ => { if backslashes > 0 { quoted.push_str(&"\\".repeat(backslashes)); backslashes = 0; } quoted.push(ch); } } } if backslashes > 0 { quoted.push_str(&"\\".repeat(backslashes * 2)); } quoted.push('"'); quoted}/// Build a Windows command line for CreateProcess-style APIs.#[cfg(target_os = "windows")]pub fn argv_to_command_line(argv: &[String]) -> String { argv.iter() .map(|arg| quote_windows_arg(arg)) .collect::<Vec<_>>() .join(" ")}// Produce a readable description for a Win32 error code.pub fn format_last_error(err: i32) -> String { unsafe { let mut buf_ptr: *mut u16 = std::ptr::null_mut(); let flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; let len = FormatMessageW( flags, std::ptr::null(), err as u32, 0, // FORMAT_MESSAGE_ALLOCATE_BUFFER expects a pointer to receive the allocated buffer. // Cast &mut *mut u16 to *mut u16 as required by windows-sys. (&mut buf_ptr as *mut *mut u16) as *mut u16, 0, std::ptr::null_mut(), ); if len == 0 || buf_ptr.is_null() { return format!("Win32 error {err}"); } let slice = std::slice::from_raw_parts(buf_ptr, len as usize); let mut s = String::from_utf16_lossy(slice); s = s.trim().to_string(); let _ = LocalFree(buf_ptr as HLOCAL); s }}pub fn string_from_sid_bytes(sid: &[u8]) -> Result<String, String> { unsafe { let mut str_ptr: *mut u16 = std::ptr::null_mut(); let ok = ConvertSidToStringSidW(sid.as_ptr() as *mut std::ffi::c_void, &mut str_ptr); if ok == 0 || str_ptr.is_null() { return Err(format!( "ConvertSidToStringSidW failed: {}", std::io::Error::last_os_error() )); } let mut len = 0; while *str_ptr.add(len) != 0 { len += 1; } let slice = std::slice::from_raw_parts(str_ptr, len); let out = String::from_utf16_lossy(slice); let _ = LocalFree(str_ptr as HLOCAL); Ok(out) }}const SID_ADMINISTRATORS: &str = "S-1-5-32-544";const SID_USERS: &str = "S-1-5-32-545";const SID_AUTHENTICATED_USERS: &str = "S-1-5-11";const SID_EVERYONE: &str = "S-1-1-0";const SID_SYSTEM: &str = "S-1-5-18";pub fn resolve_sid(name: &str) -> Result<Vec<u8>> { if let Some(sid_str) = well_known_sid_str(name) { return sid_bytes_from_string(sid_str); } let name_w = to_wide(OsStr::new(name)); let mut sid_buffer = vec![0u8; 68]; let mut sid_len: u32 = sid_buffer.len() as u32; let mut domain: Vec<u16> = Vec::new(); let mut domain_len: u32 = 0; let mut use_type: SID_NAME_USE = 0; loop { let ok = unsafe { LookupAccountNameW( std::ptr::null(), name_w.as_ptr(), sid_buffer.as_mut_ptr() as *mut std::ffi::c_void, &mut sid_len, domain.as_mut_ptr(), &mut domain_len, &mut use_type, ) }; if ok != 0 { sid_buffer.truncate(sid_len as usize); return Ok(sid_buffer); } let err = unsafe { GetLastError() }; if err == ERROR_INSUFFICIENT_BUFFER { sid_buffer.resize(sid_len as usize, 0); domain.resize(domain_len as usize, 0); continue; } return Err(anyhow::anyhow!( "LookupAccountNameW failed for {name}: {err}" )); }}fn well_known_sid_str(name: &str) -> Option<&'static str> { match name { "Administrators" => Some(SID_ADMINISTRATORS), "Users" => Some(SID_USERS), "Authenticated Users" => Some(SID_AUTHENTICATED_USERS), "Everyone" => Some(SID_EVERYONE), "SYSTEM" => Some(SID_SYSTEM), _ => None, }}fn sid_bytes_from_string(sid_str: &str) -> Result<Vec<u8>> { let sid_w = to_wide(OsStr::new(sid_str)); let mut psid: *mut std::ffi::c_void = std::ptr::null_mut(); if unsafe { ConvertStringSidToSidW(sid_w.as_ptr(), &mut psid) } == 0 { return Err(anyhow::anyhow!( "ConvertStringSidToSidW failed for {sid_str}: {}", unsafe { GetLastError() } )); } let sid_len = unsafe { GetLengthSid(psid) }; if sid_len == 0 { unsafe { LocalFree(psid as _); } return Err(anyhow::anyhow!("GetLengthSid failed for {sid_str}")); } let mut out = vec![0u8; sid_len as usize]; let ok = unsafe { CopySid(sid_len, out.as_mut_ptr() as *mut std::ffi::c_void, psid) }; unsafe { LocalFree(psid as _); } if ok == 0 { return Err(anyhow::anyhow!("CopySid failed for {sid_str}")); } Ok(out)}#[cfg(test)]mod tests { use super::argv_to_command_line; use pretty_assertions::assert_eq; #[test] fn argv_to_command_line_quotes_each_argument_independently() { let argv = vec![ "cmd.exe".to_string(), "/c".to_string(), "\"C:\\Program Files\\PowerShell\\7\\pwsh.exe\" -NoProfile -EncodedCommand abc==" .to_string(), ]; assert_eq!( argv_to_command_line(&argv), "cmd.exe /c \"\\\"C:\\Program Files\\PowerShell\\7\\pwsh.exe\\\" -NoProfile -EncodedCommand abc==\"" ); } #[test] fn argv_to_command_line_quotes_regular_program_args() { let argv = vec![ "pwsh.exe".to_string(), "-Command".to_string(), "Write-Output \"hello world\"".to_string(), ]; assert_eq!( argv_to_command_line(&argv), "pwsh.exe -Command \"Write-Output \\\"hello world\\\"\"" ); }}