Formatting byte size numbers with PowerShell

6 minute read

For this week, I’m going to share another classic for me, my Format-Bytes function. This is one of those helper functions I’ve got that I scoff at because it is so simple, but I miss it whenever I need it.

The problem

Ever used PowerShell to look at the size of a file? Say an Exchange ISO file?

Get-Item $exchangeIso

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----          4/2/2019  4:16 PM     5161132032 Exchange.ISO

Can you glance at that number and tell me how big that is? Since it is an Exchange installation ISO file, I can assume that the 5 at the front means that it is either 500MB or 5GB. Though if I spent some more time thinking about that, I’d probably guess 5GB.

Adding commas

If we put commas in that number, it would make it easier to read:

"{0:n0}" -f 5161132032

5,161,132,032

But even that could be made easier to understand.

A solution

With PowerShell, we can take a number representing a size in bytes, divide it by the right number of 1000s, and add the proper suffix rather easily!

Determining the size

Did you know that you can create a number that is 1024 by using 1KB? This means that we can make easier to understand comparisons to find out how big a number really is:

$num = 5161132032
if ($num -lt 1KB) {
    # format as B
} elseif ($num -lt 1MB) {
    # format as KB
} elseif ($num -lt 1GB) {
    # format as MB
} elseif ($num -lt 1TB) {
    # format as GB
}

In this example, it would format the number as GB.

Formatting the number

But how would we go about doing so? This is where we can use the format operator in PowerShell. The official docs are a little sparse and reference .NET framework stuff which isn’t all that useful to most PowerShell users, so I’d recommend checking out the SS64 version which explains the :n syntax that I’ll be using.

The short version is that using {0:n} will format a number and you can specify decimal places by placing a number after like: {0:n2}.

Using our Exchange ISO file example from before:

"{0:n0}" -f 5161132032

5,161,132,032

# Then with 2 decimals:
"{0:n2}" -f 5161132032

5,161,132,032.00

Combining them

And when we combine both the size and the format, we end up with something like:

$num = 5161132032
if ($num -lt 1TB) {
    $num = $num / 1GB
    $num = "{0:n2}" -f $num
    "$num GB"
}

4.81 GB

And we can confirm that by looking at the properties of the file itself:

Exchange ISO Properties

So it is a bit smaller than the 5GB I originally estimated.

To function or not to function

If you’ve read any of my other posts, you know that there is a good chance I’ll be functionizing this. Well, you are right! But I want to show another piece of code as well:

Preamble

So with this function, you probably noticed all the if/elseif statements from earlier. Meaning that if I wanted to add petabytes, exabytes, zettabytes, or yottabytes I’d have to add an elseif statement for each. Which is way too difficult.

Instead I would suggest starting with an array of the sizes, and then iterating through that:

$number = 5161132032
$sizes = 'KB','MB','GB','TB','PB'
for($x = 0;$x -lt $sizes.count; $x++){
    if ($number -lt [int64]"1$($sizes[$x])"){
        if ($x -eq 0){
            return "$number B"
        } else {
            $num = $number / [int64]"1$($sizes[$x-1])"
            $num = "{0:N2}" -f $num
            return "$num $($sizes[$x-1])"
        }
    }
}

While this is harder to read, it makes it a cinch to add in additional sizes. Though, as I just discovered, PowerShell only supports up to petabytes. So I’ve limited the above script to what is actually supported.

The function

Function Format-Bytes {
    Param
    (
        [Parameter(
            ValueFromPipeline = $true
        )]
        [ValidateNotNullOrEmpty()]
        [float]$number
    )
    Begin{
        $sizes = 'KB','MB','GB','TB','PB'
    }
    Process {
        # New for loop
        for($x = 0;$x -lt $sizes.count; $x++){
            if ($number -lt [int64]"1$($sizes[$x])"){
                if ($x -eq 0){
                    return "$number B"
                } else {
                    $num = $number / [int64]"1$($sizes[$x-1])"
                    $num = "{0:N2}" -f $num
                    return "$num $($sizes[$x-1])"
                }
            }
        }
        <# Original way
        if ($number -lt 1KB) {
            return "$number B"
        } elseif ($number -lt 1MB) {
            $number = $number / 1KB
            $number = "{0:N2}" -f $number
            return "$number KB"
        } elseif ($number -lt 1GB) {
            $number = $number / 1MB
            $number = "{0:N2}" -f $number
            return "$number MB"
        } elseif ($number -lt 1TB) {
            $number = $number / 1GB
            $number = "{0:N2}" -f $number
            return "$number GB"
        } elseif ($number -lt 1PB) {
            $number = $number / 1TB
            $number = "{0:N2}" -f $number
            return "$number TB"
        } else {
            $number = $number / 1PB
            $number = "{0:N2}" -f $number
            return "$number PB"
        }
        #>
    }
    End{}
}

For the sake of explanation, I’ve included both methods in the script. With the if/elseif/else statements just commented out.

Usage

Using this function is pretty straight forward. You can give it a number:

Format-Bytes 1234567890

1.15 GB

Or pass one through the pipeline:

9876543210 | Format-Bytes

9.20 GB

Or you can integrate it into some output from Get-ChildItem:

Get-ChildItem -File | Select-Object Name,@{
    Name = 'Size'
    Expression = {Format-Bytes $_.Length}
}

Name           Size
----           ----
.gitattributes 66 B
LICENSE        1.04 KB
README.md      1.07 KB

But keep in mind that this is simple a string format of the number. If you took that number and tried any comparison or arithmetic operators, they would treat it as a string.

Conclusion

I hope you find this useful! But I also hope it inspires you to build even simple tools such as this one. Its quite amazing how many times I’ve just wanted to see a formatted version of a long byte number.

If you’ve got any feedback you can comment here, tweet at me, open an issue in my Utilities repo, or shoot me an email. Whatever you prefer.

Thanks for reading!

Leave a Comment