lib/core/tools/Convert-IcingaPluginThresholds.psm1

<#
.SYNOPSIS
    Converts any kind of Icinga threshold with provided units
    to the lowest base of the unit which makes sense. It does
    support the Icinga plugin language, like ~:30, @10:40, 15:30,
    ...
 
    The conversion does currently support the following units:
 
    Size: B, KB, MB, GB, TB, PT, KiB, MiB, GiB, TiB, PiB
    Time: ms, s, m, h, d w, M, y
.DESCRIPTION
    Converts any kind of Icinga threshold with provided units
    to the lowest base of the unit. It does support the Icinga
    plugin language, like ~:30, @10:40, 15:30, ...
 
    The conversion does currently support the following units:
 
    Size: B, KB, MB, GB, TB, PT, KiB, MiB, GiB, TiB, PiB
    Time: ms, s, m, h, d w, M, y
.FUNCTIONALITY
    Converts values with units to the lowest unit of this category.
    Accepts Icinga Thresholds.
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '20d';
 
    Name Value
    ---- -----
    Value 1728000
    Unit s
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '5GB';
 
    Name Value
    ---- -----
    Value 5000000000
    Unit B
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '10MB:20MB';
 
    Name Value
    ---- -----
    Value 10000000:20000000
    Unit B
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '10m:1h';
 
    Name Value
    ---- -----
    Value 600:3600
    Unit s
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '@10m:1h';
 
    Name Value
    ---- -----
    Value @600:3600
    Unit s
.EXAMPLE
    Convert-IcingaPluginThresholds -Threshold '~:1M';
 
    Name Value
    ---- -----
    Value ~:2592000
    Unit s
.INPUTS
   System.String
.OUTPUTS
    System.Hashtable
.LINK
   https://github.com/Icinga/icinga-powershell-framework
#>


function Convert-IcingaPluginThresholds()
{
    param (
        [string]$Threshold = $null
    );

    [hashtable]$RetValue = @{
        'Unit'  = '';
        'Value' =  $null;
    };

    if ($null -eq $Threshold) {
        return $RetValue;
    }

    # Always ensure we are using correct digits
    $Threshold = $Threshold.Replace(',', '.');

    [array]$Content    = @();

    if ($Threshold.Contains(':')) {
        # If we have more than one ':' inside our string, lets check if this is a date time value
        # In case it is convert it properly to a FileTime we can work with later on
        if ([Regex]::Matches($Threshold, ":").Count -gt 1) {
            try {
                $DateTimeValue  = [DateTime]::ParseExact($Threshold, 'yyyy\/MM\/dd HH:mm:ss', $null);
                $RetValue.Value = $DateTimeValue.ToFileTime();
                $RetValue.Unit  = 's';
            } catch {
                $RetValue.Value = $Threshold;
            }

            return $RetValue;
        }

        $Content = $Threshold.Split(':');
    } else {
        $Content += $Threshold;
    }

    [array]$ConvertedValue = @();

    foreach ($ThresholdValue in $Content) {

        [bool]$HasTilde = $FALSE;
        [bool]$HasAt    = $FALSE;
        [bool]$Negate   = $FALSE;
        $Value          = '';
        $WorkUnit       = '';

        if ($ThresholdValue.Contains('~')) {
            $ThresholdValue = $ThresholdValue.Replace('~', '');
            $HasTilde = $TRUE;
        } elseif ($ThresholdValue.Contains('@')) {
            $HasAt = $TRUE;
            $ThresholdValue = $ThresholdValue.Replace('@', '');
        }

        if ($ThresholdValue[0] -eq '-' -And $ThresholdValue.Length -ge 1) {
            $Negate         = $TRUE;
            $ThresholdValue = $ThresholdValue.Substring(1, $ThresholdValue.Length - 1);
        }

        If (($ThresholdValue -Match "(^[\d\.]*) ?(B|KB|MB|GB|TB|PT|KiB|MiB|GiB|TiB|PiB)")) {
            $WorkUnit = 'B';
            if ([string]::IsNullOrEmpty($RetValue.Unit) -eq $FALSE -And $RetValue.Unit -ne $WorkUnit) {
                Exit-IcingaThrowException -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.MultipleUnitUsage -Force;
            }
            $Value         = (Convert-Bytes -Value $ThresholdValue -Unit $WorkUnit).Value;
            $RetValue.Unit = $WorkUnit;
        } elseif (($ThresholdValue -Match "(^[\d\.]*) ?(ms|s|m|h|d|w|M|y)")) {
            $WorkUnit = 's';
            if ([string]::IsNullOrEmpty($RetValue.Unit) -eq $FALSE -And $RetValue.Unit -ne $WorkUnit) {
                Exit-IcingaThrowException -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.MultipleUnitUsage -Force;
            }
            $Value         = (ConvertTo-Seconds -Value $ThresholdValue);
            $RetValue.Unit = $WorkUnit;
        } elseif (($ThresholdValue -Match "(^[\d\.]*) ?(%)")) {
            $WorkUnit      = '%';
            $Value         = ([string]$ThresholdValue).Replace(' ', '').Replace('%', '');
            $RetValue.Unit = $WorkUnit;
        } else {
            # Load all other units/values generically
            [string]$StrNumeric = '';
            [bool]$FirstChar    = $TRUE;
            [bool]$Delimiter    = $FALSE;
            foreach ($entry in ([string]($ThresholdValue)).ToCharArray()) {
                if ((Test-Numeric $entry) -Or ($entry -eq '.' -And $Delimiter -eq $FALSE)) {
                    $StrNumeric += $entry;
                    $FirstChar   = $FALSE;
                    if ($entry -eq '.') {
                        $Delimiter = $TRUE;
                    }
                } else {
                    if ([string]::IsNullOrEmpty($RetValue.Unit) -And $FirstChar -eq $FALSE) {
                        $RetValue.Unit = $entry;
                    } else {
                        $StrNumeric    = '';
                        $RetValue.Unit = '';
                        break;
                    }
                }
            }
            if ([string]::IsNullOrEmpty($StrNumeric)) {
                $Value = $ThresholdValue;
            } else {
                $Value = [decimal]$StrNumeric;
            }
        }

        if ((Test-Numeric $Value) -And $Negate) {
            $Value = $Value * -1;
        } elseif ($Negate) {
            $Value = [string]::Format('-{0}', $Value);
        }

        if ($HasTilde) {
            $ConvertedValue += [string]::Format('~{0}', $Value);
        } elseif ($HasAt) {
            $ConvertedValue += [string]::Format('@{0}', $Value);
        } else {
            $ConvertedValue += $Value;
        }
    }

    [string]$Value = [string]::Join(':', $ConvertedValue);

    if ([string]::IsNullOrEmpty($Value) -eq $FALSE -And $Value.Contains(':') -eq $FALSE) {
        if ((Test-Numeric $Value)) {
            $RetValue.Value = [decimal]$Value;
            return $RetValue;
        }
    }

    # Always ensure we are using correct digits
    $Value = ([string]$Value).Replace(',', '.');
    $RetValue.Value = $Value;

    return $RetValue;
}