1use std::{
4 env,
5 ffi::{OsString, c_void},
6 mem,
7 os::windows::ffi::OsStringExt,
8 path::{Path, PathBuf},
9 ptr,
10};
11
12use windows::{
13 Win32::{
14 System::{
15 Environment::{
16 CreateEnvironmentBlock, DestroyEnvironmentBlock, ExpandEnvironmentStringsW,
17 },
18 Threading::{
19 CREATE_PROCESS_LOGON_FLAGS, CREATE_UNICODE_ENVIRONMENT, CreateProcessWithTokenW,
20 PROCESS_INFORMATION, STARTF_USESHOWWINDOW, STARTUPINFOW,
21 },
22 },
23 UI::WindowsAndMessaging::SW_SHOWNORMAL,
24 },
25 core::{HSTRING, PCWSTR, PWSTR},
26};
27
28use super::{HandleGuard, token::Token, win32};
29use crate::error::ElevateError;
30
31#[derive(Debug, Clone, PartialEq, Eq, Default)]
33pub enum InteractiveDesktop {
34 #[default]
36 Default,
37
38 Winlogon,
40
41 Screensaver,
43
44 Custom(String),
46}
47
48impl InteractiveDesktop {
49 pub fn custom(station_and_desktop: impl Into<String>) -> Self {
51 Self::Custom(station_and_desktop.into())
52 }
53
54 pub(crate) fn as_station_str(&self) -> &str {
55 match self {
56 Self::Default => "WinSta0\\Default",
57 Self::Winlogon => "WinSta0\\Winlogon",
58 Self::Screensaver => "WinSta0\\Screen-Saver",
59 Self::Custom(station) => station.as_str(),
60 }
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum ExecutionContext {
67 Interactive(InteractiveDesktop),
69
70 Headless,
72}
73
74impl Default for ExecutionContext {
75 fn default() -> Self {
76 Self::Interactive(InteractiveDesktop::default())
77 }
78}
79
80struct EnvBlockGuard(*mut c_void);
82
83impl Drop for EnvBlockGuard {
84 fn drop(&mut self) {
85 if !self.0.is_null() {
86 unsafe {
87 let _ = DestroyEnvironmentBlock(self.0);
88 }
89 }
90 }
91}
92
93#[must_use = "commands do nothing unless spawned"]
95pub struct TokenCommand {
96 token: Token,
97 exe: Option<PathBuf>,
98 args: Option<String>,
99 desktop_str: Option<HSTRING>,
100}
101
102impl TokenCommand {
103 pub fn new(token: Token, desktop: Option<InteractiveDesktop>) -> Self {
105 let desktop_str = desktop.map(|d| HSTRING::from(d.as_station_str()));
106 Self {
107 token,
108 exe: None,
109 args: None,
110 desktop_str,
111 }
112 }
113
114 pub fn current_exe(mut self) -> Result<Self, ElevateError> {
116 self.exe = Some(env::current_exe()?);
117 Ok(self)
118 }
119
120 pub fn exe(mut self, path: impl AsRef<Path>) -> Self {
122 self.exe = Some(expand_env_path(path.as_ref()));
123 self
124 }
125
126 pub fn args(mut self, args: impl Into<String>) -> Self {
128 self.args = Some(args.into());
129 self
130 }
131
132 pub fn spawn(self) -> Result<(), ElevateError> {
134 let exe_hstr = self.exe.as_ref().map(|p| HSTRING::from(p.as_os_str()));
135
136 let cmd = match (&self.args, &self.exe) {
137 (Some(args), _) => args.clone(),
138 (None, Some(exe)) => format!("\"{}\"", exe.display()),
139 (None, None) => return Err(ElevateError::MissingExePath),
140 };
141
142 let mut cmd_utf16: Vec<u16> = cmd.encode_utf16().chain(std::iter::once(0)).collect();
143
144 let mut raw_env_block: *mut c_void = ptr::null_mut();
145 win32!(CreateEnvironmentBlock(
146 &raw mut raw_env_block,
147 self.token.as_raw(),
148 false
149 ))?;
150 let _env_guard = EnvBlockGuard(raw_env_block);
151
152 let cwd = env::current_dir()?;
153 let cwd_hstr = HSTRING::from(cwd.as_os_str());
154
155 let lp_desktop = match &self.desktop_str {
156 Some(d) => PWSTR(d.as_ptr().cast_mut()),
157 None => PWSTR::null(),
158 };
159
160 let si = STARTUPINFOW {
161 cb: mem::size_of::<STARTUPINFOW>() as u32,
162 lpDesktop: lp_desktop,
163 dwFlags: STARTF_USESHOWWINDOW,
164 wShowWindow: SW_SHOWNORMAL.0 as u16,
165 ..Default::default()
166 };
167 let mut pi = PROCESS_INFORMATION::default();
168
169 let app_name = match &exe_hstr {
170 Some(h) => PCWSTR(h.as_ptr()),
171 None => PCWSTR::null(),
172 };
173
174 let spawn_res = win32!(CreateProcessWithTokenW(
175 self.token.as_raw(),
176 CREATE_PROCESS_LOGON_FLAGS(0),
177 app_name,
178 PWSTR(cmd_utf16.as_mut_ptr()),
179 CREATE_UNICODE_ENVIRONMENT,
180 Some(raw_env_block),
181 &cwd_hstr,
182 &raw const si,
183 &raw mut pi,
184 ));
185
186 let _process_guard = HandleGuard(pi.hProcess);
187 let _thread_guard = HandleGuard(pi.hThread);
188
189 spawn_res?;
190 Ok(())
191 }
192}
193
194fn expand_env_path(path: &Path) -> PathBuf {
195 let input = HSTRING::from(path.as_os_str());
196
197 let needed = unsafe { ExpandEnvironmentStringsW(&input, None) };
198 if needed == 0 {
199 return path.to_path_buf();
200 }
201
202 let mut buf: Vec<u16> = vec![0; needed as usize];
203 let written = unsafe { ExpandEnvironmentStringsW(&input, Some(&mut buf)) };
204 if written == 0 || written as usize > buf.len() {
205 return path.to_path_buf();
206 }
207
208 let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
209 PathBuf::from(OsString::from_wide(&buf[..len]))
210}