Private/Paths.ps1

#Requires -Version 5.1

function Get-SMBeatDefaultDataRoot {
    $pd = $env:ProgramData
    if ([string]::IsNullOrWhiteSpace($pd)) {
        if (-not [string]::IsNullOrWhiteSpace($env:HOME)) {
            return (Join-Path $env:HOME '.smbeat')
        }
        return (Join-Path ([System.IO.Path]::GetTempPath()) 'SMBeat')
    }

    Join-Path $pd 'SMBeat'
}

function Get-SMBeatDefaultReportRoot {
    $pd = $env:ProgramData
    if ([string]::IsNullOrWhiteSpace($pd)) {
        if (-not [string]::IsNullOrWhiteSpace($env:HOME)) {
            return (Join-Path $env:HOME '.smbeat-reports')
        }
        return (Join-Path ([System.IO.Path]::GetTempPath()) 'SMBeat-Reports')
    }

    Join-Path $pd 'SMBeat-Reports'
}

function Get-SMBeatDataRoot {
    param(
        [string]$Path,
        [string]$ComputerName
    )

    $root = $Path
    if ([string]::IsNullOrWhiteSpace($root)) {
        $root = Get-SMBeatDefaultDataRoot
    }

    if (-not [string]::IsNullOrWhiteSpace($ComputerName)) {
        $root = Join-Path $root $ComputerName
    }

    return $root
}

function Get-SMBeatSampleDirectory {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'samples'
}

function Get-SMBeatSampleFilePath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot,
        [Parameter(Mandatory = $true)]
        [datetime]$Utc
    )

    $day = $Utc.ToString('yyyy-MM-dd', $script:SMBeatInvariant)
    Join-Path (Get-SMBeatSampleDirectory -DataRoot $DataRoot) ($day + '.jsonl')
}

function Get-SMBeatStatePath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'state.json'
}

function Get-SMBeatHeartbeatPath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'heartbeat.json'
}

function Get-SMBeatConfigPath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'config.json'
}

function Get-SMBeatLogPath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'collector.log'
}

function Get-SMBeatEventsPath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'events.jsonl'
}

function Get-SMBeatBootStatePath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'boot.json'
}

function Get-SMBeatStopRequestPath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'stop.request'
}

function Get-SMBeatFlushRequestPath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'flush.request'
}

function Get-SMBeatCollectorScriptPath {
    Join-Path $script:SMBeatModuleRoot 'Scripts\Invoke-SMBeatCollector.ps1'
}

function Get-SMBeatPowerShellExePath {
    Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
}

function Get-SMBeatModuleVersion {
    $mod = Get-Module -Name SMBeat -ErrorAction SilentlyContinue
    if ($mod -and $mod.Version) {
        return $mod.Version.ToString()
    }

    $psd1 = Join-Path $script:SMBeatModuleRoot 'SMBeat.psd1'
    if (Test-Path -LiteralPath $psd1) {
        $data = Import-PowerShellDataFile -Path $psd1 -ErrorAction SilentlyContinue
        if ($data -and $data.ModuleVersion) {
            return [string]$data.ModuleVersion
        }
    }

    return 'unknown'
}

function Get-SMBeatInstallDestination {
    $pf = $env:ProgramFiles
    if ([string]::IsNullOrWhiteSpace($pf)) {
        return ''
    }

    Join-Path $pf 'WindowsPowerShell\Modules\SMBeat'
}

function Get-SMBeatModuleCopyNames {
    @('SMBeat.psd1', 'SMBeat.psm1', 'Public', 'Private', 'Scripts', 'en-US', 'LICENSE')
}

function Copy-SMBeatModuleToDestination {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Destination,
        [string]$SourceRoot
    )

    if ([string]::IsNullOrWhiteSpace($SourceRoot)) {
        $SourceRoot = $script:SMBeatModuleRoot
    }

    if ([string]::IsNullOrWhiteSpace($Destination)) {
        throw 'SMBeat install destination is empty.'
    }

    if (-not (Test-Path -LiteralPath $Destination)) {
        New-Item -ItemType Directory -Path $Destination -Force | Out-Null
    }

    foreach ($name in Get-SMBeatModuleCopyNames) {
        $from = Join-Path $SourceRoot $name
        if (Test-Path -LiteralPath $from) {
            Copy-Item -LiteralPath $from -Destination $Destination -Recurse -Force
        }
    }

    return $Destination
}

