Wednesday, April 18, 2018

Formatting Output in KB, MB, GB, TB ....

One of the things that bugged me about PowerShell since I first worked with it was its penchant to give me File directory listings with the file length in bytes. DIR in the CMD shell did it for me.

The textbook answer, of course, is to simply divide the output by 1KB, 1MB, 1GB  as these constants are set in the environment. The obvious problem with this is that it makes the assumption you know what size you're going to get. or that you want the answer in all the same units. But what if you have limited space to display the info, and you don't want to put  23B in the same column with 1293834556 in the same column.

So recently I set about to find a way to have a function that would auto-scale the Byte prefix. I played with the various number types available and put it to the test to see how high I could go with the prefixes. A list I got from Wikipedia.

Experimenting, I worked out an algorithm for this, and it should work cross-platform.
I will update this when I hear of it being tested in something other than Windows Power Shell (WPS). I created these 2 functions, one to work with KB and the second to work with standard SI 1000 units. The second is just a bonus from figuring out the byte calculations.

I added a few features to the function to make it more flexible in use. I added the "-digits" switch so you could control how many decimal places you got on the output. and I also added the "-Units" switch so that you could change from the default "B" to Bytes or whatever fits your need.

I then wracked my brain with what to call it.
Having watched SAO – Ordinal Scale. On Blu-ray with my wife recently, I looked up Ordinal and it seems to fit. Prefix would also work, but that may have other implications.

While I was tweaking it for this article I thought I could make a minor change and have it do the same autoscale for regular 1000 based items. So this could also be used to deal with anything from Dollars to  Miles or anything you have to describe a large number of.

If you new to functions, you can incorporate this code into your script, or you can load it into memory.  This is called Dot sourcing. Google 'Dot source PowerShell function'

Convert-kBOrdinal.ps1

<#  
    .NOTES
    ===========================================================================
     Created with:  SAPIEN Technologies, Inc., PowerShell Studio 2018 v5.5.150
     Created by:    Rich Stoddart
     Filename:     Convert-kBOrdinal.ps1    
    ===========================================================================
    .DESCRIPTION
        Functions to format output by standard Metric Prefixs for binary (1024) 
        and decimal (1000) units. 
        Created to format data concisely on reports, 
        this will process things to as large a number you cn old in a [decimal] 
        type. 
    .EXAMPLE
        gci |% { New-Object -TypeName PSObject -Property ( @{Length = ($_.Length | Convert-kBOrdinal) ; Name = $_.Name}})
        (gi .\install.exe ).length | Convert-kBOrdinal
#>

function Convert-kBOrdinal
{
    param (
        [Parameter(ValueFromPipeline, Position = 0, Mandatory = $true)]
        [decimal]$Num,
        [Parameter(Position = 1, Mandatory = $false)]
        [int]$digits = 2,
        [Parameter(Position = 2, Mandatory = $false)]
        [string]$Units = "B"
    )
    
    if ($NUM -lt 1E+28)
    {
        [int]$Ordinal = ([math]::Floor((($Num.ToString()).Length - 1)/ 3))
        [string]$Digit = [math]::Round($Num / [math]::pow(1024, $Ordinal), $digits)
        [string]$Suffix = "$(((' KMGTPEZYB').Substring($Ordinal, 1)).Trim())$Units"
    }
    Else { "OverRange" }
    return "$Digit$Suffix"
}

function Convert-kOrdinal
{
    param (
        [Parameter(ValueFromPipeline, Position = 0, Mandatory = $true)]
        [decimal]$Num,
        [Parameter(Position = 1, Mandatory = $false)]
        [int]$digits = 2,
        [Parameter(Position = 2, Mandatory = $false)]
        [string]$Units
    )
    
    if ($NUM -lt 1E+28)
    {
        [int]$Ordinal = ([math]::Floor((($Num.ToString()).Length - 1)/ 3))
        [string]$Digit = [math]::Round($Num / [math]::pow(1000, $Ordinal), $digits)
        [string]$Suffix = "$(((' KMGTPEZYB').Substring($Ordinal, 1)).Trim())$Units"
    }
    Else { "OverRange" }
    return "$Digit$Suffix"
}

No comments:

Post a Comment