ntprocesses/process/
build.rs

1use std::path::PathBuf;
2use thiserror::Error;
3use windows::Win32::System::Threading::{
4    CREATE_SUSPENDED, PROCESS_ACCESS_RIGHTS, PROCESS_CREATION_FLAGS,
5};
6
7use super::{Created, Process, ProcessError, Snapshot, NT};
8
9#[derive(Error, Debug)]
10pub enum ProcessBuilderError {
11    #[error("Could not build the Process with these arguments.")]
12    InvalidArgument,
13    #[error(transparent)]
14    ProcessError(#[from] ProcessError),
15}
16
17type Result<X> = std::result::Result<X, ProcessBuilderError>;
18
19/// Marker type for process creation.
20#[derive(Default)]
21pub struct Create;
22/// Marker type for attaching to existing process.
23#[derive(Default)]
24pub struct Attach;
25
26#[derive(Default)]
27pub struct ProcessBuilder<T> {
28    name: Option<String>,
29    process_id: Option<u32>,
30    permissions: Option<PROCESS_ACCESS_RIGHTS>,
31    file_path: Option<PathBuf>,
32    creation_flags: PROCESS_CREATION_FLAGS,
33    exe_args: Option<String>,
34    _marker: std::marker::PhantomData<T>,
35}
36
37impl ProcessBuilder<Attach> {
38    /// Set the permissions to use
39    pub fn permissions(mut self, permissions: PROCESS_ACCESS_RIGHTS) -> Self {
40        self.permissions = Some(permissions);
41        self
42    }
43
44    pub fn process_name(mut self, name: &str) -> Self {
45        self.name = Some(name.to_string());
46        self
47    }
48
49    pub fn process_id(mut self, pid: u32) -> Self {
50        self.process_id = Some(pid);
51        self
52    }
53
54    fn pre_build(&self) -> Result<()> {
55        // we can't build a process that has mutually exclusive arugments.
56        if self.process_id.is_some() && self.name.is_some() {
57            return Err(ProcessBuilderError::InvalidArgument);
58        }
59
60        // we can't build a process that doesn't have permissions for type attach.
61        if self.permissions.is_none() {
62            return Err(ProcessBuilderError::InvalidArgument);
63        }
64
65        Ok(())
66    }
67
68    pub fn build_from_snapshot(self) -> Result<Process<Snapshot>> {
69        self.pre_build()?;
70
71        if let Some(name) = self.name {
72            return Ok(Process::<Snapshot>::from_name(
73                &name,
74                self.permissions.unwrap(),
75            )?);
76        }
77
78        if let Some(pid) = self.process_id {
79            return Ok(Process::<Snapshot>::from_pid(
80                pid,
81                self.permissions.unwrap(),
82            )?);
83        }
84
85        return Err(ProcessBuilderError::InvalidArgument);
86    }
87
88    pub fn build_from_nt(self) -> Result<Process<NT>> {
89        self.pre_build()?;
90
91        if let Some(name) = self.name {
92            return Ok(Process::<NT>::from_name(&name, self.permissions.unwrap())?);
93        }
94
95        if let Some(pid) = self.process_id {
96            return Ok(Process::<NT>::from_pid(pid, self.permissions.unwrap())?);
97        }
98
99        return Err(ProcessBuilderError::InvalidArgument);
100    }
101}
102
103impl ProcessBuilder<Create> {
104    pub fn file_path(mut self, path: PathBuf) -> Self {
105        self.file_path = Some(path);
106        self
107    }
108
109    /// Start the process suspended, shorthand for the flags method.
110    pub fn suspended(mut self) -> Self {
111        self.creation_flags |= CREATE_SUSPENDED;
112        self
113    }
114
115    /// Provide process creation flags, like starting suspended for example.
116    pub fn flags(mut self, flags: PROCESS_CREATION_FLAGS) -> Self {
117        self.creation_flags |= flags;
118        self
119    }
120
121    pub fn args(mut self, args: &str) -> Self {
122        self.exe_args = Some(args.to_string());
123        self
124    }
125
126    pub fn spawn(self) -> Result<Process<Created>> {
127        // ensure that there is at least the file path.
128        if self.file_path.is_none() {
129            return Err(ProcessBuilderError::InvalidArgument);
130        }
131
132        Ok(Process::<Created>::from_path(
133            self.file_path.unwrap(),
134            &self.exe_args.unwrap_or_default(),
135            self.creation_flags,
136        )?)
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    // Note this useful idiom: importing names from outer (for mod tests) scope.
143    use super::*;
144    use pretty_assertions::assert_eq;
145    use windows::Win32::System::Threading::PROCESS_ALL_ACCESS;
146
147    #[test]
148    fn builder_attach_snapshot() -> Result<()> {
149        // get the current process id for the current process.
150        let process_id = Process::<Snapshot>::get_current_process_id();
151
152        ProcessBuilder::<Attach>::default()
153            .permissions(PROCESS_ALL_ACCESS)
154            .process_id(process_id)
155            .build_from_snapshot()?;
156
157        Ok(())
158    }
159
160    #[test]
161    fn builder_attach_nt() -> Result<()> {
162        // get the current process id for the current process.
163        let process_id = Process::<NT>::get_current_process_id();
164
165        ProcessBuilder::<Attach>::default()
166            .permissions(PROCESS_ALL_ACCESS)
167            .process_id(process_id)
168            .build_from_nt()?;
169
170        Ok(())
171    }
172
173    #[test]
174    fn builder_create_process() -> Result<()> {
175        // get the current process id for the current process.
176        let process_id = Process::<Snapshot>::get_current_process_id();
177
178        let target_process = ProcessBuilder::<Attach>::default()
179            .permissions(PROCESS_ALL_ACCESS)
180            .process_id(process_id)
181            .build_from_snapshot()?;
182
183        let process = ProcessBuilder::<Create>::default()
184            .file_path(target_process.get_full_path()?)
185            .suspended()
186            .args("-test")
187            .spawn()?;
188
189        process.kill()?;
190
191        Ok(())
192    }
193
194    #[test]
195    fn builder_attach_invalid_config() {
196        // get the current process id for the current process.
197        let process_id = Process::<Snapshot>::get_current_process_id();
198
199        let should_err = ProcessBuilder::<Attach>::default()
200            .permissions(PROCESS_ALL_ACCESS)
201            .process_id(process_id)
202            .process_name("random.exe")
203            .build_from_snapshot();
204
205        assert_eq!(should_err.is_err(), true)
206    }
207}