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;
147
148#[cfg(test)]
149mod tests {
150    use std::collections::HashMap;
151
152    use super::*;
153    use crate::Token;
154
155    #[test]
156    fn deobfuscation() {
157        // assign variable and print it to screen
158        let mut p = PowerShellSession::new();
159        let input = r#" $script:var = [char]([int]("9e4e" -replace "e")+3); [int]'a';$var"#;
160        let script_res = p.parse_input(input).unwrap();
161        assert_eq!(script_res.result(), 'a'.into());
162        assert_eq!(
163            script_res.deobfuscated(),
164            vec!["$script:var = 'a'", "[int]'a'", "'a'"].join(NEWLINE)
165        );
166        assert_eq!(script_res.errors().len(), 1);
167        assert_eq!(
168            script_res.errors()[0].to_string(),
169            "ValError: Cannot convert value \"String\" to type \"Int\""
170        );
171
172        // the same but do it in two parts
173        let mut p = PowerShellSession::new();
174        let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3) "#;
175        let script_res = p.parse_input(input).unwrap();
176
177        assert_eq!(script_res.errors().len(), 0);
178
179        let script_res = p.parse_input(" [int]'a';$var ").unwrap();
180        assert_eq!(
181            script_res.deobfuscated(),
182            vec!["[int]'a'", "'a'"].join(NEWLINE)
183        );
184        assert_eq!(script_res.output(), vec!["a"].join(NEWLINE));
185        assert_eq!(script_res.errors().len(), 1);
186        assert_eq!(
187            script_res.errors()[0].to_string(),
188            "ValError: Cannot convert value \"String\" to type \"Int\""
189        );
190    }
191
192    #[test]
193    fn deobfuscation_non_existing_value() {
194        // assign not existing value, without forcing evaluation
195        let mut p = PowerShellSession::new();
196        let input = r#" $local:var = $env:programfiles;[int]'a';$var"#;
197        let script_res = p.parse_input(input).unwrap();
198        assert_eq!(script_res.result(), PsValue::Null);
199        assert_eq!(
200            script_res.deobfuscated(),
201            vec!["$local:var = $env:programfiles", "[int]'a'", "$var"].join(NEWLINE)
202        );
203        assert_eq!(script_res.errors().len(), 3);
204        assert_eq!(
205            script_res.errors()[0].to_string(),
206            "VariableError: Variable \"programfiles\" is not defined"
207        );
208        assert_eq!(
209            script_res.errors()[1].to_string(),
210            "ValError: Cannot convert value \"String\" to type \"Int\""
211        );
212        assert_eq!(
213            script_res.errors()[2].to_string(),
214            "VariableError: Variable \"var\" is not defined"
215        );
216
217        // assign not existing value, forcing evaluation
218        let mut p = PowerShellSession::new().with_variables(Variables::force_eval());
219        let input = r#" $local:var = $env:programfiles;[int]'a';$script:var"#;
220        let script_res = p.parse_input(input).unwrap();
221        assert_eq!(script_res.result(), PsValue::Null);
222        assert_eq!(
223            script_res.deobfuscated(),
224            vec!["$local:var = $null", "[int]'a'"].join(NEWLINE)
225        );
226        assert_eq!(script_res.errors().len(), 1);
227    }
228
229    #[test]
230    fn deobfuscation_env_value() {
231        // assign not existing value, without forcing evaluation
232        let mut p = PowerShellSession::new().with_variables(Variables::env());
233        let input = r#" $local:var = $env:programfiles;$var"#;
234        let script_res = p.parse_input(input).unwrap();
235        assert_eq!(
236            script_res.result(),
237            PsValue::String(std::env::var("PROGRAMFILES").unwrap())
238        );
239        let program_files = std::env::var("PROGRAMFILES").unwrap();
240        assert_eq!(
241            script_res.deobfuscated(),
242            vec![
243                format!("$local:var = \"{}\"", program_files),
244                format!("\"{}\"", program_files)
245            ]
246            .join(NEWLINE)
247        );
248        assert_eq!(script_res.errors().len(), 0);
249    }
250
251    #[test]
252    fn hash_table() {
253        // assign not existing value, without forcing evaluation
254        let mut p = PowerShellSession::new().with_variables(Variables::env().values_persist());
255        let input = r#" 
256$nestedData = @{
257    Users = @(
258        @{ Name = "Alice"; Age = 30; Skills = @("PowerShell", "Python") }
259        @{ Name = "Bob"; Age = 25; Skills = @("Java", "C#") }
260    )
261    Settings = @{
262        Theme = "Dark"
263        Language = "en-US"
264    }
265}
266"$nestedData"
267        "#;
268        let script_res = p.parse_input(input).unwrap();
269        assert_eq!(
270            script_res.result(),
271            PsValue::String("System.Collections.Hashtable".to_string())
272        );
273
274        assert_eq!(
275            p.parse_input("$nesteddata.settings").unwrap().result(),
276            PsValue::HashTable(HashMap::from([
277                ("language".to_string(), PsValue::String("en-US".to_string())),
278                ("theme".to_string(), PsValue::String("Dark".to_string())),
279            ]))
280        );
281
282        assert_eq!(
283            p.safe_eval("$nesteddata.settings.theme").unwrap(),
284            "Dark".to_string()
285        );
286
287        assert_eq!(
288            p.parse_input("$nesteddata.users[0]").unwrap().result(),
289            PsValue::HashTable(HashMap::from([
290                (
291                    "skills".to_string(),
292                    PsValue::Array(vec![
293                        PsValue::String("PowerShell".to_string()),
294                        PsValue::String("Python".to_string().into())
295                    ])
296                ),
297                ("name".to_string(), PsValue::String("Alice".to_string())),
298                ("age".to_string(), PsValue::Int(30)),
299            ]))
300        );
301
302        assert_eq!(
303            p.safe_eval("$nesteddata.users[0]['name']").unwrap(),
304            "Alice".to_string()
305        );
306
307        assert_eq!(
308            p.safe_eval("$nesteddata.users[0].NAME").unwrap(),
309            "Alice".to_string()
310        );
311
312        let input = r#" $a=@{val = 4};$a.val"#;
313        let script_res = p.parse_input(input).unwrap();
314        assert_eq!(script_res.result(), PsValue::Int(4));
315        assert_eq!(
316            script_res.deobfuscated(),
317            vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
318        );
319    }
320
321    #[test]
322    fn test_simple_arithmetic() {
323        let input = r#"
324Write-Host "=== Test 3: Arithmetic Operations ===" -ForegroundColor Green
325$a = 10
326$b = 5
327Write-Output "Addition: $(($a + $b))"
328Write-Output "Subtraction: $(($a - $b))"
329Write-Output "Multiplication: $(($a * $b))"
330Write-Output "Division: $(($a / $b))"
331Write-Output "Modulo: $(($a % $b))"
332"#;
333
334        let script_result = PowerShellSession::new().parse_input(input).unwrap();
335
336        assert_eq!(script_result.result(), PsValue::String("Modulo: 0".into()));
337        assert_eq!(
338            script_result.output(),
339            vec![
340                r#"=== Test 3: Arithmetic Operations ==="#,
341                r#"Addition: 15"#,
342                r#"Subtraction: 5"#,
343                r#"Multiplication: 50"#,
344                r#"Division: 2"#,
345                r#"Modulo: 0"#
346            ]
347            .join(NEWLINE)
348        );
349        assert_eq!(script_result.errors().len(), 0);
350        assert_eq!(script_result.tokens().expandable_strings().len(), 6);
351        assert_eq!(
352            script_result.tokens().expandable_strings()[1],
353            Token::StringExpandable(
354                "\"Addition: $(($a + $b))\"".to_string(),
355                "Addition: 15".to_string()
356            )
357        );
358        assert_eq!(script_result.tokens().expression().len(), 12);
359        assert_eq!(
360            script_result.tokens().expression()[2],
361            Token::Expression("$a + $b".to_string(), PsValue::Int(15))
362        );
363    }
364
365    #[test]
366    fn test_scripts() {
367        use std::fs;
368        let Ok(entries) = fs::read_dir("test_scripts") else {
369            panic!("Failed to read 'test_scripts' directory");
370        };
371        for entry in entries {
372            let dir_entry = entry.unwrap();
373            if std::fs::FileType::is_dir(&dir_entry.file_type().unwrap()) {
374                // If it's a directory, we can read the files inside it
375                let input_script = dir_entry.path().join("input.ps1");
376                let expected_deobfuscated_script = dir_entry.path().join("deobfuscated.txt");
377                let expected_output_script = dir_entry.path().join("output.txt");
378
379                let Ok(input) = fs::read_to_string(&input_script) else {
380                    panic!("Failed to read test file: {}", input_script.display());
381                };
382
383                let Ok(expected_deobfuscated) = fs::read_to_string(&expected_deobfuscated_script)
384                else {
385                    panic!(
386                        "Failed to read test file: {}",
387                        expected_deobfuscated_script.display()
388                    );
389                };
390
391                let Ok(expected_output) = fs::read_to_string(&expected_output_script) else {
392                    panic!(
393                        "Failed to read test file: {}",
394                        expected_output_script.display()
395                    );
396                };
397
398                let script_result = PowerShellSession::new()
399                    .with_variables(Variables::env())
400                    .parse_input(&input)
401                    .unwrap();
402
403                let expected_deobfuscated_vec = expected_deobfuscated
404                    .lines()
405                    .map(|s| s.trim_end())
406                    .collect::<Vec<&str>>();
407
408                let current_deobfuscated = script_result.deobfuscated();
409                let current_output = script_result.output();
410
411                let expected_output_vec = expected_output
412                    .lines()
413                    .map(|s| s.trim_end())
414                    .collect::<Vec<&str>>();
415
416                //save_files(&dir_entry, &current_deobfuscated, &current_output);
417                let current_deobfuscated_vec = current_deobfuscated
418                    .lines()
419                    .map(|s| s.trim_end())
420                    .collect::<Vec<&str>>();
421
422                let current_output_vec = current_output
423                    .lines()
424                    .map(|s| s.trim_end())
425                    .collect::<Vec<&str>>();
426
427                for i in 0..expected_deobfuscated_vec.len() {
428                    assert_eq!(
429                        expected_deobfuscated_vec[i],
430                        current_deobfuscated_vec[i],
431                        "File: {}, Deobfuscated line: {}",
432                        file_name(&dir_entry),
433                        i + 1
434                    );
435                }
436
437                for i in 0..expected_output_vec.len() {
438                    assert_eq!(
439                        expected_output_vec[i],
440                        current_output_vec[i],
441                        "File: {}, Output line: {}",
442                        file_name(&dir_entry),
443                        i + 1
444                    );
445                }
446            }
447        }
448    }
449
450    fn file_name(dir_entry: &std::fs::DirEntry) -> String {
451        dir_entry
452            .path()
453            .components()
454            .last()
455            .unwrap()
456            .as_os_str()
457            .to_string_lossy()
458            .to_string()
459    }
460
461    #[allow(dead_code)]
462    fn save_files(dir_entry: &std::fs::DirEntry, deobfuscated: &str, output: &str) {
463        let name = file_name(dir_entry);
464        std::fs::write(format!("{}_deobfuscated.txt", name), deobfuscated).unwrap();
465        std::fs::write(format!("{}_output.txt", name), output).unwrap();
466    }
467
468    #[test]
469    fn test_range() {
470        // Test for even numbers
471        let mut p = PowerShellSession::new().with_variables(Variables::env());
472        let input = r#" $numbers = 1..10; $numbers"#;
473        let script_res = p.parse_input(input).unwrap();
474        assert_eq!(
475            script_res.deobfuscated(),
476            vec![
477                "$numbers = @(1,2,3,4,5,6,7,8,9,10)",
478                "@(1,2,3,4,5,6,7,8,9,10)"
479            ]
480            .join(NEWLINE)
481        );
482        assert_eq!(script_res.errors().len(), 0);
483    }
484
485    #[test]
486    fn even_numbers() {
487        // Test for even numbers
488        let mut p = PowerShellSession::new().with_variables(Variables::env());
489        let input = r#" $numbers = 1..10; $evenNumbers = $numbers | Where-Object { $_ % 2 -eq 0 }; $evenNumbers"#;
490        let script_res = p.parse_input(input).unwrap();
491        assert_eq!(
492            script_res.result(),
493            PsValue::Array(vec![
494                PsValue::Int(2),
495                PsValue::Int(4),
496                PsValue::Int(6),
497                PsValue::Int(8),
498                PsValue::Int(10)
499            ])
500        );
501        assert_eq!(
502            script_res.deobfuscated(),
503            vec![
504                "$numbers = @(1,2,3,4,5,6,7,8,9,10)",
505                "$evennumbers = @(2,4,6,8,10)",
506                "@(2,4,6,8,10)"
507            ]
508            .join(NEWLINE)
509        );
510        assert_eq!(script_res.errors().len(), 0);
511    }
512
513    #[test]
514    fn divisible_by_2_and_3() {
515        // Test for even numbers
516        let mut p = PowerShellSession::new().with_variables(Variables::env());
517        let input = r#" $numbers = 1..10; $numbers | Where { $_ % 2 -eq 0 } | ? { $_ % 3 -eq 0 }"#;
518        let script_res = p.parse_input(input).unwrap();
519        assert_eq!(script_res.result(), PsValue::Int(6));
520        assert_eq!(
521            script_res.deobfuscated(),
522            vec!["$numbers = @(1,2,3,4,5,6,7,8,9,10)", "6"].join(NEWLINE)
523        );
524        assert_eq!(script_res.errors().len(), 0);
525    }
526
527    //#[test]
528    fn _test_function() {
529        // Test for even numbers
530        let mut p = PowerShellSession::new().with_variables(Variables::env());
531        let input = r#" 
532function Get-Square($number) {
533    return $number * $number
534}
535"Square of 5: $(Get-Square 5)" "#;
536        let script_res = p.parse_input(input).unwrap();
537        assert_eq!(
538            script_res.deobfuscated(),
539            vec![
540                "function Get-Square($number) {",
541                "    return $number * $number",
542                "}",
543                " \"Square of 5: $(Get-Square 5)\""
544            ]
545            .join(NEWLINE)
546        );
547        assert_eq!(script_res.errors().len(), 2);
548    }
549
550    #[test]
551    fn test_if() {
552        // Test for even numbers
553        let mut p = PowerShellSession::new().with_variables(Variables::env());
554        let input = r#" 
555        # Test 10: Conditional Statements
556if ($true) {
557    $if_result = "condition true"
558}
559
560if ($false) {
561    $else_result = "false branch"
562} else {
563    $else_result = "true branch"
564}
565
566$score = 85
567if ($score -ge 90) {
568    $grade = "A"
569} elseif ($score -ge 80) {
570    $grade = "B"
571} else {
572    $grade = "C"
573}
574        
575        "#;
576        let script_res = p.parse_input(input).unwrap();
577        assert_eq!(
578            script_res.deobfuscated(),
579            vec![
580                "$if_result = \"condition true\"",
581                "$else_result = \"true branch\"",
582                "$score = 85",
583                "$grade = \"B\""
584            ]
585            .join(NEWLINE)
586        );
587        assert_eq!(script_res.errors().len(), 0);
588    }
589
590    #[test]
591    fn format_operator() {
592        let mut p = PowerShellSession::new().with_variables(Variables::env());
593        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')"#;
594        let script_res = p.parse_input(input).unwrap();
595        assert_eq!(
596            script_res.result(),
597            PsValue::String("System.Danagement.Automation.EmsiUtils".into())
598        );
599        assert_eq!(
600            script_res.deobfuscated(),
601            vec![r#""System.Danagement.Automation.EmsiUtils""#].join(NEWLINE)
602        );
603        assert_eq!(script_res.errors().len(), 0);
604    }
605
606    #[test]
607    fn encod_command() {
608        let mut p = PowerShellSession::new().with_variables(Variables::env());
609        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')"#;
610        let script_res = p.parse_input(input).unwrap();
611        assert_eq!(
612            script_res.result(),
613            PsValue::String("System.Danagement.Automation.EmsiUtils".into())
614        );
615        assert_eq!(
616            script_res.deobfuscated(),
617            vec![r#""System.Danagement.Automation.EmsiUtils""#].join(NEWLINE)
618        );
619        assert_eq!(script_res.errors().len(), 0);
620    }
621
622    #[test]
623    fn array_literals() {
624        let mut p = PowerShellSession::new().with_variables(Variables::env());
625
626        //integers
627        let input = r#" $a = 1,2,3;$a"#;
628        let script_res = p.parse_input(input).unwrap();
629        assert_eq!(
630            script_res.result(),
631            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3)])
632        );
633        assert_eq!(
634            script_res.deobfuscated(),
635            vec!["$a = @(1,2,3)", "@(1,2,3)"].join(NEWLINE)
636        );
637
638        // strings
639        let input = r#" $a = "x", 'yyy', "z";$a"#;
640        let script_res = p.parse_input(input).unwrap();
641        assert_eq!(
642            script_res.result(),
643            PsValue::Array(vec![
644                PsValue::String("x".into()),
645                PsValue::String("yyy".into()),
646                PsValue::String("z".into())
647            ])
648        );
649        assert_eq!(
650            script_res.deobfuscated(),
651            vec![r#"$a = @("x","yyy","z")"#, r#"@("x","yyy","z")"#].join(NEWLINE)
652        );
653
654        // expresssions
655        let input = r#" $a = 1,2+ 3,[long]4;$a"#;
656        let script_res = p.parse_input(input).unwrap();
657        assert_eq!(
658            script_res.result(),
659            PsValue::Array(vec![
660                PsValue::Int(1),
661                PsValue::Int(2),
662                PsValue::Int(3),
663                PsValue::Int(4),
664            ])
665        );
666        assert_eq!(
667            script_res.deobfuscated(),
668            vec!["$a = @(1,2,3,4)", "@(1,2,3,4)"].join(NEWLINE)
669        );
670
671        // variables
672        let input = r#" $x = 3; $a = $x, $x+1, "count=$x";$a"#;
673        let script_res = p.parse_input(input).unwrap();
674        assert_eq!(
675            script_res.result(),
676            PsValue::Array(vec![
677                PsValue::Int(3),
678                PsValue::Int(3),
679                PsValue::Int(1),
680                PsValue::String("count=3".into()),
681            ])
682        );
683        assert_eq!(
684            script_res.deobfuscated(),
685            vec![
686                "$x = 3",
687                "$a = @(3,3,1,\"count=3\")",
688                "@(3,3,1,\"count=3\")"
689            ]
690            .join(NEWLINE)
691        );
692
693        // nested arrays
694        let input = r#" $a = (1, 2), (3, 4);$a"#;
695        let script_res = p.parse_input(input).unwrap();
696        assert_eq!(
697            script_res.result(),
698            PsValue::Array(vec![
699                PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2)]),
700                PsValue::Array(vec![PsValue::Int(3), PsValue::Int(4)]),
701            ])
702        );
703        assert_eq!(
704            script_res.deobfuscated(),
705            vec!["$a = @(@(1,2),@(3,4))", "@(@(1,2),@(3,4))"].join(NEWLINE)
706        );
707
708        // nested arrays
709        let input = r#" $a = 1, "two", 3.0, $false, (Get-Date);$a"#;
710        let script_res = p.parse_input(input).unwrap();
711        assert_eq!(
712            script_res.result(),
713            PsValue::Array(vec![
714                PsValue::Int(1),
715                PsValue::String("two".into()),
716                PsValue::Float(3.0),
717                PsValue::Bool(false),
718                PsValue::String("Get-Date".into()),
719            ])
720        );
721        assert_eq!(
722            script_res.deobfuscated(),
723            vec![
724                "$a = @(1,\"two\",3,$false,Get-Date)",
725                "@(1,\"two\",3,$false,Get-Date)"
726            ]
727            .join(NEWLINE)
728        );
729
730        // array assign to another array
731        let input = r#" $a = 1, 2,3;$b = $a,4,5;$b"#;
732        let script_res = p.parse_input(input).unwrap();
733        assert_eq!(
734            script_res.result(),
735            PsValue::Array(vec![
736                PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3)]),
737                PsValue::Int(4),
738                PsValue::Int(5),
739            ])
740        );
741        assert_eq!(
742            script_res.deobfuscated(),
743            vec!["$a = @(1,2,3)", "$b = @(@(1,2,3),4,5)", "@(@(1,2,3),4,5)"].join(NEWLINE)
744        );
745
746        // forEach-Object
747        let input = r#"  $a = 1,-2,(-3) | ForEach-Object { $_ * 2 };$a"#;
748        let script_res = p.parse_input(input).unwrap();
749        assert_eq!(
750            script_res.result(),
751            PsValue::Array(vec![PsValue::Int(2), PsValue::Int(-4), PsValue::Int(-6),])
752        );
753
754        // forEach-Object - parentheses
755        let input = r#"  $a = (1,2,3) | ForEach-Object { $_ };$a"#;
756        let script_res = p.parse_input(input).unwrap();
757        assert_eq!(
758            script_res.result(),
759            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3),])
760        );
761        assert_eq!(
762            script_res.deobfuscated(),
763            vec!["$a = @(1,2,3)", "@(1,2,3)"].join(NEWLINE)
764        );
765
766        // array assign to another array
767        let input = r#" $a = @{
768    A = 1,2,3
769    B = (4,5),6
770}
771$a"#;
772        let script_res = p.parse_input(input).unwrap();
773        assert_eq!(
774            script_res.result(),
775            PsValue::HashTable(HashMap::from([
776                (
777                    "a".into(),
778                    PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3),])
779                ),
780                (
781                    "b".into(),
782                    PsValue::Array(vec![
783                        PsValue::Array(vec![PsValue::Int(4), PsValue::Int(5)]),
784                        PsValue::Int(6),
785                    ])
786                ),
787            ]))
788        );
789
790        // function argument as array
791        let input = r#" function Foo($x) { $x.GetType().name + $x[2]};Foo(1,2,3)"#;
792        let script_res = p.parse_input(input).unwrap();
793        assert_eq!(script_res.result(), PsValue::String("Object[]3".into()));
794
795        // function argument as array
796        let input = r#" $a = ,(42,2);$a"#;
797        let script_res = p.parse_input(input).unwrap();
798        assert_eq!(
799            script_res.result(),
800            PsValue::Array(vec![PsValue::Array(vec![
801                PsValue::Int(42),
802                PsValue::Int(2)
803            ])])
804        );
805
806        // function argument as array
807        let input = r#" function Foo($x) { $x.GetType().name + $x[2]};Foo(1,2,3)"#;
808        let script_res = p.parse_input(input).unwrap();
809        assert_eq!(script_res.result(), PsValue::String("Object[]3".into()));
810
811        // function argument as array
812        let input = r#" function b($x) {$x};b(1,2+3,4)"#;
813        let script_res = p.parse_input(input).unwrap();
814        assert_eq!(
815            script_res.result(),
816            PsValue::Array(vec![
817                PsValue::Int(1),
818                PsValue::Int(2),
819                PsValue::Int(3),
820                PsValue::Int(4),
821            ])
822        );
823
824        // function argument as array
825        let input =
826            r#" $a=@{val = 4};function b($x) {$x};b(1, [long]($a | Where-Object val -eq 4).val)"#;
827        let script_res = p.parse_input(input).unwrap();
828        assert_eq!(
829            script_res.result(),
830            PsValue::Array(vec![PsValue::Int(1), PsValue::Int(4)])
831        );
832    }
833
834    #[test]
835    fn cast_expression() {
836        let mut p = PowerShellSession::new().with_variables(Variables::env());
837
838        //simple
839        let input = r#" $a=@{val = 4};[long]($a).val"#;
840        let script_res = p.parse_input(input).unwrap();
841        assert_eq!(script_res.result(), PsValue::Int(4));
842        assert_eq!(
843            script_res.deobfuscated(),
844            vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
845        );
846
847        let input = r#" $a=@{val = 4};[long]($a | Where-Object Val -eq 4).val"#;
848        let script_res = p.parse_input(input).unwrap();
849        assert_eq!(script_res.result(), PsValue::Int(4));
850        assert_eq!(
851            script_res.deobfuscated(),
852            vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
853        );
854    }
855}