Skip to main content

PowerShellSession

Struct PowerShellSession 

Source
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

Source

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?
examples/enc_oneliner.rs (line 11)
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
Hide additional examples
examples/panic_hunt.rs (line 201)
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}
Source

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 - A Variables instance 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();
Source

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");
Source

pub fn deobfuscate_script( &mut self, script: &str, ) -> Result<String, ParserError>

Source

pub fn env_variables(&self) -> HashMap<String, PsValue>

Source

pub fn session_variables(&self) -> HashMap<String, PsValue>

Source

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?
examples/enc_oneliner.rs (line 17)
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
Hide additional examples
examples/panic_hunt.rs (line 201)
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}
Source

pub fn parse_command( &mut self, input: &str, ) -> Result<ScriptResult, ParserError>

Examples found in repository?
examples/panic_hunt.rs (line 220)
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}
More examples
Hide additional examples
examples/enc_oneliner.rs (line 11)
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

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Default for PowerShellSession

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> MaybeSendSync for T

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.