At one of my recent classes, while discussing hashtables and calculated properties I showed an example of how to list the file’s size in kilobytes instead of the default bytes size.
This can be accomplished with {$_.Length/1kb} as the expression in the property’s hash, but then the output can be… not too pretty… and from some reason, the folders have a non-zero size:
PS> dir | Select-Object -Property Mode, LastWriteTime, @{N=’SizeInKb’;E={$_.Length/1kb}}, Name
Mode LastWriteTime SizeInKb Name
—- ————- ——– —-
d—– 20/05/2017 20:10:51 0.0009765625 Logs
-ar— 03/01/2017 15:58:42 3.041015625 differentfile.exe
-ar— 30/11/2016 09:40:47 0.2705078125 file.txt
-a—- 21/03/2017 19:46:16 12044 large.msi
-a—- 21/03/2017 19:46:18 2902.8388671875 medium.pkg
-a—- 20/05/2017 21:02:19 1.2568359375 otherfile.txt
A possible solution to this, can be truncating some numbers after the decimal point, and then casting it to a double to still be able to sort the files by their size:
PS> dir | Select-Object -Property Mode, LastWriteTime, @{N=’SizeInKb’;E={[double](‘{0:N2}’ -f ($_.Length/1kb))}}, Name | Sort-Object -Property SizeInKb
Mode LastWriteTime SizeInKb Name
—- ————- ——– —-
d—– 20/05/2017 20:10:51 0 Logs
-ar— 30/11/2016 09:40:47 0.27 file.txt
-a—- 20/05/2017 21:02:19 1.26 otherfile.txt
-ar— 03/01/2017 15:58:42 3.04 differentfile.exe
-a—- 21/03/2017 19:46:18 2902.84 medium.pkg
-a—- 21/03/2017 19:46:16 12044 large.msi
But why only in kilobytes? Why not have the size display in human readable format?
So this brought me to write the Get-FriendlySize function:
function Get-FriendlySize {
param($Bytes)
$sizes=‘Bytes,KB,MB,GB,TB,PB,EB,ZB’ -split ‘,’
for($i=0; ($Bytes -ge 1kb) -and
($i -lt $sizes.Count); $i++) {$Bytes/=1kb}
$N=2; if($i -eq 0) {$N=0}
“{0:N$($N)} {1}” -f $Bytes, $sizes[$i]
}
So I could call it with the Select-Object cmdlet:
PS> dir | Select-Object -Property Mode, LastWriteTime, @{N=’FriendlySize’;E={Get-FriendlySize -Bytes $_.Length}}, Name
Mode LastWriteTime FriendlySize Name
—- ————- ———— —-
d—– 20/05/2017 20:10:51 1 Bytes Logs
-ar— 03/01/2017 15:58:42 3.04 KB differentfile.exe
-ar— 30/11/2016 09:40:47 277 Bytes file.txt
-a—- 21/03/2017 19:46:16 11.76 MB large.msi
-a—- 21/03/2017 19:46:18 2.83 MB medium.pkg
-a—- 20/05/2017 21:02:19 1.26 KB otherfile.txt
But wait! Now I can’t sort them. The FriendlySize is just a string:
PS> dir | Select-Object -Property Mode, LastWriteTime, @{N=’FriendlySize’;E={Get-FriendlySize -Bytes $_.Length}}, Name | Sort-Object -Property SizeInKb
Mode LastWriteTime FriendlySize Name
—- ————- ———— —-
d—– 20/05/2017 20:10:51 1 Bytes Logs
-ar— 03/01/2017 15:58:42 3.04 KB differentfile.exe
-ar— 30/11/2016 09:40:47 277 Bytes file.txt
-a—- 21/03/2017 19:46:16 11.76 MB large.msi
-a—- 21/03/2017 19:46:18 2.83 MB medium.pkg
-a—- 20/05/2017 21:02:19 1.26 KB otherfile.txt
Then it occurred me, why not just override the ToString method on the Length’s FileInfo object property when it’s displayed and leave the original length’s value as it was?
This can be accomplished by changing the format data for the object.
So I wrote some lines of code that read the default FileSystem.format.ps1xml file and change the property length to a scriptblock, and update the format data with the Update-FormatData cmdlet:
(Note the double dollar sings before the under-score ($$_) in the here-string, this is to avoid the substitution with the match group)
$file = ‘{0}myTypes.ps1xml’ -f ([System.IO.Path]::GetTempPath())
$data = Get-Content -Path $PSHOMEFileSystem.format.ps1xml
$data -replace ‘<PropertyName>Length</PropertyName>’, @’
<ScriptBlock>
if($$_ -is [System.IO.FileInfo]) {
$this=$$_.Length; $sizes=’Bytes,KB,MB,GB,TB,PB,EB,ZB’ -split ‘,’
for($i=0; ($this -ge 1kb) -and ($i -lt $sizes.Count); $i++) {$this/=1kb}
$N=2; if($i -eq 0) {$N=0}
“{0:N$($N)} {1}” -f $this, $sizes[$i]
} else { $null }
</ScriptBlock>
‘@ | Set-Content -Path $file
Update-FormatData -PrependPath $file
After running the code, I can simply list the files and get their friendly sizes with sorting and everything!
PS> dir | Sort-Object -Property Length
Directory: C:TestDirectory
Mode LastWriteTime Length Name
—- ————- —— —-
-ar— 30/11/2016 09:40 277 Bytes file.txt
-a—- 20/05/2017 21:02 1.26 KB otherfile.txt
-ar— 03/01/2017 15:58 3.04 KB differentfile.exe
-a—- 21/03/2017 19:46 2.83 MB medium.pkg
-a—- 21/03/2017 19:46 11.76 MB large.msi
d—– 20/05/2017 20:10 Logs
If you want to always have this, put the new myTypes.ps1xml file somewhere on your drive, and add the call to Update-FormatData -PrependPath $file your profile.
HTH,
Martin.