Skip to main content

winrm_rs/
command.rs

1// Command output and PowerShell encoding utilities.
2//
3// Extracted from client.rs for separation of concerns.
4
5use base64::Engine;
6use base64::engine::general_purpose::STANDARD as B64;
7
8/// Collected output from a completed remote command.
9///
10/// Returned by [`WinrmClient::run_command`](crate::WinrmClient::run_command) and
11/// [`WinrmClient::run_powershell`](crate::WinrmClient::run_powershell).
12/// Streams are raw bytes; use [`String::from_utf8_lossy`] for text conversion.
13#[derive(Debug)]
14pub struct CommandOutput {
15    /// Standard output bytes accumulated from all Receive polls.
16    pub stdout: Vec<u8>,
17    /// Standard error bytes accumulated from all Receive polls.
18    pub stderr: Vec<u8>,
19    /// Process exit code, or `-1` if the server did not report one.
20    pub exit_code: i32,
21}
22
23/// Encode a PowerShell script as UTF-16LE base64 for use with `-EncodedCommand`.
24///
25/// This is the encoding format expected by `powershell.exe -EncodedCommand`.
26/// The input is converted to UTF-16LE and then base64-encoded, which avoids
27/// shell quoting and character escaping issues.
28pub fn encode_powershell_command(script: &str) -> String {
29    let mut utf16 = Vec::with_capacity(script.len() * 2);
30    for c in script.encode_utf16() {
31        utf16.extend_from_slice(&c.to_le_bytes());
32    }
33    B64.encode(&utf16)
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn encode_powershell_roundtrip() {
42        let script = "Get-Process";
43        let encoded = encode_powershell_command(script);
44
45        // Decode and verify
46        let decoded_bytes = B64.decode(&encoded).unwrap();
47        let u16s: Vec<u16> = decoded_bytes
48            .chunks_exact(2)
49            .map(|c| u16::from_le_bytes([c[0], c[1]]))
50            .collect();
51        let decoded = String::from_utf16(&u16s).unwrap();
52        assert_eq!(decoded, script);
53    }
54
55    #[test]
56    fn encode_powershell_unicode() {
57        // Test with non-ASCII characters
58        let script = "Write-Output 'h\u{00e9}llo w\u{00f6}rld'";
59        let encoded = encode_powershell_command(script);
60        let decoded_bytes = B64.decode(&encoded).unwrap();
61        let u16s: Vec<u16> = decoded_bytes
62            .chunks_exact(2)
63            .map(|c| u16::from_le_bytes([c[0], c[1]]))
64            .collect();
65        let decoded = String::from_utf16(&u16s).unwrap();
66        assert_eq!(decoded, script);
67    }
68
69    #[test]
70    fn encode_powershell_empty() {
71        let encoded = encode_powershell_command("");
72        // base64 of empty UTF-16LE is empty string, but it should not panic
73        assert!(encoded.is_empty());
74    }
75}