function Get-SMBeatLatestInstalledVersion {
    $list = @(Get-Module -Name SMBeat -ListAvailable -ErrorAction SilentlyContinue | Sort-Object Version)
    if ($list.Count -gt 0) {
        return $list[$list.Count - 1].Version.ToString()
    }

    return (Get-SMBeatModuleVersion)
}

function Update-SMBeatInstalledModule {
    $methods = New-Object System.Collections.Generic.List[string]
    $dest = Get-SMBeatInstallDestination

    if (Get-Command Update-Module -ErrorAction SilentlyContinue) {
        try {
            Update-Module -Name SMBeat -Force -ErrorAction Stop
            $methods.Add('gallery') | Out-Null
        }
        catch {
        }
    }

    if (-not [string]::IsNullOrWhiteSpace($dest)) {
        try {
            Copy-SMBeatModuleToDestination -Destination $dest | Out-Null
            $methods.Add('copy') | Out-Null
        }
        catch {
        }
    }

    $path = $dest
    if ([string]::IsNullOrWhiteSpace($path)) {
        $path = $script:SMBeatModuleRoot
    }

    [PSCustomObject]@{
        Methods    = $methods.ToArray()
        ModulePath = $path
        Version    = Get-SMBeatLatestInstalledVersion
    }
}

function ConvertTo-SMBeatHashtable {
    param(
        $Object
    )

    $hash = @{}
    if ($null -eq $Object) {
        return $hash
    }

    if ($Object -is [hashtable]) {
        return $Object
    }

    foreach ($prop in $Object.PSObject.Properties) {
        $hash[$prop.Name] = $prop.Value
    }

    return $hash
}

function Get-SMBeatPropertyValue {
    param(
        $Object,
        [Parameter(Mandatory = $true)]
        [string]$Name,
        $Default
    )

    if ($null -eq $Object) {
        return $Default
    }

    if ($Object -is [hashtable]) {
        if ($Object.ContainsKey($Name)) {
            return $Object[$Name]
        }
        return $Default
    }

    $prop = $Object.PSObject.Properties[$Name]
    if ($null -eq $prop) {
        return $Default
    }

    return $prop.Value
}

function Get-SMBeatDefaultConfig {
    [PSCustomObject]@{
        IntervalSec   = $script:SMBeatDefaultIntervalSec
        RetentionDays = $script:SMBeatDefaultRetentionDays
        Path          = Get-SMBeatDefaultDataRoot
        IncludeNics        = $true
        IncludeEtwShares   = $true
        IncludeSmbPerf     = $true
        IncludeAdminShares = $false
        Mode               = 'tcp445+etw'
        MailTo             = ''
        SmtpServer         = ''
        MailFrom           = ''
    }
}

function Read-SMBeatConfig {
    param(
        [string]$DataRoot
    )

    if ([string]::IsNullOrWhiteSpace($DataRoot)) {
        $DataRoot = Get-SMBeatDefaultDataRoot
    }

    $path = Get-SMBeatConfigPath -DataRoot $DataRoot
    $json = Read-SMBeatTextFile -Path $path
    $defaults = Get-SMBeatDefaultConfig
    if ($null -eq $json) {
        return $defaults
    }

    $parsed = ConvertFrom-SMBeatJson -Json $json
    $result = ConvertTo-SMBeatHashtable -Object $defaults
    foreach ($prop in $parsed.PSObject.Properties) {
        $result[$prop.Name] = $prop.Value
    }

    [PSCustomObject]$result
}

function Save-SMBeatConfig {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot,
        [Parameter(Mandatory = $true)]
        $Config
    )

    $path = Get-SMBeatConfigPath -DataRoot $DataRoot
    Write-SMBeatTextFile -Path $path -Text (ConvertTo-SMBeatJson -InputObject $Config)
}