4

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?

Nacht
  • 3,342
  • 4
  • 26
  • 41
  • care to explain the down vote? – Nacht Oct 26 '17 at 06:57
  • I could imagine that the downvoter wanted to see some example code. Also this seems to be the kind of questions every user should answer on hisself by just trying around. However I think it's an useful question, but also would like to see some example command with it's output. – Clijsters Oct 26 '17 at 08:00
  • you better add an example, so we can test it... – Avshalom Oct 26 '17 at 10:44
  • @Clijsters I have edited my question. – Nacht Oct 26 '17 at 11:19
  • One thing would be to redirect programs output to a file (which in _this specific case_ is quite useless) using `Out-File` (Where you can set the `-Encdoding` and read it back in via e.g. `Get-Content -Encoding OEM`) – Clijsters Oct 26 '17 at 11:45
  • @Clijsters I'm asking this question because I'm unable to do that. Is it really not possible to directly deal with binary streams of data from executables? What kind of a shell can't do that? – Nacht Oct 26 '17 at 13:04
  • Sure it is. I'll write an answer – Clijsters Oct 26 '17 at 13:21

1 Answers1

1

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
Clijsters
  • 4,031
  • 1
  • 27
  • 37
  • I suppose this does answer my question as it stands but it doesn't actually help me. I have a third-party program calling my powershell script and passing binary data to it. The image was just a placeholder for testing. – Nacht Nov 20 '17 at 05:34
  • If the 3rd party program calls your script and not vice versa, an image is not a good placeholder. Can you tell us the exact command line called by this program? – Clijsters Nov 20 '17 at 06:43
  • I can control the command line called by this program - so I pass it "powershell.exe -file script.ps1" and it passes an image in through stdin. The third party program is openssh. I am attempting to use a command (ForceCommand) option in my authorized_keys file on windows. – Nacht Nov 21 '17 at 02:12
  • I should just write my own executable to save stdin to disk. – Nacht Nov 21 '17 at 02:13
  • I think I will end up using these cmdlets to accomplish what I need: https://www.powershellgallery.com/packages/Use-RawPipeline/1.2 – Nacht Nov 21 '17 at 02:25
  • _native utilities invoked from PowerShell._ which is again the wrong direction. Isn't it? Consider adding a new question. This one is about _get original data from external command_. Eventually some might find a similiar one to your other question. – Clijsters Nov 21 '17 at 07:39
  • yeah i think a new question will have to do. if this doesnt work – Nacht Nov 21 '17 at 09:46