How to get original binary data from external comm

2019-08-23 02:35发布

I have read here that when you run external commands in powershell, their output is always interpreted as a string or string array: https://stackoverflow.com/a/35980675/983442

I'm trying to process binary output from an external command, but it seems like PowerShell can only give me strings.

This leaves me wondering, what encoding is used to convert the binary data into strings? And also, how does it interpret newlines in order to divide the binary data into a string array? It seems to be splitting on the \n character alone, but I'm sure it would also split on \r\n.

Is there even a reliable way to take the strings powershell gives me and turn them back into a byte array?

For example, let's say I have a batch file with the following contents, call it thing.bat:

@echo off
type image.jpg

I then run the following powershell:

PS> $x = & .\thing.bat
PS> $x.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS> $x[0].gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object


PS> $x.count
36

How can I reliably recreate this image.jpg in PowerShell once I have the $x variable?

1条回答
家丑人穷心不美
2楼-- · 2019-08-23 03:35

PowerShell assumes, that every external program called by you provides only strings over it's output stream. While this is not so far from reality, one might want to get the real bytes from an external program. To achieve that, we will create a new process "from scratch"

$procInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{
    FileName = "cmd.exe"
    Arguments = "thing.bat"
    RedirectStandardError = $true
    RedirectStandardOutput = $true
    UseShellExecute = $false
}
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $procInfo
$proc.Start() | Out-Null
$proc.WaitForExit()

Which provides us a StreamReader for StandardOutput and StandardError, when the respective Redirect properties are set to $true.

Now to get the stream's content we could easily use ReadToEnd() like $outContent = $proc.StandardOutput.ReadToEnd(), but that would give us just a string again.

A StreamReader gives us the following methods (amongst others):

Read             Method   int Read(), int Read(char[] buffer, int index, int count)
ReadAsync        Method   System.Threading.Tasks.Task[int] ReadAsync(char[] buffer, int index, int count)
ReadBlock        Method   int ReadBlock(char[] buffer, int index, int count)
ReadBlockAsync   Method   System.Threading.Tasks.Task[int] ReadBlockAsync(char[] buffer, int index, int count)
ReadLine         Method   string ReadLine()
ReadLineAsync    Method   System.Threading.Tasks.Task[string] ReadLineAsync()
ReadToEnd        Method   string ReadToEnd()
ReadToEndAsync   Method   System.Threading.Tasks.Task[string] ReadToEndAsync()

Just create and pass a char[] buffer to Read() and use it like you want:

$length = $proc.StandardOutput.Length
$s = New-Object 'char[]' $length
$proc.StandardOutput.Read($s, 0, $length - 1)

A second - easier but less flexible solution:

If you don't have a problem writing files to disk you could easily redirect the program's standard output to a file with -Encoding Oem and read it in again with Get-Content:

& .\thing.bat | Out-File -FilePath "C:/tmp/out.txt" -Encoding Oem
$rawContent = Get-Content -Path "C:/tmp/out.txt" -Encoding Oem
查看更多
登录 后发表回答