Skip to main content

windows_erg/process/
peb.rs

1//! PEB (Process Environment Block) access for reading process parameters.
2
3use std::collections::HashMap;
4use std::mem::size_of;
5use windows::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation};
6use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
7use windows::Win32::System::Threading::{
8    PEB, PROCESS_BASIC_INFORMATION, RTL_USER_PROCESS_PARAMETERS,
9};
10
11use super::processes::Process;
12use super::types::{ImagePath, ProcessParameters};
13use crate::error::{Error, ProcessError, ProcessOpenError, Result};
14
15// UNICODE_STRING structure (used in RTL_USER_PROCESS_PARAMETERS)
16#[repr(C)]
17struct UNICODE_STRING {
18    length: u16,
19    maximum_length: u16,
20    buffer: *mut u16,
21}
22
23// Cache struct sizes for fast access
24const PROCESS_BASIC_INFORMATION_SIZE: usize = size_of::<PROCESS_BASIC_INFORMATION>();
25const PEB_SIZE: usize = size_of::<PEB>();
26const RTL_USER_PROCESS_PARAMETERS_SIZE: usize = size_of::<RTL_USER_PROCESS_PARAMETERS>();
27
28// RTL_USER_PROCESS_PARAMETERS layout for Windows 7+ (x64)
29// This partial struct includes fields up to and including Environment pointer
30// Stable across Windows versions; only includes fields we actually need
31// https://ntdoc.m417z.com/rtl_user_process_parameters
32#[repr(C)]
33struct RTL_USER_PROCESS_PARAMETERS_PARTIAL {
34    _pad1: [u8; 32],                 // Reserved fields (0x00-0x1F)
35    _flags: u32,                     // Flags at 0x20
36    _pad2: [u8; 8],                  // More reserved (0x24-0x2B)
37    stdin: *mut u8,                  // 0x2C
38    stdout: *mut u8,                 // 0x34
39    stderr: *mut u8,                 // 0x3C
40    image_path_name: UNICODE_STRING, // 0x44
41    command_line: UNICODE_STRING,    // 0x54
42    environment: *mut u16,           // 0x64 - Environment block pointer
43}
44
45impl Process {
46    /// Get the command line of the process.
47    ///
48    /// This reads the command line from the Process Environment Block (PEB).
49    pub fn command_line(&self) -> Result<String> {
50        let mut buffer = Vec::with_capacity(8192);
51        self.command_line_with_buffer(&mut buffer)
52    }
53
54    /// Get the command line using a reusable output buffer.
55    pub fn command_line_with_buffer(&self, out_buffer: &mut Vec<u8>) -> Result<String> {
56        let params = self.read_process_parameters(out_buffer)?;
57        Ok(params.command_line)
58    }
59
60    /// Get the environment variables of the process.
61    pub fn environment(&self) -> Result<HashMap<String, String>> {
62        let mut buffer = Vec::with_capacity(8192);
63        self.environment_with_buffer(&mut buffer)
64    }
65
66    /// Get the environment variables using a reusable output buffer.
67    pub fn environment_with_buffer(
68        &self,
69        out_buffer: &mut Vec<u8>,
70    ) -> Result<HashMap<String, String>> {
71        // Read PEB to get RTL_USER_PROCESS_PARAMETERS pointer
72        let peb_addr = self.read_peb_address(out_buffer)?;
73
74        out_buffer.clear();
75        if out_buffer.capacity() < PEB_SIZE {
76            out_buffer.reserve(PEB_SIZE - out_buffer.capacity());
77        }
78        unsafe {
79            out_buffer.set_len(PEB_SIZE);
80        }
81
82        let mut bytes_read = 0;
83        unsafe {
84            ReadProcessMemory(
85                self.as_raw_handle(),
86                peb_addr as _,
87                out_buffer.as_mut_ptr() as _,
88                PEB_SIZE,
89                Some(&mut bytes_read),
90            )
91        }
92        .map_err(|e| {
93            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
94                self.id().as_u32(),
95                "Failed to read PEB",
96                e.code().0,
97            )))
98        })?;
99
100        let peb = unsafe { &*(out_buffer.as_ptr() as *const PEB) };
101        let params_addr = peb.ProcessParameters as usize;
102
103        // Read RTL_USER_PROCESS_PARAMETERS
104        out_buffer.clear();
105        if out_buffer.capacity() < RTL_USER_PROCESS_PARAMETERS_SIZE {
106            out_buffer.reserve(RTL_USER_PROCESS_PARAMETERS_SIZE - out_buffer.capacity());
107        }
108        unsafe {
109            out_buffer.set_len(RTL_USER_PROCESS_PARAMETERS_SIZE);
110        }
111
112        bytes_read = 0;
113        unsafe {
114            ReadProcessMemory(
115                self.as_raw_handle(),
116                params_addr as _,
117                out_buffer.as_mut_ptr() as _,
118                RTL_USER_PROCESS_PARAMETERS_SIZE,
119                Some(&mut bytes_read),
120            )
121        }
122        .map_err(|e| {
123            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
124                self.id().as_u32(),
125                "Failed to read process parameters",
126                e.code().0,
127            )))
128        })?;
129
130        // Cast to our partial struct to get the environment pointer
131        let params =
132            unsafe { &*(out_buffer.as_ptr() as *const RTL_USER_PROCESS_PARAMETERS_PARTIAL) };
133
134        // Read environment block from the environment pointer
135        self.read_environment_block(params.environment as usize, out_buffer)
136    }
137
138    /// Get all process parameters (command line, current directory, image path).
139    pub fn parameters(&self) -> Result<ProcessParameters> {
140        let mut buffer = Vec::with_capacity(8192);
141        self.parameters_with_buffer(&mut buffer)
142    }
143
144    /// Get all process parameters using a reusable output buffer.
145    pub fn parameters_with_buffer(&self, out_buffer: &mut Vec<u8>) -> Result<ProcessParameters> {
146        self.read_process_parameters(out_buffer)
147    }
148
149    /// Internal: Read PEB address.
150    fn read_peb_address(&self, buffer: &mut Vec<u8>) -> Result<usize> {
151        buffer.clear();
152        if buffer.capacity() < PROCESS_BASIC_INFORMATION_SIZE {
153            buffer.reserve(PROCESS_BASIC_INFORMATION_SIZE - buffer.capacity());
154        }
155        unsafe {
156            buffer.set_len(PROCESS_BASIC_INFORMATION_SIZE);
157        }
158
159        let mut return_length = 0u32;
160        unsafe {
161            NtQueryInformationProcess(
162                self.as_raw_handle(),
163                ProcessBasicInformation,
164                buffer.as_mut_ptr() as _,
165                PROCESS_BASIC_INFORMATION_SIZE as u32,
166                &mut return_length,
167            )
168            .ok()
169        }
170        .map_err(|e| {
171            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
172                self.id().as_u32(),
173                "Failed to query process information",
174                e.code().0,
175            )))
176        })?;
177
178        let basic_info = unsafe { &*(buffer.as_ptr() as *const PROCESS_BASIC_INFORMATION) };
179        Ok(basic_info.PebBaseAddress as usize)
180    }
181
182    /// Internal: Read process parameters from PEB.
183    fn read_process_parameters(&self, buffer: &mut Vec<u8>) -> Result<ProcessParameters> {
184        let peb_addr = self.read_peb_address(buffer)?;
185
186        // Read PEB
187        buffer.clear();
188        if buffer.capacity() < PEB_SIZE {
189            buffer.reserve(PEB_SIZE - buffer.capacity());
190        }
191        unsafe {
192            buffer.set_len(PEB_SIZE);
193        }
194
195        let mut bytes_read = 0;
196        unsafe {
197            ReadProcessMemory(
198                self.as_raw_handle(),
199                peb_addr as _,
200                buffer.as_mut_ptr() as _,
201                PEB_SIZE,
202                Some(&mut bytes_read),
203            )
204        }
205        .map_err(|e| {
206            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
207                self.id().as_u32(),
208                "Failed to read PEB for parameters",
209                e.code().0,
210            )))
211        })?;
212
213        let peb = unsafe { &*(buffer.as_ptr() as *const PEB) };
214        let params_addr = peb.ProcessParameters as usize;
215
216        // Read RTL_USER_PROCESS_PARAMETERS
217        buffer.clear();
218        if buffer.capacity() < RTL_USER_PROCESS_PARAMETERS_SIZE {
219            buffer.reserve(RTL_USER_PROCESS_PARAMETERS_SIZE - buffer.capacity());
220        }
221        unsafe {
222            buffer.set_len(RTL_USER_PROCESS_PARAMETERS_SIZE);
223        }
224
225        bytes_read = 0;
226        unsafe {
227            ReadProcessMemory(
228                self.as_raw_handle(),
229                params_addr as _,
230                buffer.as_mut_ptr() as _,
231                RTL_USER_PROCESS_PARAMETERS_SIZE,
232                Some(&mut bytes_read),
233            )
234        }
235        .map_err(|e| {
236            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
237                self.id().as_u32(),
238                "Failed to read RTL_USER_PROCESS_PARAMETERS",
239                e.code().0,
240            )))
241        })?;
242
243        let params = unsafe { &*(buffer.as_ptr() as *const RTL_USER_PROCESS_PARAMETERS) };
244
245        // Read command line
246        let cmd_line = self.read_unicode_string(
247            params.CommandLine.Buffer.0 as usize,
248            params.CommandLine.Length as usize,
249            buffer,
250        )?;
251
252        // Read image path
253        let image_path = self.read_unicode_string(
254            params.ImagePathName.Buffer.0 as usize,
255            params.ImagePathName.Length as usize,
256            buffer,
257        )?;
258
259        Ok(ProcessParameters {
260            command_line: cmd_line,
261            current_directory: String::new(), // Not available in windows-rs bindings
262            image_path: ImagePath::from_str(&image_path),
263        })
264    }
265
266    /// Internal: Read a UNICODE_STRING from process memory.
267    fn read_unicode_string(
268        &self,
269        addr: usize,
270        length: usize,
271        buffer: &mut Vec<u8>,
272    ) -> Result<String> {
273        if addr == 0 || length == 0 {
274            return Ok(String::new());
275        }
276
277        buffer.clear();
278        if buffer.capacity() < length {
279            buffer.reserve(length - buffer.capacity());
280        }
281        unsafe {
282            buffer.set_len(length);
283        }
284
285        let mut bytes_read = 0;
286        unsafe {
287            ReadProcessMemory(
288                self.as_raw_handle(),
289                addr as _,
290                buffer.as_mut_ptr() as _,
291                length,
292                Some(&mut bytes_read),
293            )
294        }
295        .map_err(|e| {
296            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
297                self.id().as_u32(),
298                "Failed to read string from process memory",
299                e.code().0,
300            )))
301        })?;
302
303        let u16_slice =
304            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u16, bytes_read / 2) };
305
306        // Find null terminator or use full length
307        let end = u16_slice
308            .iter()
309            .position(|&c| c == 0)
310            .unwrap_or(u16_slice.len());
311
312        Ok(String::from_utf16_lossy(&u16_slice[..end]))
313    }
314
315    /// Internal: Read environment block from process memory and parse into HashMap.
316    /// Environment block format: KEY1=VALUE1\0KEY2=VALUE2\0...\0
317    fn read_environment_block(
318        &self,
319        addr: usize,
320        buffer: &mut Vec<u8>,
321    ) -> Result<HashMap<String, String>> {
322        if addr == 0 {
323            return Ok(HashMap::new());
324        }
325
326        // Read up to 64KB of environment data (typical max)
327        let max_size = 65536;
328        buffer.clear();
329        buffer.resize(max_size, 0);
330
331        let mut bytes_read = 0;
332        unsafe {
333            ReadProcessMemory(
334                self.as_raw_handle(),
335                addr as _,
336                buffer.as_mut_ptr() as _,
337                max_size,
338                Some(&mut bytes_read),
339            )
340        }
341        .map_err(|e| {
342            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
343                self.id().as_u32(),
344                "Failed to read environment block",
345                e.code().0,
346            )))
347        })?;
348
349        // Parse the environment block
350        let u16_data =
351            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u16, bytes_read / 2) };
352
353        let mut env_vars = HashMap::new();
354        let mut pos = 0;
355
356        // Parse KEY=VALUE pairs separated by null terminators
357        // Block ends with double null terminator
358        while pos < u16_data.len() {
359            // Find next null terminator
360            let start = pos;
361            while pos < u16_data.len() && u16_data[pos] != 0 {
362                pos += 1;
363            }
364
365            // Empty string means we hit double null (end of block)
366            if start == pos {
367                break;
368            }
369
370            // Convert this entry to string
371            let entry = String::from_utf16_lossy(&u16_data[start..pos]);
372
373            // Split on first '=' to get key and value
374            if let Some(eq_pos) = entry.find('=') {
375                let key = entry[..eq_pos].to_string();
376                let value = entry[eq_pos + 1..].to_string();
377                env_vars.insert(key, value);
378            }
379
380            pos += 1; // Skip the null terminator
381        }
382
383        Ok(env_vars)
384    }
385}
386
387#[cfg(test)]
388mod tests {
389    use super::*;
390
391    /// Helper to convert a string to UTF-16 u16 vector
392    fn str_to_u16_vec(s: &str) -> Vec<u16> {
393        s.encode_utf16().collect()
394    }
395
396    /// Helper to create environment block bytes (KEY=VALUE\0KEY=VALUE\0\0)
397    fn create_env_block(pairs: &[(&str, &str)]) -> Vec<u8> {
398        let mut block = Vec::new();
399
400        for (key, value) in pairs {
401            let entry = format!("{}={}", key, value);
402            for u16_val in entry.encode_utf16() {
403                block.push((u16_val & 0xFF) as u8);
404                block.push(((u16_val >> 8) & 0xFF) as u8);
405            }
406            // Null terminator for this entry
407            block.push(0);
408            block.push(0);
409        }
410
411        // Double null to end block
412        block.push(0);
413        block.push(0);
414
415        block
416    }
417
418    #[test]
419    fn test_parse_simple_environment() {
420        // Create a simple environment block: PATH=C:\Windows\0\0
421        let env_block = create_env_block(&[("PATH", "C:\\Windows"), ("TEMP", "C:\\Temp")]);
422
423        // Convert to u16 slice for parsing
424        let u16_data: Vec<u16> = env_block
425            .chunks_exact(2)
426            .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
427            .collect();
428
429        // Manual parsing logic (same as in read_environment_block)
430        let mut env_vars: HashMap<String, String> = HashMap::new();
431        let mut pos = 0;
432
433        while pos < u16_data.len() {
434            let start = pos;
435            while pos < u16_data.len() && u16_data[pos] != 0 {
436                pos += 1;
437            }
438
439            if start == pos {
440                break;
441            }
442
443            let entry = String::from_utf16_lossy(&u16_data[start..pos]);
444            if let Some(eq_pos) = entry.find('=') {
445                let key = entry[..eq_pos].to_string();
446                let value = entry[eq_pos + 1..].to_string();
447                env_vars.insert(key, value);
448            }
449
450            pos += 1;
451        }
452
453        assert_eq!(
454            env_vars.get("PATH").map(|s| s.as_str()),
455            Some("C:\\Windows")
456        );
457        assert_eq!(env_vars.get("TEMP").map(|s| s.as_str()), Some("C:\\Temp"));
458    }
459
460    #[test]
461    fn test_parse_environment_with_equals_in_value() {
462        // Environment variable with = in the value
463        let env_block = create_env_block(&[("URL", "https://example.com?foo=bar")]);
464
465        let u16_data: Vec<u16> = env_block
466            .chunks_exact(2)
467            .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
468            .collect();
469
470        let mut env_vars: HashMap<String, String> = HashMap::new();
471        let mut pos = 0;
472
473        while pos < u16_data.len() {
474            let start = pos;
475            while pos < u16_data.len() && u16_data[pos] != 0 {
476                pos += 1;
477            }
478
479            if start == pos {
480                break;
481            }
482
483            let entry = String::from_utf16_lossy(&u16_data[start..pos]);
484            if let Some(eq_pos) = entry.find('=') {
485                let key = entry[..eq_pos].to_string();
486                let value = entry[eq_pos + 1..].to_string();
487                env_vars.insert(key, value);
488            }
489
490            pos += 1;
491        }
492
493        assert_eq!(
494            env_vars.get("URL").map(|s| s.as_str()),
495            Some("https://example.com?foo=bar")
496        );
497    }
498
499    #[test]
500    fn test_parse_environment_many_variables() {
501        // Test with many environment variables
502        let pairs = vec![
503            ("PATH", "C:\\Windows"),
504            ("TEMP", "C:\\Temp"),
505            ("WINDIR", "C:\\Windows"),
506            ("USERNAME", "Admin"),
507            ("COMPUTERNAME", "DESKTOP"),
508            ("PROCESSOR_ARCHITECTURE", "AMD64"),
509        ];
510
511        let env_block = create_env_block(&pairs);
512        let u16_data: Vec<u16> = env_block
513            .chunks_exact(2)
514            .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
515            .collect();
516
517        let mut env_vars = HashMap::new();
518        let mut pos = 0;
519
520        while pos < u16_data.len() {
521            let start = pos;
522            while pos < u16_data.len() && u16_data[pos] != 0 {
523                pos += 1;
524            }
525
526            if start == pos {
527                break;
528            }
529
530            let entry = String::from_utf16_lossy(&u16_data[start..pos]);
531            if let Some(eq_pos) = entry.find('=') {
532                let key = entry[..eq_pos].to_string();
533                let value = entry[eq_pos + 1..].to_string();
534                env_vars.insert(key, value);
535            }
536
537            pos += 1;
538        }
539
540        assert_eq!(env_vars.len(), 6);
541        assert_eq!(
542            env_vars.get("PATH").map(|s| s.as_str()),
543            Some("C:\\Windows")
544        );
545        assert_eq!(env_vars.get("USERNAME").map(|s| s.as_str()), Some("Admin"));
546        assert_eq!(
547            env_vars.get("PROCESSOR_ARCHITECTURE").map(|s| s.as_str()),
548            Some("AMD64")
549        );
550    }
551
552    #[test]
553    fn test_parse_environment_unicode_values() {
554        // Test with Unicode characters in environment values
555        let env_block = create_env_block(&[("TEST", "Hello🌍World")]);
556
557        let u16_data: Vec<u16> = env_block
558            .chunks_exact(2)
559            .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
560            .collect();
561
562        let mut env_vars: HashMap<String, String> = HashMap::new();
563        let mut pos = 0;
564
565        while pos < u16_data.len() {
566            let start = pos;
567            while pos < u16_data.len() && u16_data[pos] != 0 {
568                pos += 1;
569            }
570
571            if start == pos {
572                break;
573            }
574
575            let entry = String::from_utf16_lossy(&u16_data[start..pos]);
576            if let Some(eq_pos) = entry.find('=') {
577                let key = entry[..eq_pos].to_string();
578                let value = entry[eq_pos + 1..].to_string();
579                env_vars.insert(key, value);
580            }
581
582            pos += 1;
583        }
584
585        assert_eq!(
586            env_vars.get("TEST").map(|s| s.as_str()),
587            Some("Hello🌍World")
588        );
589    }
590
591    #[test]
592    fn test_unicode_string_struct_layout() {
593        // Verify that UNICODE_STRING has correct size and layout
594        assert_eq!(
595            size_of::<UNICODE_STRING>(),
596            16,
597            "UNICODE_STRING should be 16 bytes"
598        );
599    }
600
601    #[test]
602    fn test_rtl_user_process_parameters_partial_layout() {
603        // Verify that our partial struct has correct size
604        // Should be at least 0x6C bytes (environment pointer + size)
605        assert!(
606            size_of::<RTL_USER_PROCESS_PARAMETERS_PARTIAL>() >= 0x6C,
607            "RTL_USER_PROCESS_PARAMETERS_PARTIAL should be at least 0x6C bytes"
608        );
609    }
610
611    #[test]
612    fn test_str_to_u16_conversion() {
613        let s = "PATH";
614        let u16_vec = str_to_u16_vec(s);
615        let recovered = String::from_utf16_lossy(&u16_vec);
616        assert_eq!(recovered, s);
617    }
618
619    #[test]
620    fn test_environment_block_structure() {
621        // Verify the structure of our environment block creation
622        let block = create_env_block(&[("A", "B"), ("C", "D")]);
623
624        // Just verify it's not empty and contains the right structure
625        assert!(!block.is_empty(), "Block should not be empty");
626
627        // Convert and verify first few characters
628        if block.len() >= 6 {
629            let a_char = u16::from_le_bytes([block[0], block[1]]);
630            let eq_char = u16::from_le_bytes([block[2], block[3]]);
631            let b_char = u16::from_le_bytes([block[4], block[5]]);
632
633            assert_eq!(a_char, b'A' as u16, "First char should be 'A'");
634            assert_eq!(eq_char, b'=' as u16, "Second should be '='");
635            assert_eq!(b_char, b'B' as u16, "Third should be 'B'");
636        }
637    }
638
639    // Note: These integration tests read actual process PEB data.
640    // They may not work with pseudo-handles in all cases - using #[ignore] to make optional
641
642    #[test]
643    #[ignore] // May fail with pseudo-handle - run manually: cargo test -- --ignored
644    fn test_command_line_of_current_process() {
645        // Get current process and read its command line
646        let current_process = Process::current();
647        let cmd_line = current_process
648            .command_line()
649            .expect("Should read command line");
650
651        // Command line should not be empty
652        assert!(!cmd_line.is_empty(), "Command line should not be empty");
653
654        // Should contain the executable name or path
655        // For test runner, should contain something like "cargo" or the test executable
656        assert!(
657            cmd_line.contains(".exe") || cmd_line.contains("cargo") || !cmd_line.is_empty(),
658            "Command line should contain executable name"
659        );
660    }
661
662    #[test]
663    #[ignore] // May fail with pseudo-handle - run manually: cargo test -- --ignored
664    fn test_command_line_with_buffer() {
665        // Test the buffer-reusable version
666        let current_process = Process::current();
667        let mut buffer = Vec::with_capacity(8192);
668
669        let cmd_line = current_process
670            .command_line_with_buffer(&mut buffer)
671            .expect("Should read command line with buffer");
672
673        assert!(!cmd_line.is_empty(), "Command line should not be empty");
674
675        // Reuse buffer for second call - should work correctly
676        let cmd_line2 = current_process
677            .command_line_with_buffer(&mut buffer)
678            .expect("Should read command line again");
679
680        assert_eq!(cmd_line, cmd_line2, "Command line should be consistent");
681    }
682
683    #[test]
684    #[ignore] // May fail with pseudo-handle - run manually: cargo test -- --ignored
685    fn test_parameters_of_current_process() {
686        // Get current process and read its parameters
687        let current_process = Process::current();
688        let params = current_process
689            .parameters()
690            .expect("Should read parameters");
691
692        // Command line should not be empty
693        assert!(
694            !params.command_line.is_empty(),
695            "Parameters command_line should not be empty"
696        );
697
698        // Image path should be set (the executable path)
699        let image_str = params.image_path.as_str();
700        assert!(
701            !image_str.is_empty(),
702            "Parameters image_path should not be empty"
703        );
704        assert!(
705            image_str.contains(".exe") || !image_str.is_empty(),
706            "Image path should look like an executable"
707        );
708    }
709
710    #[test]
711    #[ignore] // May fail with pseudo-handle - run manually: cargo test -- --ignored
712    fn test_parameters_with_buffer() {
713        // Test the buffer-reusable version
714        let current_process = Process::current();
715        let mut buffer = Vec::with_capacity(8192);
716
717        let params = current_process
718            .parameters_with_buffer(&mut buffer)
719            .expect("Should read parameters with buffer");
720
721        assert!(
722            !params.command_line.is_empty(),
723            "Command line should not be empty"
724        );
725
726        // Reuse buffer for second call
727        let params2 = current_process
728            .parameters_with_buffer(&mut buffer)
729            .expect("Should read parameters again");
730
731        assert_eq!(
732            params.command_line, params2.command_line,
733            "Command line should be consistent"
734        );
735        assert_eq!(
736            params.image_path.as_str(),
737            params2.image_path.as_str(),
738            "Image path should be consistent"
739        );
740    }
741
742    #[test]
743    #[ignore] // May fail with pseudo-handle - run manually: cargo test -- --ignored
744    fn test_environment_of_current_process() {
745        // Get current process and read its environment variables
746        let current_process = Process::current();
747        let env = current_process
748            .environment()
749            .expect("Should read environment variables");
750
751        // Should have some environment variables
752        assert!(!env.is_empty(), "Environment should not be empty");
753
754        // PATH is almost always present
755        let has_path = env.iter().any(|(k, _)| k.eq_ignore_ascii_case("PATH"));
756        assert!(
757            has_path || env.len() > 5,
758            "Should have PATH or multiple environment variables"
759        );
760    }
761
762    #[test]
763    #[ignore] // May fail with pseudo-handle - run manually: cargo test -- --ignored
764    fn test_environment_with_buffer() {
765        // Test the buffer-reusable version
766        let current_process = Process::current();
767        let mut buffer = Vec::with_capacity(8192);
768
769        let env = current_process
770            .environment_with_buffer(&mut buffer)
771            .expect("Should read environment with buffer");
772
773        assert!(!env.is_empty(), "Environment should not be empty");
774
775        // Reuse buffer for second call
776        let env2 = current_process
777            .environment_with_buffer(&mut buffer)
778            .expect("Should read environment again");
779
780        assert_eq!(
781            env.len(),
782            env2.len(),
783            "Environment variable count should be consistent"
784        );
785    }
786
787    #[test]
788    #[ignore] // May fail with pseudo-handle - run manually: cargo test -- --ignored
789    fn test_environment_common_variables() {
790        // Check for commonly present environment variables
791        let current_process = Process::current();
792        let env = current_process
793            .environment()
794            .expect("Should read environment variables");
795
796        // At least one of these should be present
797        let common_vars = ["PATH", "TEMP", "TMP", "WINDIR", "SYSTEMROOT", "USERNAME"];
798        let found = common_vars
799            .iter()
800            .any(|var| env.iter().any(|(k, _)| k.eq_ignore_ascii_case(var)));
801
802        assert!(
803            found,
804            "Should find at least one common environment variable (PATH, TEMP, WINDIR, etc.)"
805        );
806    }
807
808    #[test]
809    #[ignore] // May fail with pseudo-handle - run manually: cargo test -- --ignored
810    fn test_environment_values_are_valid_strings() {
811        // Verify all environment values are valid UTF-16 strings
812        let current_process = Process::current();
813        let env = current_process
814            .environment()
815            .expect("Should read environment variables");
816
817        // All keys and values should be non-empty and valid strings
818        for (key, _value) in env.iter() {
819            assert!(
820                !key.is_empty(),
821                "Environment variable key should not be empty"
822            );
823            // Value can be empty (e.g., some vars have empty values)
824            assert!(
825                key.chars().all(|c| c.is_ascii_graphic() || c == '_'),
826                "Environment variable key should contain valid characters"
827            );
828        }
829    }
830}