Private/Culture.ps1

#Requires -Version 5.1

function Get-SMBeatUtcNow {
    [DateTime]::UtcNow
}

function ConvertTo-SMBeatUtcString {
    param(
        [Parameter(Mandatory = $true)]
        [datetime]$DateTime
    )

    $utc = $DateTime
    if ($utc.Kind -eq [DateTimeKind]::Local) {
        $utc = $utc.ToUniversalTime()
    }
    elseif ($utc.Kind -eq [DateTimeKind]::Unspecified) {
        $utc = [datetime]::SpecifyKind($utc, [DateTimeKind]::Utc)
    }

    $utc.ToString('yyyy-MM-ddTHH:mm:ssZ', $script:SMBeatInvariant)
}

function ConvertFrom-SMBeatUtcString {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Value
    )

    [datetime]::Parse(
        $Value,
        $script:SMBeatInvariant,
        [System.Globalization.DateTimeStyles]::AdjustToUniversal -bor [System.Globalization.DateTimeStyles]::AssumeUniversal
    )
}

function Get-SMBeatRecordUtc {
    param(
        $Record
    )

    if ($null -eq $Record) {
        return [datetime]::MinValue
    }

    if ($Record.PSObject.Properties['TsUtc'] -and $null -ne $Record.TsUtc) {
        $utc = [datetime]$Record.TsUtc
        if ($utc.Kind -eq [DateTimeKind]::Utc) {
            return $utc
        }
        return [datetime]::SpecifyKind($utc, [DateTimeKind]::Utc)
    }

    if ($Record.PSObject.Properties['ts'] -and $Record.ts) {
        return ConvertFrom-SMBeatUtcString -Value ([string]$Record.ts)
    }

    [datetime]::MinValue
}

function ConvertTo-SMBeatJson {
    param(
        [Parameter(Mandatory = $true)]
        $InputObject
    )

    ConvertTo-Json -InputObject $InputObject -Depth 8 -Compress:$false
}

function ConvertFrom-SMBeatJson {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Json
    )

    ConvertFrom-Json -InputObject $Json
}

function Write-SMBeatTextFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,
        [Parameter(Mandatory = $true)]
        [string]$Text
    )

    $dir = Split-Path -Parent $Path
    if (-not (Test-Path -LiteralPath $dir)) {
        New-Item -ItemType Directory -Path $dir -Force | Out-Null
    }

    [System.IO.File]::WriteAllText($Path, $Text, $script:SMBeatUtf8NoBom)
}

function Add-SMBeatTextFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,
        [Parameter(Mandatory = $true)]
        [string]$Text
    )

    $dir = Split-Path -Parent $Path
    if (-not (Test-Path -LiteralPath $dir)) {
        New-Item -ItemType Directory -Path $dir -Force | Out-Null
    }

    [System.IO.File]::AppendAllText($Path, $Text, $script:SMBeatUtf8NoBom)
}

function Read-SMBeatTextFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    if (-not (Test-Path -LiteralPath $Path)) {
        return $null
    }

    [System.IO.File]::ReadAllText($Path, $script:SMBeatUtf8NoBom)
}

function Get-SMBeatLanguage {
    param(
        [string]$Language
    )

    if (-not [string]::IsNullOrWhiteSpace($Language)) {
        $normalized = $Language.Trim().ToLowerInvariant()
        if ($normalized -eq 'de' -or $normalized.StartsWith('de-')) {
            return 'de'
        }
        return 'en'
    }

    $ui = [string]$PSUICulture
    if ($ui.StartsWith('de', [System.StringComparison]::OrdinalIgnoreCase)) {
        return 'de'
    }
    return 'en'
}

function Resolve-SMBeatTimeZone {
    param(
        [object]$TimeZone
    )

    if ($null -eq $TimeZone -or ($TimeZone -is [string] -and [string]::IsNullOrWhiteSpace([string]$TimeZone))) {
        return [TimeZoneInfo]::Local
    }

    if ($TimeZone -is [TimeZoneInfo]) {
        return $TimeZone
    }

    return [TimeZoneInfo]::FindSystemTimeZoneById([string]$TimeZone)
}

