Formatting byte size numbers with PowerShell
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:
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