ps_parser/
lib.rs

1//! # ps-parser
2//!
3//! A fast and flexible PowerShell parser written in Rust.
4//!
5//! ## Overview
6//!
7//! `ps-parser` provides parsing, evaluation, and manipulation of PowerShell
8//! scripts. It supports variables, arrays, hash tables, script blocks,
9//! arithmetic, logical operations, and more.
10//!
11//! ## Features
12//!
13//! - Parse PowerShell scripts using pest grammar
14//! - Evaluate expressions, variables, arrays, hash tables, and script blocks
15//! - Environment and INI variable loading
16//! - Deobfuscation and error reporting
17//! - Extensible for custom PowerShell types
18//!
19//! ## Usage
20//!
21//! ```rust
22//! use ps_parser::PowerShellSession;
23//!
24//! let mut session = PowerShellSession::new();
25//! let output = session.safe_eval(r#"$a = 42; Write-Output $a"#).unwrap();
26//! println!("{}", output); // prints: 42
27//! ```
28
29mod parser;
30pub(crate) use parser::NEWLINE;
31/// Represents a PowerShell parsing and evaluation session.
32///
33/// This is the main entry point for parsing and evaluating PowerShell scripts.
34/// It maintains the session state including variables, tokens, and error
35/// information.
36///
37/// # Examples
38///
39/// ```rust
40/// use ps_parser::PowerShellSession;
41///
42/// // Create a new session
43/// let mut session = PowerShellSession::new();
44///
45/// // Evaluate a simple expression
46/// let result = session.safe_eval("$a = 1 + 2; Write-Output $a").unwrap();
47/// assert_eq!(result, "3");
48///
49/// // Parse and get detailed results
50/// let script_result = session.parse_input("$b = 'Hello World'; $b").unwrap();
51/// println!("Result: {:?}", script_result.result());
52/// ```
53pub use parser::PowerShellSession;
54/// Represents a PowerShell value that can be stored and manipulated.
55///
56/// This enum covers all the basic PowerShell data types including primitives,
57/// collections, and complex objects like script blocks and hash tables.
58///
59/// # Examples
60///
61/// ```rust
62/// use ps_parser::PsValue;
63///
64/// // Different value types  
65/// let int_val = PsValue::Int(42);
66/// let string_val = PsValue::String("Hello".into());
67/// let bool_val = PsValue::Bool(true);
68/// ```
69pub use parser::PsValue;
70/// Contains the complete result of parsing and evaluating a PowerShell script.
71///
72/// This structure holds the final result value, any output generated,
73/// parsing errors encountered, and the tokenized representation of the script.
74/// It's particularly useful for debugging and deobfuscation purposes.
75///
76/// # Examples
77///
78/// ```rust
79/// use ps_parser::PowerShellSession;
80///
81/// let mut session = PowerShellSession::new();
82/// let script_result = session.parse_input("$a = 42; $a").unwrap();
83///
84/// // Access different parts of the result
85/// println!("Final value: {:?}", script_result.result());
86/// println!("Output: {:?}", script_result.output());
87/// println!("Errors: {:?}", script_result.errors());
88/// ```
89pub use parser::ScriptResult;
90/// Represents a parsed token from a PowerShell script.
91///
92/// Tokens are the building blocks of parsed PowerShell code and are used
93/// for syntax analysis, deobfuscation, and code transformation.
94///
95/// Right now 4 token types are supported:
96/// - **String**: Representation of single quoted PowerShell strings (e.g.,
97///   `'hello world'`)
98/// - **StringExpandable**: Representation of double quoted PowerShell strings
99///   with variable expansion (e.g., `"Hello $name"`)
100/// - **Expression**: Parsed PowerShell expressions with their evaluated results
101///   (e.g., `$a + $b`)
102/// - **Function**: PowerShell function definitions and calls
103///
104/// Each token type stores both the original source code and its
105/// processed/evaluated form, making it useful for deobfuscation and analysis
106/// purposes.
107///
108/// # Examples
109///
110/// ```rust
111/// use ps_parser::PowerShellSession;
112///
113/// let mut session = PowerShellSession::new();
114/// let script_result = session.parse_input("$var = 123").unwrap();
115///
116/// // Inspect the tokens
117/// for token in script_result.tokens().all() {
118///     println!("Token: {:?}", token);
119/// }
120/// ```
121pub use parser::Token;
122/// Manages PowerShell variables across different scopes.
123///
124/// This structure handles variable storage, retrieval, and scope management
125/// for PowerShell scripts. It supports loading variables from environment
126/// variables, INI files, and manual assignment.
127///
128/// # Examples
129///
130/// ```rust
131/// use ps_parser::{Variables, PowerShellSession};
132/// use std::path::Path;
133///
134/// // Load environment variables
135/// let env_vars = Variables::env();
136/// let mut session = PowerShellSession::new().with_variables(env_vars);
137///
138/// // Load from INI string
139/// let ini_vars = Variables::from_ini_string("[global]\nname = John Doe\n[local]\nlocal_var = \"local_value\"").unwrap();
140/// let mut session2 = PowerShellSession::new().with_variables(ini_vars);
141///
142/// // Create empty and add manually
143/// let mut vars = Variables::new();
144/// // ... add variables manually
145/// ```
146pub use parser::Variables;
147pub use parser::{CommandToken, ExpressionToken, MethodToken, StringExpandableToken};
148
149#[cfg(test)]
150mod tests {
151    use std::collections::HashMap;
152
153    use super::*;
154    use crate::{ExpressionToken, StringExpandableToken};
155
156    #[test]
157    fn deobfuscation() {
158        // assign variable and print it to screen
159        let mut p = PowerShellSession::new();
160        let input = r#" $script:var = [char]([int]("9e4e" -replace "e")+3); [int]'a';$var"#;
161        let script_res = p.parse_input(input).unwrap();
162        assert_eq!(script_res.result(), 'a'.into());
163        assert_eq!(
164            script_res.deobfuscated(),
165            vec!["$script:var = 'a'", "[int]'a'", "'a'"].join(NEWLINE)
166        );
167        assert_eq!(script_res.errors().len(), 1);
168        assert_eq!(
169            script_res.errors()[0].to_string(),
170            "ValError: Failed to convert value \"a\" to type Int"
171        );
172
173        // the same but do it in two parts
174        let mut p = PowerShellSession::new();
175        let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3) "#;
176        let script_res = p.parse_input(input).unwrap();
177
178        assert_eq!(script_res.errors().len(), 0);
179
180        let script_res = p.parse_input(" [int]'a';$var ").unwrap();
181        assert_eq!(
182            script_res.deobfuscated(),
183            vec!["[int]'a'", "'a'"].join(NEWLINE)
184        );
185        assert_eq!(script_res.output(), vec!["a"].join(NEWLINE));
186        assert_eq!(script_res.errors().len(), 1);
187        assert_eq!(
188            script_res.errors()[0].to_string(),
189            "ValError: Failed to convert value \"a\" to type Int"
190        );
191    }
192
193    #[test]
194    fn deobfuscation_non_existing_value() {
195        // assign not existing value, without forcing evaluation
196        let mut p = PowerShellSession::new();
197        let input = r#" $local:var = $env:programfiles;[int]'a';$var"#;
198        let script_res = p.parse_input(input).unwrap();
199        assert_eq!(script_res.result(), PsValue::Null);
200        assert_eq!(
201            script_res.deobfuscated(),
202            vec!["$local:var = $env:programfiles", "[int]'a'", "$var"].join(NEWLINE)
203        );
204        assert_eq!(script_res.errors().len(), 3);
205        assert_eq!(
206            script_res.errors()[0].to_string(),
207            "VariableError: Variable \"programfiles\" is not defined"
208        );
209        assert_eq!(
210            script_res.errors()[1].to_string(),
211            "ValError: Failed to convert value \"a\" to type Int"
212        );
213        assert_eq!(
214            script_res.errors()[2].to_string(),
215            "VariableError: Variable \"var\" is not defined"
216        );
217
218        // assign not existing value, forcing evaluation
219        let mut p = PowerShellSession::new().with_variables(Variables::force_eval());
220        let input = r#" $local:var = $env:programfiles;[int]'a';$script:var"#;
221        let script_res = p.parse_input(input).unwrap();
222        assert_eq!(script_res.result(), PsValue::Null);
223        assert_eq!(
224            script_res.deobfuscated(),
225            vec!["$local:var = $null", "[int]'a'"].join(NEWLINE)
226        );
227        assert_eq!(script_res.errors().len(), 1);
228    }
229
230    #[test]
231    fn deobfuscation_env_value() {
232        // assign not existing value, without forcing evaluation
233        let mut p = PowerShellSession::new().with_variables(Variables::env());
234        let input = r#" $local:var = $env:programfiles;$var"#;
235        let script_res = p.parse_input(input).unwrap();
236        assert_eq!(
237            script_res.result(),
238            PsValue::String(std::env::var("PROGRAMFILES").unwrap())
239        );
240        let program_files = std::env::var("PROGRAMFILES").unwrap();
241        assert_eq!(
242            script_res.deobfuscated(),
243            vec![
244                format!("$local:var = \"{}\"", program_files),
245                format!("\"{}\"", program_files)
246            ]
247            .join(NEWLINE)
248        );
249        assert_eq!(script_res.errors().len(), 0);
250    }
251
252    #[test]
253    fn deobfuscation_from_base_64() {
254        let mut p = PowerShellSession::new();
255        let input = r#" $encoded = [syStem.texT.EncoDInG]::unIcoDe.geTstRiNg([char]97);$encoded"#;
256        let script_res = p.parse_input(input).unwrap();
257        assert_eq!(script_res.result(), String::from("\u{FFFD}").into());
258
259        let input = r#" [syStem.texT.EncoDInG]::unIcoDe.geTstRiNg([SYSTem.cOnVERT]::froMbasE64striNg("ZABlAGMAbwBkAGUAZAA="))"#;
260        let script_res = p.parse_input(input).unwrap();
261        assert_eq!(script_res.result(), String::from("decoded").into());
262    }
263
264    #[test]
265    fn hash_table() {
266        // assign not existing value, without forcing evaluation
267        let mut p = PowerShellSession::new().with_variables(Variables::env().values_persist());
268        let input = r#" 
269$nestedData = @{
270    Users = @(
271        @{ Name = "Alice"; Age = 30; Skills = @("PowerShell", "Python") }
272        @{ Name = "Bob"; Age = 25; Skills = @("Java", "C#") }
273    )
274    Settings = @{
275        Theme = "Dark"
276        Language = "en-US"
277    }
278}
279"$nestedData"
280        "#;
281        let script_res = p.parse_input(input).unwrap();
282        assert_eq!(
283            script_res.result(),
284            PsValue::String("System.Collections.Hashtable".to_string())
285        );
286
287        assert_eq!(
288            p.parse_input("$nesteddata.settings").unwrap().result(),
289            PsValue::HashTable(HashMap::from([
290                ("language".to_string(), PsValue::String("en-US".to_string())),
291                ("theme".to_string(), PsValue::String("Dark".to_string())),
292            ]))
293        );
294
295        assert_eq!(
296            p.safe_eval("$nesteddata.settings.theme").unwrap(),
297            "Dark".to_string()
298        );
299
300        assert_eq!(
301            p.parse_input("$nesteddata.users[0]").unwrap().result(),
302            PsValue::HashTable(HashMap::from([
303                (
304                    "skills".to_string(),
305                    PsValue::Array(vec![
306                        PsValue::String("PowerShell".to_string()),
307                        PsValue::String("Python".to_string().into())
308                    ])
309                ),
310                ("name".to_string(), PsValue::String("Alice".to_string())),
311                ("age".to_string(), PsValue::Int(30)),
312            ]))
313        );
314
315        assert_eq!(
316            p.safe_eval("$nesteddata.users[0]['name']").unwrap(),
317            "Alice".to_string()
318        );
319
320        assert_eq!(
321            p.safe_eval("$nesteddata.users[0].NAME").unwrap(),
322            "Alice".to_string()
323        );
324
325        let input = r#" $a=@{val = 4};$a.val"#;
326        let script_res = p.parse_input(input).unwrap();
327        assert_eq!(script_res.result(), PsValue::Int(4));
328        assert_eq!(
329            script_res.deobfuscated(),
330            vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
331        );
332    }
333
334    #[test]
335    fn test_simple_arithmetic() {
336        let input = r#"
337Write-Host "=== Test 3: Arithmetic Operations ===" -ForegroundColor Green
338$a = 10
339$b = 5
340Write-Output "Addition: $(($a + $b))"
341Write-Output "Subtraction: $(($a - $b))"
342Write-Output "Multiplication: $(($a * $b))"
343Write-Output "Division: $(($a / $b))"
344Write-Output "Modulo: $(($a % $b))"
345"#;
346
347        let script_result = PowerShellSession::new().parse_input(input).unwrap();
348
349        assert_eq!(script_result.result(), PsValue::String("Modulo: 0".into()));
350        assert_eq!(
351            script_result.output(),
352            vec![
353                r#"=== Test 3: Arithmetic Operations ==="#,
354                r#"Addition: 15"#,
355                r#"Subtraction: 5"#,
356                r#"Multiplication: 50"#,
357                r#"Division: 2"#,
358                r#"Modulo: 0"#
359            ]
360            .join(NEWLINE)
361        );
362        assert_eq!(script_result.errors().len(), 0);
363        assert_eq!(script_result.tokens().expandable_strings().len(), 6);
364        assert_eq!(
365            script_result.tokens().expandable_strings()[1],
366            StringExpandableToken::new(
367                "\"Addition: $(($a + $b))\"".to_string(),
368                "Addition: 15".to_string()
369            )
370        );
371        assert_eq!(script_result.tokens().expressions().len(), 12);
372        assert_eq!(
373            script_result.tokens().expressions()[2],
374            ExpressionToken::new("$a + $b".to_string(), PsValue::Int(15))
375        );
376    }
377
378    #[test]
379    fn test_scripts() {
380        use std::fs;
381        let Ok(entries) = fs::read_dir("test_scripts") else {
382            panic!("Failed to read 'test_scripts' directory");
383        };
384        for entry in entries {
385            let dir_entry = entry.unwrap();
386            if std::fs::FileType::is_dir(&dir_entry.file_type().unwrap()) {
387                // If it's a directory, we can read the files inside it
388                let input_script = dir_entry.path().join("input.ps1");
389                let expected_deobfuscated_script = dir_entry.path().join("deobfuscated.txt");
390                let expected_output_script = dir_entry.path().join("output.txt");
391
392                let Ok(input) = fs::read_to_string(&input_script) else {
393                    panic!("Failed to read test file: {}", input_script.display());
394                };
395
396                let Ok(expected_deobfuscated) = fs::read_to_string(&expected_deobfuscated_script)
397                else {
398                    panic!(
399                        "Failed to read test file: {}",
400                        expected_deobfuscated_script.display()
401                    );
402                };
403
404                let Ok(expected_output) = fs::read_to_string(&expected_output_script) else {
405                    panic!(
406                        "Failed to read test file: {}",
407                        expected_output_script.display()
408                    );
409                };
410
411                let script_result = PowerShellSession::new()
412                    .with_variables(Variables::env())
413                    .parse_input(&input)
414                    .unwrap();
415
416                let expected_deobfuscated_vec = expected_deobfuscated
417                    .lines()
418                    .map(|s| s.trim_end())
419                    .collect::<Vec<&str>>();
420
421                let current_deobfuscated = script_result.deobfuscated();
422                let current_output = script_result.output();
423
424                let expected_output_vec = expected_output
425                    .lines()
426                    .map(|s| s.trim_end())
427                    .collect::<Vec<&str>>();
428
429                //save_files(&dir_entry, &current_deobfuscated, &current_output);
430                let current_deobfuscated_vec = current_deobfuscated
431                    .lines()
432                    .map(|s| s.trim_end())
433                    .collect::<Vec<&str>>();
434
435                let current_output_vec = current_output
436                    .lines()
437                    .map(|s| s.trim_end())
438                    .collect::<Vec<&str>>();
439
440                for i in 0..expected_deobfuscated_vec.len() {
441                    assert_eq!(
442                        expected_deobfuscated_vec[i],
443                        current_deobfuscated_vec[i],
444                        "File: {}, Deobfuscated line: {}",
445                        file_name(&dir_entry),
446                        i + 1
447                    );
448                }
449
450                for i in 0..expected_output_vec.len() {
451                    assert_eq!(
452                        expected_output_vec[i],
453                        current_output_vec[i],
454                        "File: {}, Output line: {}",
455                        file_name(&dir_entry),
456                        i + 1
457                    );
458                }
459            }
460        }
461    }
462
463    fn file_name(dir_entry: &std::fs::DirEntry) -> String {
464        dir_entry
465            .path()
466            .components()
467            .last()
468            .unwrap()
469            .as_os_str()
470            .to_string_lossy()
471            .to_string()
472    }
473
474    #[allow(dead_code)]
475    fn save_files(dir_entry: &std::fs::DirEntry, deobfuscated: &str, output: &str) {
476        let name = file_name(dir_entry);
477        std::fs::write(format!("{}_deobfuscated.txt", name), deobfuscated).unwrap();
478        std::fs::write(format!("{}_output.txt", name), output).unwrap();
479    }
480
481    #[test]
482    fn test_range() {
483        // Test for even numbers
484        let mut p = PowerShellSession::new().with_variables(Variables::env());
485        let input = r#" $numbers = 1..10; $numbers"#;
486        let script_res = p.parse_input(input).unwrap();
487        assert_eq!(
488            script_res.deobfuscated(),
489            vec![
490                "$numbers = @(1,2,3,4,5,6,7,8,9,10)",
491                "@(1,2,3,4,5,6,7,8,9,10)"
492            ]
493            .join(NEWLINE)
494        );
495        assert_eq!(script_res.errors().len(), 0);
496    }
497
498    #[test]
499    fn even_numbers() {
500        // Test for even numbers
501        let mut p = PowerShellSession::new().with_variables(Variables::env());
502        let input = r#" $numbers = 1..10; $evenNumbers = $numbers | Where-Object { $_ % 2 -eq 0 }; $evenNumbers"#;
503        let script_res = p.parse_input(input).unwrap();
504        assert_eq!(
505            script_res.result(),
506            PsValue::Array(vec![
507                PsValue::Int(2),
508                PsValue::Int(4),
509                PsValue::Int(6),
510                PsValue::Int(8),
511                PsValue::Int(10)
512            ])
513        );
514        assert_eq!(
515            script_res.deobfuscated(),
516            vec![
517                "$numbers = @(1,2,3,4,5,6,7,8,9,10)",
518                "$evennumbers = @(2,4,6,8,10)",
519                "@(2,4,6,8,10)"
520            ]
521            .join(NEWLINE)
522        );
523        assert_eq!(script_res.errors().len(), 0);
524    }
525
526    #[test]
527    fn divisible_by_2_and_3() {
528        // Test for even numbers
529        let mut p = PowerShellSession::new().with_variables(Variables::env());
530        let input = r#" $numbers = 1..10; $numbers | Where { $_ % 2 -eq 0 } | ? { $_ % 3 -eq 0 }"#;
531        let script_res = p.parse_input(input).unwrap();
532        assert_eq!(script_res.result(), PsValue::Int(6));
533        assert_eq!(
534            script_res.deobfuscated(),
535            vec!["$numbers = @(1,2,3,4,5,6,7,8,9,10)", "6"].join(NEWLINE)
536        );
537        assert_eq!(script_res.errors().len(), 0);
538    }
539
540    //#[test]
541    fn _test_function() {
542        // Test for even numbers
543        let mut p = PowerShellSession::new().with_variables(Variables::env());
544        let input = r#" 
545function Get-Square($number) {
546    return $number * $number
547}
548"Square of 5: $(Get-Square 5)" "#;
549        let script_res = p.parse_input(input).unwrap();
550        assert_eq!(
551            script_res.deobfuscated(),
552            vec![
553                "function Get-Square($number) {",
554                "    return $number * $number",
555                "}",
556                " \"Square of 5: $(Get-Square 5)\""
557            ]
558            .join(NEWLINE)
559        );
560        assert_eq!(script_res.errors().len(), 2);
561    }
562
563    #[test]
564    fn test_if() {
565        // Test for even numbers
566        let mut p = PowerShellSession::new().with_variables(Variables::env());
567        let input = r#" 
568        # Test 10: Conditional Statements
569if ($true) {
570    $if_result = "condition true"
571}
572
573if ($false) {
574    $else_result = "false branch"
575} else {
576    $else_result = "true branch"
577}
578
579$score = 85
580if ($score -ge 90) {
581    $grade = "A"
582} elseif ($score -ge 80) {
583    $grade = "B"
584} else {
585    $grade = "C"
586}
587        
588        "#;
589        let script_res = p.parse_input(input).unwrap();
590        assert_eq!(
591            script_res.deobfuscated(),
592            vec![
593                "$if_result = \"condition true\"",
594                "$else_result = \"true branch\"",
595                "$score = 85",
596                "$grade = \"B\""
597            ]
598            .join(NEWLINE)
599        );
600        assert_eq!(script_res.errors().len(), 0);
601    }
602
603    #[test]
604    fn format_operator() {
605        let mut p = PowerShellSession::new().with_variables(Variables::env());
606        let input = r#" ("{5}{2}{0}{1}{3}{6}{4}" -f 'ut',('oma'+'t'+'ion.'),'.A',('Ems'+'iUt'),'ls',('S'+'ystem.'+'Danage'+'men'+'t'),'i')"#;
607        let script_res = p.parse_input(input).unwrap();
608        assert_eq!(
609            script_res.result(),
610            PsValue::String("System.Danagement.Automation.EmsiUtils".into())
611        );
612        assert_eq!(
613            script_res.deobfuscated(),
614            vec![r#""System.Danagement.Automation.EmsiUtils""#].join(NEWLINE)
615        );
616        assert_eq!(script_res.errors().len(), 0);
617    }
618
619    #[test]
620    fn encod_command() {
621        let mut p = PowerShellSession::new().with_variables(Variables::env());
622        let input = r#" ("{5}{2}{0}{1}{3}{6}{4}" -f 'ut',('oma'+'t'+'ion.'),'.A',('Ems'+'iUt'),'ls',('S'+'ystem.'+'Danage'+'men'+'t'),'i')"#;
623        let script_res = p.parse_input(input).unwrap();
624        assert_eq!(
625            script_res.result(),
626            PsValue::String("System.Danagement.Automation.EmsiUtils".into())
627        );
628        assert_eq!(
629            script_res.deobfuscated(),
630            vec![r#""System.Danagement.Automation.EmsiUtils""#].join(NEWLINE)
631        );
632        assert_eq!(script_res.errors().len(), 0);
633    }
634
635    #[test]
636    fn array_literals() {
637        let mut p = PowerShellSession::new().with_variables(Variables::env());
638
639        //integers
640        let input = r#" $a = 1,2,3;$a"#;
641        let script_res = p.parse_input(input).unwrap();
642        assert_eq!(
643            script_res.result(),
644            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3)])
645        );
646        assert_eq!(
647            script_res.deobfuscated(),
648            vec!["$a = @(1,2,3)", "@(1,2,3)"].join(NEWLINE)
649        );
650
651        // strings
652        let input = r#" $a = "x", 'yyy', "z";$a"#;
653        let script_res = p.parse_input(input).unwrap();
654        assert_eq!(
655            script_res.result(),
656            PsValue::Array(vec![
657                PsValue::String("x".into()),
658                PsValue::String("yyy".into()),
659                PsValue::String("z".into())
660            ])
661        );
662        assert_eq!(
663            script_res.deobfuscated(),
664            vec![r#"$a = @("x","yyy","z")"#, r#"@("x","yyy","z")"#].join(NEWLINE)
665        );
666
667        // expresssions
668        let input = r#" $a = 1,2+ 3,[long]4;$a"#;
669        let script_res = p.parse_input(input).unwrap();
670        assert_eq!(
671            script_res.result(),
672            PsValue::Array(vec![
673                PsValue::Int(1),
674                PsValue::Int(2),
675                PsValue::Int(3),
676                PsValue::Int(4),
677            ])
678        );
679        assert_eq!(
680            script_res.deobfuscated(),
681            vec!["$a = @(1,2,3,4)", "@(1,2,3,4)"].join(NEWLINE)
682        );
683
684        // variables
685        let input = r#" $x = 3; $a = $x, $x+1, "count=$x";$a"#;
686        let script_res = p.parse_input(input).unwrap();
687        assert_eq!(
688            script_res.result(),
689            PsValue::Array(vec![
690                PsValue::Int(3),
691                PsValue::Int(3),
692                PsValue::Int(1),
693                PsValue::String("count=3".into()),
694            ])
695        );
696        assert_eq!(
697            script_res.deobfuscated(),
698            vec![
699                "$x = 3",
700                "$a = @(3,3,1,\"count=3\")",
701                "@(3,3,1,\"count=3\")"
702            ]
703            .join(NEWLINE)
704        );
705
706        // nested arrays
707        let input = r#" $a = (1, 2), (3, 4);$a"#;
708        let script_res = p.parse_input(input).unwrap();
709        assert_eq!(
710            script_res.result(),
711            PsValue::Array(vec![
712                PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2)]),
713                PsValue::Array(vec![PsValue::Int(3), PsValue::Int(4)]),
714            ])
715        );
716        assert_eq!(
717            script_res.deobfuscated(),
718            vec!["$a = @(@(1,2),@(3,4))", "@(@(1,2),@(3,4))"].join(NEWLINE)
719        );
720
721        // nested arrays
722        let input = r#" $a = 1, "two", 3.0, $false, (Get-Date);$a"#;
723        let script_res = p.parse_input(input).unwrap();
724        assert_eq!(
725            script_res.result(),
726            PsValue::Array(vec![
727                PsValue::Int(1),
728                PsValue::String("two".into()),
729                PsValue::Float(3.0),
730                PsValue::Bool(false),
731                PsValue::String("Get-Date".into()),
732            ])
733        );
734        assert_eq!(
735            script_res.deobfuscated(),
736            vec![
737                "$a = @(1,\"two\",3,$false,Get-Date)",
738                "@(1,\"two\",3,$false,Get-Date)"
739            ]
740            .join(NEWLINE)
741        );
742
743        // array assign to another array
744        let input = r#" $a = 1, 2,3;$b = $a,4,5;$b"#;
745        let script_res = p.parse_input(input).unwrap();
746        assert_eq!(
747            script_res.result(),
748            PsValue::Array(vec![
749                PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3)]),
750                PsValue::Int(4),
751                PsValue::Int(5),
752            ])
753        );
754        assert_eq!(
755            script_res.deobfuscated(),
756            vec!["$a = @(1,2,3)", "$b = @(@(1,2,3),4,5)", "@(@(1,2,3),4,5)"].join(NEWLINE)
757        );
758
759        // forEach-Object
760        let input = r#"  $a = 1,-2,(-3) | ForEach-Object { $_ * 2 };$a"#;
761        let script_res = p.parse_input(input).unwrap();
762        assert_eq!(
763            script_res.result(),
764            PsValue::Array(vec![PsValue::Int(2), PsValue::Int(-4), PsValue::Int(-6),])
765        );
766
767        // forEach-Object - parentheses
768        let input = r#"  $a = (1,2,3) | ForEach-Object { $_ };$a"#;
769        let script_res = p.parse_input(input).unwrap();
770        assert_eq!(
771            script_res.result(),
772            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3),])
773        );
774        assert_eq!(
775            script_res.deobfuscated(),
776            vec!["$a = @(1,2,3)", "@(1,2,3)"].join(NEWLINE)
777        );
778
779        // array assign to another array
780        let input = r#" $a = @{
781    A = 1,2,3
782    B = (4,5),6
783}
784$a"#;
785        let script_res = p.parse_input(input).unwrap();
786        assert_eq!(
787            script_res.result(),
788            PsValue::HashTable(HashMap::from([
789                (
790                    "a".into(),
791                    PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3),])
792                ),
793                (
794                    "b".into(),
795                    PsValue::Array(vec![
796                        PsValue::Array(vec![PsValue::Int(4), PsValue::Int(5)]),
797                        PsValue::Int(6),
798                    ])
799                ),
800            ]))
801        );
802
803        // function argument as array
804        let input = r#" function Foo($x) { $x.GetType().name + $x[2]};Foo(1,2,3)"#;
805        let script_res = p.parse_input(input).unwrap();
806        assert_eq!(script_res.result(), PsValue::String("Object[]3".into()));
807
808        // function argument as array
809        let input = r#" [object[]](1,2,3)"#;
810        let script_res = p.parse_input(input).unwrap();
811        assert_eq!(
812            script_res.result(),
813            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3)])
814        );
815
816        // function argument as array
817        let input = r#" $a = ,(42,2);$a"#;
818        let script_res = p.parse_input(input).unwrap();
819        assert_eq!(
820            script_res.result(),
821            PsValue::Array(vec![PsValue::Array(vec![
822                PsValue::Int(42),
823                PsValue::Int(2)
824            ])])
825        );
826
827        // function argument as array
828        let input = r#" function Foo($x) { $x.GetType().name + $x[2]};Foo(1,2,3)"#;
829        let script_res = p.parse_input(input).unwrap();
830        assert_eq!(script_res.result(), PsValue::String("Object[]3".into()));
831
832        // function argument as array
833        let input = r#" function b($x) {$x};b(1,2+3,4)"#;
834        let script_res = p.parse_input(input).unwrap();
835        assert_eq!(
836            script_res.result(),
837            PsValue::Array(vec![
838                PsValue::Int(1),
839                PsValue::Int(2),
840                PsValue::Int(3),
841                PsValue::Int(4),
842            ])
843        );
844
845        // function argument as array
846        let input =
847            r#" $a=@{val = 4};function b($x) {$x};b(1, [long]($a | Where-Object val -eq 4).val)"#;
848        let script_res = p.parse_input(input).unwrap();
849        assert_eq!(
850            script_res.result(),
851            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(4)])
852        );
853    }
854
855    #[test]
856    fn cast_expression() {
857        let mut p = PowerShellSession::new().with_variables(Variables::env());
858
859        //simple
860        let input = r#" $a=@{val = 4};[long]($a).val"#;
861        let script_res = p.parse_input(input).unwrap();
862        assert_eq!(script_res.result(), PsValue::Int(4));
863        assert_eq!(
864            script_res.deobfuscated(),
865            vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
866        );
867
868        let input = r#" $a=@{val = 4};[long]($a | Where-Object Val -eq 4).val"#;
869        let script_res = p.parse_input(input).unwrap();
870        assert_eq!(script_res.result(), PsValue::Int(4));
871        assert_eq!(
872            script_res.deobfuscated(),
873            vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
874        );
875    }
876
877    #[test]
878    fn as_expression() {
879        let mut p = PowerShellSession::new().with_variables(Variables::env());
880
881        //simple
882        let input = r#" '1a1' -replace 'a' -as [int] "#;
883        let script_res = p.parse_input(input).unwrap();
884        assert_eq!(script_res.result(), PsValue::Int(11));
885
886        let input = r#" '1a1' -replace ('a' -as [int])"#;
887        let script_res = p.parse_input(input).unwrap();
888        assert_eq!(script_res.result(), PsValue::String("1a1".into()));
889
890        let input = r#" '2' -as [int] -shl 1"#;
891        let script_res = p.parse_input(input).unwrap();
892        assert_eq!(script_res.result(), PsValue::Int(4));
893
894        let input = r#" [system.text.encoding]::unicode -shl 1 "#;
895        let script_res = p.parse_input(input).unwrap();
896        assert_eq!(script_res.result(), PsValue::Null);
897        assert_eq!(
898            script_res.errors()[0].to_string(),
899            String::from("BitwiseError: -shl not defined for UnicodeEncoding")
900        );
901
902        let input = r#" [int] -shl 1 "#;
903        let script_res = p.parse_input(input).unwrap();
904        assert_eq!(script_res.result(), PsValue::Null);
905        assert_eq!(
906            script_res.errors()[0].to_string(),
907            String::from("BitwiseError: -shl not defined for Int32")
908        );
909
910        let input = r#" '2' -as ([string] -shl 1) "#;
911        let script_res = p.parse_input(input).unwrap();
912        assert_eq!(script_res.result(), PsValue::Null);
913        assert_eq!(
914            script_res.errors()[0].to_string(),
915            String::from("BitwiseError: -shl not defined for String")
916        );
917
918        let input = r#" '2' -as ([int]) "#;
919        let script_res = p.parse_input(input).unwrap();
920        assert_eq!(script_res.result(), PsValue::Int(2));
921
922        let input = r#" '2' -As ([int]) "#;
923        let script_res = p.parse_input(input).unwrap();
924        assert_eq!(script_res.result(), PsValue::Int(2));
925    }
926
927    #[test]
928    fn cast_assignment() {
929        let mut p = PowerShellSession::new().with_variables(Variables::env());
930
931        let input = r#" [int] $elo = "1"; $elo "#;
932        let script_res = p.parse_input(input).unwrap();
933        assert_eq!(script_res.result(), PsValue::Int(1));
934
935        let input = r#" [int] $elo = "1a": $elo"#;
936        let script_res = p.parse_input(input).unwrap();
937        assert_eq!(script_res.result(), PsValue::Null);
938        assert_eq!(
939            script_res.errors()[0].to_string(),
940            String::from("ValError: Failed to convert value \"1a\" to type Int")
941        );
942
943        let input = r#" [double] $elo = "1a": $elo"#;
944        let script_res = p.parse_input(input).unwrap();
945        assert_eq!(script_res.result(), PsValue::Null);
946        assert_eq!(
947            script_res.errors()[0].to_string(),
948            String::from("ValError: Failed to convert value \"1a\" to type Float")
949        );
950
951        let input = r#" [int[]] $elo = "1", "2"; $elo"#;
952        let script_res = p.parse_input(input).unwrap();
953        assert_eq!(
954            script_res.result(),
955            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2)])
956        );
957
958        let input = r#" [byte[]] $elo = "1", "2"; $elo"#;
959        let script_res = p.parse_input(input).unwrap();
960        assert_eq!(
961            script_res.result(),
962            PsValue::Array(vec![PsValue::Char(49), PsValue::Char(50)])
963        );
964    }
965
966    #[test]
967    fn splatten_arg() {
968        let mut p = PowerShellSession::new().with_variables(Variables::env());
969
970        let input = r#" $a = @{ elo= 2; name= "radek"}; write-output @a "#;
971        let script_res = p.parse_input(input).unwrap();
972        assert!(script_res.output().contains("-elo 2"));
973        assert!(script_res.output().contains("-name radek"));
974    }
975
976    #[test]
977    fn strange_assignment() {
978        let mut p = PowerShellSession::new().with_variables(Variables::env());
979
980        let input = r#" @(1,2)[0] = 1 "#;
981        let script_res = p.parse_input(input).unwrap();
982        assert_eq!(script_res.errors()[0].to_string(), "Skip".to_string());
983
984        let input = r#" "elo"[0] = 1 "#;
985        let script_res = p.parse_input(input).unwrap();
986        assert_eq!(script_res.errors()[0].to_string(), "Skip".to_string());
987
988        let input = r#" $a = @(1,2); $a[1] = 5; $a "#;
989        let script_res = p.parse_input(input).unwrap();
990        assert_eq!(
991            script_res.result(),
992            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(5)])
993        );
994
995        let input = r#" $a = @(1,@(2,3));$a[1] = 6;$a "#;
996        let script_res = p.parse_input(input).unwrap();
997        assert_eq!(
998            script_res.result(),
999            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(6)])
1000        );
1001
1002        let input = r#" $a = @(1,@(2,3));$a[1][1] = 6;$a "#;
1003        let script_res = p.parse_input(input).unwrap();
1004        assert_eq!(
1005            script_res.result(),
1006            PsValue::Array(vec![
1007                PsValue::Int(1),
1008                PsValue::Array(vec![PsValue::Int(2), PsValue::Int(6)])
1009            ])
1010        );
1011    }
1012
1013    #[test]
1014    fn script_param_block() {
1015        let mut p = PowerShellSession::new().with_variables(Variables::env());
1016
1017        let input = r#" 
1018[CmdletBinding(DefaultParameterSetName = "Path", HelpURI = "https://go.microsoft.com/fwlink/?LinkId=517145")]
1019param(
1020	[Parameter(ParameterSetName="Path", Position = 0)]
1021	[System.String[]]
1022	$Path
1023
1024
1025)
1026
1027begin
1028{
1029	# Construct the strongly-typed crypto object
1030}
1031
1032process
1033{
1034	Write-output elo
1035}
1036"#;
1037        let _script_res = p.parse_input(input).unwrap();
1038    }
1039}