pub struct PowerShellSession { /* private fields */ }Expand description
Represents a PowerShell parsing and evaluation session.
This is the main entry point for parsing and evaluating PowerShell scripts. It maintains the session state including variables, tokens, and error information.
§Examples
use ps_parser::PowerShellSession;
// Create a new session
let mut session = PowerShellSession::new();
// Evaluate a simple expression
let result = session.safe_eval("$a = 1 + 2; Write-Output $a").unwrap();
assert_eq!(result, "3");
// Parse and get detailed results
let script_result = session.parse_command("$b = 'Hello World'; $b").unwrap();
println!("Result: {:?}", script_result.result());Implementations§
Source§impl<'a> PowerShellSession
impl<'a> PowerShellSession
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a new PowerShell parsing session with default settings.
The session is initialized with built-in variables like $true,
$false, $null, and special variables like $? for error status
tracking.
§Returns
A new PowerShellSession instance ready for script evaluation.
§Examples
use ps_parser::PowerShellSession;
let mut session = PowerShellSession::new();
let result = session.safe_eval("$true").unwrap();
assert_eq!(result, "True");Examples found in repository?
3fn main() {
4 let Some(path) = std::env::args().nth(1) else {
5 eprintln!("usage: cargo run --example enc_oneliner -- <script.ps1>");
6 std::process::exit(1);
7 };
8 let script = std::fs::read_to_string(path).unwrap();
9
10 println!("=== parse_command ===");
11 match PowerShellSession::new().parse_command(script.trim()) {
12 Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
13 Err(e) => println!("error: {e:?}"),
14 }
15
16 println!("=== parse_script ===");
17 match PowerShellSession::new().parse_script(script.trim()) {
18 Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
19 Err(e) => println!("error: {e:?}"),
20 }
21}More examples
160fn depth_matrix() {
161 let patterns: &[(&str, &str, &str)] = &[
162 ("paren", "(", ")"),
163 ("block", "& {", "}"),
164 ("sub", "$(", ")"),
165 ("dq", "\"$(", ")\""),
166 ("idx", "$a[", "]"),
167 ("if", "if(1){", "}"),
168 ("ifelse", "if(0){1}else{", "}"),
169 ("arr", "@(", ")"),
170 ("hash", "@{a=", "}"),
171 ("neg", "-(", ")"),
172 ("method", "'a'.Replace((", "),'b')"),
173 ("plus", "+(", ")"),
174 ("not", "!(", ")"),
175 ("cast", "[int](", ")"),
176 ("sb", "{", "}"),
177 ("cmd", "echo (", ")"),
178 ];
179 let only = std::env::args().nth(2);
180 let depths: Vec<usize> = match std::env::args().nth(3) {
181 Some(d) => d.split(',').map(|d| d.parse().unwrap()).collect(),
182 None => vec![5, 10, 20, 40, 60, 80, 100, 300, 1500, 5000],
183 };
184 for n in depths {
185 println!("--- depth {n}");
186 let mut cases: Vec<(String, String)> = patterns
187 .iter()
188 .filter(|(name, _, _)| only.as_deref().is_none_or(|o| o == "all" || o == *name))
189 .map(|(name, o, c)| (name.to_string(), format!("$a=1;{}1{}", o.repeat(n), c.repeat(n))))
190 .collect();
191 cases.push((
192 "func".into(),
193 format!("function f($n) {{ if ($n -gt 0) {{ f ($n - 1) }} else {{ 'done' }} }}; f {n}"),
194 ));
195 for (name, script) in cases {
196 let (tx, rx) = std::sync::mpsc::channel();
197 std::thread::Builder::new()
198 .stack_size(1 << 20)
199 .spawn(move || {
200 let t = std::time::Instant::now();
201 let r = PowerShellSession::new().parse_script(&script).map(|r| r.result());
202 let _ = tx.send(format!("{:?} [{} ms]", r, t.elapsed().as_millis()));
203 })
204 .unwrap();
205 let res = rx
206 .recv_timeout(std::time::Duration::from_secs(20))
207 .unwrap_or_else(|_| "TIMEOUT".into());
208 let short: String = res.chars().take(70).collect();
209 println!("{name:>7}: {short}");
210 }
211 }
212 std::process::exit(0);
213}
214
215fn run(script: &str) -> Option<String> {
216 LAST.with(|l| l.borrow_mut().clear());
217 let res = panic::catch_unwind(|| {
218 let mut ps = PowerShellSession::new();
219 let _ = ps.parse_script(script);
220 let _ = PowerShellSession::new().parse_command(script);
221 });
222 // the hook fires even when the parser catches the panic internally
223 let last = LAST.with(|l| l.borrow().clone());
224 (res.is_err() || !last.is_empty()).then_some(last)
225}
226
227fn main() {
228 panic::set_hook(Box::new(|info| {
229 let loc = info
230 .location()
231 .map(|l| format!("{}:{}", l.file(), l.line()))
232 .unwrap_or_default();
233 let msg = info
234 .payload()
235 .downcast_ref::<&str>()
236 .map(|s| s.to_string())
237 .or_else(|| info.payload().downcast_ref::<String>().cloned())
238 .unwrap_or_default();
239 LAST.with(|l| *l.borrow_mut() = format!("{loc} | {msg}"));
240 }));
241
242 if std::env::args().nth(1).as_deref() == Some("--depth") {
243 depth_matrix();
244 return;
245 }
246
247 if let Some(script) = std::env::args().nth(1) {
248 let (tx, rx) = std::sync::mpsc::channel();
249 std::thread::spawn(move || {
250 let t = std::time::Instant::now();
251 let r = PowerShellSession::new().parse_script(&script).map(|r| r.result());
252 let _ = tx.send(format!("[{} ms] {:?}", t.elapsed().as_millis(), r));
253 });
254 let res = rx
255 .recv_timeout(std::time::Duration::from_secs(15))
256 .unwrap_or_else(|_| "TIMEOUT".into());
257 println!("{}", res.chars().take(200).collect::<String>());
258 std::process::exit(0);
259 }
260
261 // location -> (count, shortest input)
262 let mut found: BTreeMap<String, (usize, String)> = BTreeMap::new();
263 let mut record = |input: &str, panic: String| {
264 let key = panic.split(" | ").next().unwrap_or_default().to_string();
265 let e = found.entry(key).or_insert((0, input.to_string()));
266 e.0 += 1;
267 if input.len() < e.1.len() {
268 e.1 = input.to_string();
269 }
270 let _ = panic;
271 };
272
273 let mut runs = 0;
274 for s in TARGETED {
275 runs += 1;
276 if let Some(p) = run(s) {
277 println!("TARGETED PANIC: {s:?}\n {p}");
278 record(s, p);
279 }
280 }
281
282 let corpus = [
283 "test_scripts/comprehensive/input.ps1",
284 "test_scripts/parser_focused/input.ps1",
285 "test_scripts/stress/input.ps1",
286 "test_scripts/enc_oneliner.ps1",
287 ];
288 for path in corpus {
289 // local samples (e.g. enc_oneliner.ps1) are not in the repo
290 let Ok(text) = std::fs::read_to_string(path) else {
291 continue;
292 };
293 let lines: Vec<&str> = text.lines().collect();
294
295 // every single line on its own, and every line truncated at each char boundary
296 for line in &lines {
297 let idxs: Vec<usize> = line.char_indices().map(|(i, _)| i).chain([line.len()]).collect();
298 for &i in &idxs {
299 runs += 1;
300 let s = &line[..i];
301 if let Some(p) = run(s) {
302 record(s, p);
303 }
304 }
305 }
306
307 // whole file truncated at every line boundary
308 for n in 0..=lines.len() {
309 runs += 1;
310 let s = lines[..n].join("\n");
311 if let Some(p) = run(&s) {
312 record(&s, p);
313 }
314 }
315 }
316
317 println!("\n=== {runs} runs, {} distinct panic locations ===", found.len());
318 for (loc, (count, input)) in &found {
319 let short: String = input.chars().take(160).collect();
320 println!("{loc} (x{count})\n shortest: {short:?}");
321 }
322}Sourcepub fn with_variables(self, variables: Variables) -> Self
pub fn with_variables(self, variables: Variables) -> Self
Creates a new PowerShell session with the provided variables.
This constructor allows you to initialize the session with a custom set of variables, such as environment variables or variables loaded from configuration files.
§Arguments
variables- AVariablesinstance containing the initial variable set.
§Returns
A new PowerShellSession instance with the provided variables.
§Examples
use ps_parser::{PowerShellSession, Variables};
let env_vars = Variables::env();
let mut session = PowerShellSession::new().with_variables(env_vars);
let username = session.safe_eval("$env:USERNAME").unwrap();Sourcepub fn safe_eval(&mut self, script: &str) -> Result<String, ParserError>
pub fn safe_eval(&mut self, script: &str) -> Result<String, ParserError>
Safely evaluates a PowerShell script and returns the output as a string.
This method parses and evaluates the provided PowerShell script, handling errors gracefully and returning the result as a formatted string. It’s the recommended method for simple script evaluation.
§Arguments
script- A string slice containing the PowerShell script to evaluate.
§Returns
Result<String, ParserError>- The output of the script evaluation, or an error if parsing/evaluation fails.
§Examples
use ps_parser::PowerShellSession;
let mut session = PowerShellSession::new();
// Simple arithmetic
let result = session.safe_eval("1 + 2 * 3").unwrap();
assert_eq!(result, "7");
// Variable assignment and retrieval
let result = session.safe_eval("$name = 'World'; \"Hello $name\"").unwrap();
assert_eq!(result, "Hello World");pub fn deobfuscate_script( &mut self, script: &str, ) -> Result<String, ParserError>
pub fn env_variables(&self) -> HashMap<String, PsValue>
pub fn session_variables(&self) -> HashMap<String, PsValue>
Sourcepub fn parse_script(&mut self, input: &str) -> Result<ScriptResult, ParserError>
pub fn parse_script(&mut self, input: &str) -> Result<ScriptResult, ParserError>
Parses and evaluates a PowerShell script, returning detailed results.
This method provides comprehensive information about the parsing and evaluation process, including the final result, generated output, any errors encountered, and the tokenized representation of the script. It’s particularly useful for debugging and deobfuscation.
§Arguments
input- A string slice containing the PowerShell script to parse and evaluate.
§Returns
Result<ScriptResult, ParserError>- A detailed result containing the evaluation outcome, output, errors, and tokens, or a parsing error if the script is malformed.
§Examples
use ps_parser::PowerShellSession;
let mut session = PowerShellSession::new();
let script_result = session.parse_command("$a = 42; Write-Output $a").unwrap();
println!("Final result: {:?}", script_result.result());
println!("Generated output: {:?}", script_result.output());
println!("Parsing errors: {:?}", script_result.errors());
println!("Deobfuscated code: {:?}", script_result.deobfuscated());Examples found in repository?
3fn main() {
4 let Some(path) = std::env::args().nth(1) else {
5 eprintln!("usage: cargo run --example enc_oneliner -- <script.ps1>");
6 std::process::exit(1);
7 };
8 let script = std::fs::read_to_string(path).unwrap();
9
10 println!("=== parse_command ===");
11 match PowerShellSession::new().parse_command(script.trim()) {
12 Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
13 Err(e) => println!("error: {e:?}"),
14 }
15
16 println!("=== parse_script ===");
17 match PowerShellSession::new().parse_script(script.trim()) {
18 Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
19 Err(e) => println!("error: {e:?}"),
20 }
21}More examples
160fn depth_matrix() {
161 let patterns: &[(&str, &str, &str)] = &[
162 ("paren", "(", ")"),
163 ("block", "& {", "}"),
164 ("sub", "$(", ")"),
165 ("dq", "\"$(", ")\""),
166 ("idx", "$a[", "]"),
167 ("if", "if(1){", "}"),
168 ("ifelse", "if(0){1}else{", "}"),
169 ("arr", "@(", ")"),
170 ("hash", "@{a=", "}"),
171 ("neg", "-(", ")"),
172 ("method", "'a'.Replace((", "),'b')"),
173 ("plus", "+(", ")"),
174 ("not", "!(", ")"),
175 ("cast", "[int](", ")"),
176 ("sb", "{", "}"),
177 ("cmd", "echo (", ")"),
178 ];
179 let only = std::env::args().nth(2);
180 let depths: Vec<usize> = match std::env::args().nth(3) {
181 Some(d) => d.split(',').map(|d| d.parse().unwrap()).collect(),
182 None => vec![5, 10, 20, 40, 60, 80, 100, 300, 1500, 5000],
183 };
184 for n in depths {
185 println!("--- depth {n}");
186 let mut cases: Vec<(String, String)> = patterns
187 .iter()
188 .filter(|(name, _, _)| only.as_deref().is_none_or(|o| o == "all" || o == *name))
189 .map(|(name, o, c)| (name.to_string(), format!("$a=1;{}1{}", o.repeat(n), c.repeat(n))))
190 .collect();
191 cases.push((
192 "func".into(),
193 format!("function f($n) {{ if ($n -gt 0) {{ f ($n - 1) }} else {{ 'done' }} }}; f {n}"),
194 ));
195 for (name, script) in cases {
196 let (tx, rx) = std::sync::mpsc::channel();
197 std::thread::Builder::new()
198 .stack_size(1 << 20)
199 .spawn(move || {
200 let t = std::time::Instant::now();
201 let r = PowerShellSession::new().parse_script(&script).map(|r| r.result());
202 let _ = tx.send(format!("{:?} [{} ms]", r, t.elapsed().as_millis()));
203 })
204 .unwrap();
205 let res = rx
206 .recv_timeout(std::time::Duration::from_secs(20))
207 .unwrap_or_else(|_| "TIMEOUT".into());
208 let short: String = res.chars().take(70).collect();
209 println!("{name:>7}: {short}");
210 }
211 }
212 std::process::exit(0);
213}
214
215fn run(script: &str) -> Option<String> {
216 LAST.with(|l| l.borrow_mut().clear());
217 let res = panic::catch_unwind(|| {
218 let mut ps = PowerShellSession::new();
219 let _ = ps.parse_script(script);
220 let _ = PowerShellSession::new().parse_command(script);
221 });
222 // the hook fires even when the parser catches the panic internally
223 let last = LAST.with(|l| l.borrow().clone());
224 (res.is_err() || !last.is_empty()).then_some(last)
225}
226
227fn main() {
228 panic::set_hook(Box::new(|info| {
229 let loc = info
230 .location()
231 .map(|l| format!("{}:{}", l.file(), l.line()))
232 .unwrap_or_default();
233 let msg = info
234 .payload()
235 .downcast_ref::<&str>()
236 .map(|s| s.to_string())
237 .or_else(|| info.payload().downcast_ref::<String>().cloned())
238 .unwrap_or_default();
239 LAST.with(|l| *l.borrow_mut() = format!("{loc} | {msg}"));
240 }));
241
242 if std::env::args().nth(1).as_deref() == Some("--depth") {
243 depth_matrix();
244 return;
245 }
246
247 if let Some(script) = std::env::args().nth(1) {
248 let (tx, rx) = std::sync::mpsc::channel();
249 std::thread::spawn(move || {
250 let t = std::time::Instant::now();
251 let r = PowerShellSession::new().parse_script(&script).map(|r| r.result());
252 let _ = tx.send(format!("[{} ms] {:?}", t.elapsed().as_millis(), r));
253 });
254 let res = rx
255 .recv_timeout(std::time::Duration::from_secs(15))
256 .unwrap_or_else(|_| "TIMEOUT".into());
257 println!("{}", res.chars().take(200).collect::<String>());
258 std::process::exit(0);
259 }
260
261 // location -> (count, shortest input)
262 let mut found: BTreeMap<String, (usize, String)> = BTreeMap::new();
263 let mut record = |input: &str, panic: String| {
264 let key = panic.split(" | ").next().unwrap_or_default().to_string();
265 let e = found.entry(key).or_insert((0, input.to_string()));
266 e.0 += 1;
267 if input.len() < e.1.len() {
268 e.1 = input.to_string();
269 }
270 let _ = panic;
271 };
272
273 let mut runs = 0;
274 for s in TARGETED {
275 runs += 1;
276 if let Some(p) = run(s) {
277 println!("TARGETED PANIC: {s:?}\n {p}");
278 record(s, p);
279 }
280 }
281
282 let corpus = [
283 "test_scripts/comprehensive/input.ps1",
284 "test_scripts/parser_focused/input.ps1",
285 "test_scripts/stress/input.ps1",
286 "test_scripts/enc_oneliner.ps1",
287 ];
288 for path in corpus {
289 // local samples (e.g. enc_oneliner.ps1) are not in the repo
290 let Ok(text) = std::fs::read_to_string(path) else {
291 continue;
292 };
293 let lines: Vec<&str> = text.lines().collect();
294
295 // every single line on its own, and every line truncated at each char boundary
296 for line in &lines {
297 let idxs: Vec<usize> = line.char_indices().map(|(i, _)| i).chain([line.len()]).collect();
298 for &i in &idxs {
299 runs += 1;
300 let s = &line[..i];
301 if let Some(p) = run(s) {
302 record(s, p);
303 }
304 }
305 }
306
307 // whole file truncated at every line boundary
308 for n in 0..=lines.len() {
309 runs += 1;
310 let s = lines[..n].join("\n");
311 if let Some(p) = run(&s) {
312 record(&s, p);
313 }
314 }
315 }
316
317 println!("\n=== {runs} runs, {} distinct panic locations ===", found.len());
318 for (loc, (count, input)) in &found {
319 let short: String = input.chars().take(160).collect();
320 println!("{loc} (x{count})\n shortest: {short:?}");
321 }
322}Sourcepub fn parse_command(
&mut self,
input: &str,
) -> Result<ScriptResult, ParserError>
pub fn parse_command( &mut self, input: &str, ) -> Result<ScriptResult, ParserError>
Examples found in repository?
More examples
3fn main() {
4 let Some(path) = std::env::args().nth(1) else {
5 eprintln!("usage: cargo run --example enc_oneliner -- <script.ps1>");
6 std::process::exit(1);
7 };
8 let script = std::fs::read_to_string(path).unwrap();
9
10 println!("=== parse_command ===");
11 match PowerShellSession::new().parse_command(script.trim()) {
12 Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
13 Err(e) => println!("error: {e:?}"),
14 }
15
16 println!("=== parse_script ===");
17 match PowerShellSession::new().parse_script(script.trim()) {
18 Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
19 Err(e) => println!("error: {e:?}"),
20 }
21}Trait Implementations§
Source§impl Clone for PowerShellSession
impl Clone for PowerShellSession
Auto Trait Implementations§
impl !RefUnwindSafe for PowerShellSession
impl !UnwindSafe for PowerShellSession
impl Freeze for PowerShellSession
impl Send for PowerShellSession
impl Sync for PowerShellSession
impl Unpin for PowerShellSession
impl UnsafeUnpin for PowerShellSession
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more