function Convert-SMBeatUtcToTimeZone {
    param(
        [Parameter(Mandatory = $true)]
        [datetime]$Utc,
        [Parameter(Mandatory = $true)]
        [TimeZoneInfo]$TimeZone
    )

    $asUtc = $Utc
    if ($asUtc.Kind -eq [DateTimeKind]::Local) {
        $asUtc = $asUtc.ToUniversalTime()
    }
    else {
        $asUtc = [datetime]::SpecifyKind($asUtc, [DateTimeKind]::Utc)
    }

    [TimeZoneInfo]::ConvertTimeFromUtc($asUtc, $TimeZone)
}

function Get-SMBeatByteDelta {
    param(
        [Parameter(Mandatory = $true)]
        [uint64]$Current,
        [uint64]$Previous
    )

    if ($Current -ge $Previous) {
        return [int64]($Current - $Previous)
    }

    # Counter reset (reboot / instance recycle). Treat current raw as the new total for this interval.
    return [int64]$Current
}

function Get-SMBeatBucketStart {
    param(
        [Parameter(Mandatory = $true)]
        [datetime]$LocalTime,
        [Parameter(Mandatory = $true)]
        [ValidateSet('Hour', 'Day', 'Week')]
        [string]$Granularity
    )

    $kind = $LocalTime.Kind

    switch ($Granularity) {
        'Hour' {
            return New-Object datetime $LocalTime.Year, $LocalTime.Month, $LocalTime.Day, $LocalTime.Hour, 0, 0, $kind
        }
        'Day' {
            return New-Object datetime $LocalTime.Year, $LocalTime.Month, $LocalTime.Day, 0, 0, 0, $kind
        }
        'Week' {
            $date = New-Object datetime $LocalTime.Year, $LocalTime.Month, $LocalTime.Day, 0, 0, 0, $kind
            $offset = ([int]$date.DayOfWeek + 6) % 7
            return $date.AddDays(-1 * $offset)
        }
    }
}

function Get-SMBeatBucketEnd {
    param(
        [Parameter(Mandatory = $true)]
        [datetime]$BucketStart,
        [Parameter(Mandatory = $true)]
        [ValidateSet('Hour', 'Day', 'Week')]
        [string]$Granularity
    )

    switch ($Granularity) {
        'Hour' { return $BucketStart.AddHours(1) }
        'Day'  { return $BucketStart.AddDays(1) }
        'Week' { return $BucketStart.AddDays(7) }
    }
}

function ConvertTo-SMBeatGB {
    param(
        [int64]$Bytes
    )

    [math]::Round(([double]$Bytes / 1000000000.0), 3)
}

function ConvertTo-SMBeatGiB {
    param(
        [int64]$Bytes
    )

    ConvertTo-SMBeatGB -Bytes $Bytes
}

function Get-SMBeatInstanceLeafName {
    param(
        [string]$Name
    )

    if ([string]::IsNullOrWhiteSpace($Name)) {
        return $Name
    }

    $idx = $Name.LastIndexOf('\')
    if ($idx -ge 0 -and $idx -lt ($Name.Length - 1)) {
        return $Name.Substring($idx + 1)
    }

    return $Name
}

function Test-SMBeatAdminShareName {
    param(
        [string]$Name
    )

    if ([string]::IsNullOrWhiteSpace($Name)) {
        return $false
    }

    $leaf = Get-SMBeatInstanceLeafName -Name $Name

    if ($leaf -eq 'IPC$' -or $leaf -eq 'ADMIN$' -or $leaf -eq 'print$') {
        return $true
    }

    if ($leaf.Length -eq 2 -and $leaf.EndsWith('$') -and $leaf[0] -match '[A-Za-z]') {
        return $true
    }

    return $false
}

function Test-SMBeatSkipInstance {
    param(
        [string]$Name,
        [string]$Kind,
        [switch]$IncludeAdminShares
    )

    if ([string]::IsNullOrWhiteSpace($Name)) {
        return $true
    }

    if ($Name -eq '_Total' -or $Name -like '*_Total*') {
        return $true
    }

    $leaf = Get-SMBeatInstanceLeafName -Name $Name

    if (($Kind -eq 'share' -or $Kind -eq 'smbperf') -and $leaf -eq 'IPC$') {
        return $true
    }

    if (($Kind -eq 'share' -or $Kind -eq 'smbperf') -and -not $IncludeAdminShares -and (Test-SMBeatAdminShareName -Name $Name)) {
        return $true
    }

    return $false
}