This article was originally posted to my old WordPress blog. The content is still relevant but some details may have changed.

In a previous article, I presented a PowerShell script for inspecting and validating certificates stored as PFX files. My goal is to get data into an X509Certificate2 object so that I can validate the certificate properties. The X509Certificate2 Import() methods have two sets variations. One set takes a filename for the certificate file to be imported. The second set takes a Byte array containing the certificate data to be imported.

In this script, I can import PFX certificate files by downloading a byte stream from a web server or by reading a file stored on disk. I want to avoid creating temporary files and I want to make a generic import function that could be used independently of the data retrieval method. I settled on using an array of bytes as the import format for either scenario.

To import a PFX file from disk I use the Get-Content cmdlet. Let’s take a closer look at how Get-Content works and what it returns.

PS C:\temp> $pfxbytes = Get-Content .\DEV113.pfx
PS C:\temp> $pfxbytes.GetType().Name
Object[]
PS C:\temp> $pfxbytes[0].GetType().Name
String
PS C:\temp> $pfxbytes[0].length
18
PS C:\temp> $pfxbytes.length
70
PS C:\temp> $pfxbytes | ForEach-Object { $count += $_.length }
PS C:\temp> $count
7489
PS C:\temp> Get-ChildItem .\DEV113.pfx

Directory: C:\temp

Mode              LastWriteTime     Length Name
----              -------------     ------ ----
-a---       9/30/2014   1:03 PM       7558 DEV113.pfx

By default, we see that Get-Content returns an array of String objects. There are two problems with this for my use case.

  1. If you add up the length of all 70 strings you get a total of 7489 characters. But the files size is 7558 bytes, so this does not match. The data in a PFX files is not string-oriented. It is binary data.
  2. I need a Byte array to import the data into an X509Certificate2 object.

Fortunately, using the Encoding parameter you can specify that you want Byte encoded data returned instead of strings.

PS C:\temp> $pfxbytes = Get-Content .\DEV113.pfx -Encoding Byte
PS C:\temp> $pfxbytes.GetType().Name
Object[]
PS C:\temp> $pfxbytes[0].GetType().Name
Byte
PS C:\temp> $pfxbytes[0].length
1
PS C:\temp> $pfxbytes.length
7558

Notice that Get-Content still returns an array of objects but those objects are Bytes. The total length of $pfxbytes now matches the size on disk. To download the PFX file from the web server I am using the System.Net.Webclient class. System.Net.Webclient has three main ways of downloading content from a web server:

  • The DownloadString methods are useful when you are only expecting to receive text data (e.g. HTML, XML, or JSON). Since the PFX file format is binary, not text, this will not work as I have already shown above with Get-Content.
  • The DownloadFile methods would work except that I don’t want to have to save the file to disk as required by these methods.
  • The DownloadData methods return a byte array containing the data requested. This is the method that best meets our needs.

But what is a Byte array? How is a Byte array different than a string?

A byte array can contain any arbitrary binary data. The data does not have to be character data. Character data is subject to interpretation. Character data implies encoding. There is more than one way to encode a character. Take the following example:

PS C:\temp> $string = 'Hello World'
PS C:\temp> $string.length
11
PS C:\temp> $bytes = [System.Text.Encoding]::Unicode.GetBytes($string)
PS C:\temp> $bytes.length
22

As you can see, the length of $string is 11 characters. If we convert that to a byte[] we get 22 bytes of data. It is also important to know the format of the source data when you are converting between encoding schemes. Take for example:

PS C:\temp> $array = @(72,101,108,108,111,32,87,111,114,108,100)
PS C:\temp> $string = [System.Text.Encoding]::UTF8.GetString($array)
PS C:\temp> $string.length
11
PS C:\temp> $string
Hello World

You see it is possible to convert the byte[] $array to a UTF8 encoded string because each byte represents one character. However, if you try to convert that same array to Unicode it will treat each pair of bytes as a single character.

PS C:\temp> $string = [System.Text.Encoding]::Unicode.GetString($array)
PS C:\temp> $string.length
6
PS C:\temp> $string
??????

The result is an unreadable value stored in $string.