37

I have a script for testing an API which returns a base64 encoded image. My current solution is this.

$e = (curl.exe -H "Content-Type: multipart/form-data" -F "[email protected]" localhost:5000)
$decoded = [System.Convert]::FromBase64CharArray($e, 0, $e.Length)
[io.file]::WriteAllBytes('out.png', $decoded) # <--- line in question

Get-Content('out.png') | Format-Hex

This works, but I would like to be able to write the byte array natively in PowerShell, without having to source from [io.file].

My attempts of writing $decoded in PowerShell all resulted in writing a string-encoded list of the integer byte values. (e.g.)

42
125
230
12
34
...

How do you do this properly?

6
  • 13
    You're already doing it properly. [IO.File]::WriteAllBytes() is the correct way of writing bytes to a file in PowerShell. Commented Aug 6, 2015 at 12:46
  • Why exactly do you want not to write "natively" in Powershell ? PS is more or less built on top of .Net, so using .Net methods is perfectly fine and standard. Commented Aug 6, 2015 at 12:53
  • 2
    Not sure, so feel free to correct me, but I think your question may have been more properly phrased as something along the lines of How do you do this with PowerShell Cmdlets? -- my ~28¢ Commented Jun 1, 2016 at 13:09
  • 2
    I think "idiomatically" is the adjective you're looking for, rather than "natively". Commented Dec 12, 2018 at 23:08
  • 2
    @thomasb I know this is old, but the reason some people want to write without IO.File in powershell is that using IO.File does not work in Powershell restricted mode, which is required in some high security environments. The accepted answer below will work in restricted mode, though. Commented Dec 13, 2019 at 21:46

5 Answers 5

54

The Set-Content cmdlet lets you write raw bytes to a file by using the Byte encoding:

$decoded = [System.Convert]::FromBase64CharArray($e, 0, $e.Length)
Set-Content out.png -Value $decoded -Encoding Byte

(However, BACON points out in a comment that Set-Content is slow: in a test, it took 10 seconds, and 360 MB of RAM, to write 10 MB of data. So, I recommend not using Set-Content if that's too slow for your application.)

Sign up to request clarification or add additional context in comments.

9 Comments

An idiomatic (and high-level) solution.
Why is it so slow? It takes more than 2 minutes to write 14 megabytes.
@Sasha, it's slow because 14 million bytes are being passed via the pipeline, one byte at a time. Good details about pipeline performance at stackoverflow.com/questions/34113755/…
@jimhark With that in mind it seems this answer should include, if not recommend, writing it as Set-Content out.png -Value $decoded -Encoding Byte. A quick benchmark of the two invocations with a 10 MB array using Measure-Command and Task Manager gives 3 min/+5.5 GB RAM for $byteArray | Set-Content ... -Encoding Byte vs. 10 sec/+360 MB RAM for Set-Content ... -Value $byteArray -Encoding Byte in PS5.1; a big improvement, but still terrible. Using -AsByteStream on PS7 makes the pipeline 45 seconds faster, but no difference with -Value. I'd say avoid Set-Content, if possible.
@TannerSwett, -Encoding Byte is replaced with -AsByteStream in PowerShell ≥6.
|
22

Powershell Core (v6 and above) no longer have -Encoding byte as an option, so you'll need to use -AsByteStream, e.g:

Set-Content -Path C:\temp\test.jpg -AsByteStream

1 Comment

-Value parameter is missed. The correct command is: Set-Content -Path 'out.png' -Value $decoded -AsByteStream;
17

Running C# assemblies is native to PowerShell, therefore you are already writing bytes to a file "natively".

If you insist, you can use a construction like set-content test.jpg -value (([char[]]$decoded) -join ""), this has a drawback of adding #13#10 to the end of written data. With JPEGs it's bearable, but other files may get corrupt from this alteration. So please stick with byte-optimized routines of .NET instead of searching for "native" approaches - these are already native.

Comments

2

for Powershell 6 or above

# $byteData = Get-Content -Path $filePath -AsByteStream -Raw # slow
$byteData = Get-Content -Path $filePath -AsByteStream -ReadCount 0 -Raw | foreach { $_ }
# $byteData | Set-Content -Path $outputFile -AsByteStream -NoNewline # slow
Set-Content -Path $outputFile -Value $byteData -AsByteStream -NoNewline

for powershell 5

$byteData = Get-Content -Path $filePath -Encoding Byte -ReadCount 0 -Raw | foreach { $_ }
Set-Content -Path $outputFile -Value $byteData -Encoding Byte -NoNewline

Comments

0

What I used recently while extracting an exe from a zip file

    $newFile = New-Item -Path "C:\Temp" -Name "xyz.exe"  # -Force if overriding

    try {
        # open a writable FileStream
        $fileStream = $newFile.OpenWrite()

        # create stream writer
        $streamWriter = [System.IO.BinaryWriter]::new($fileStream)

        # write to stream
        $streamWriter.Write($SomeExecutableData)
    }
    finally {
        # clean up
        $streamWriter.Dispose()
        $fileStream.Dispose()
        # if the file is nastily big, [GC]::Collect()
    }

There might be a more modern method than [System.IO.BinaryWriter]::new($fileStream), I'm too lazy to look it up at the moment but might edit later.

Most of the credit goes to: https://stackoverflow.com/a/70595622/3875151

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.