Public/Test-tpcAvailableCounterConfig.ps1

function Test-tpcAvailableCounterConfig {
    <#
    .SYNOPSIS
 
    .DESCRIPTION
        Validates and lists available tpc counter configurations from all registered paths.
 
    .PARAMETER configFilePath
        Path to a single configuration file. If omitted, all configured paths are scanned.
 
    .PARAMETER Raw
        Returns PSCustomObject[] instead of formatted console output.
 
    .EXAMPLE
        Test-tpcAvailableCounterConfig
 
        Validates all configurations found in registered paths and displays formatted results.
 
    .EXAMPLE
        Test-tpcAvailableCounterConfig -configFilePath "C:\configs\tpc_CPU.json"
 
        Validates a single configuration file.
 
    .EXAMPLE
        Test-tpcAvailableCounterConfig -Raw | Where-Object { -not $_.JsonValid }
 
        Returns raw objects and filters for invalid JSON configurations.
    #>


    [CmdletBinding()]
    [OutputType([System.Collections.Generic.List[PSCustomObject]])]
    param (
        [Parameter()]
        [string] $configFilePath,

        [Parameter()]
        [switch] $Raw
    )

    try {

        $skipSchemaValidation   = $false
        $isSingleFile           = $false
        $AllResults             = [System.Collections.Generic.List[PSCustomObject]]::new() # wenn irgendwann mal 100te Configs da sind, ist besser
        $ConfigNamesFound       = @{}
        $localCounterMap        = Get-CounterMap


        if ( -not (Test-Path $script:JSON_SCHEMA_CONFIG_FILE) ) {
            Write-Warning "Central schema file not found at: $script:JSON_SCHEMA_CONFIG_FILE. Skipping schema validation."
            $skipSchemaValidation = $true
        } else {
            $configSchema = Get-Content $script:JSON_SCHEMA_CONFIG_FILE -Raw
        }

        if ( $PSBoundParameters.ContainsKey('configFilePath') ) {
            $ConfigPaths    = @($configFilePath)
            $isSingleFile   = $true
        } else {
            $ConfigPaths    = @(Get-tpcConfigPaths)
        }

        if ( $ConfigPaths.Count -eq 0 -and -not $isSingleFile ) {
            Write-Warning "No configuration paths found. Use Add-tpcConfigPath to add configuration directories."
            return [System.Collections.Generic.List[PSCustomObject]]::new()
        }


        foreach ( $ConfigPath in $ConfigPaths ) {

            $PathResults = [System.Collections.Generic.List[PSCustomObject]]::new()

            if ( -not (Test-Path $ConfigPath) ) {
                Write-Warning "Configuration directory / file not found: $ConfigPath"
                continue
            }

            if ( -not $isSingleFile ) {

                $ConfigFiles = @(Get-ChildItem -Path $ConfigPath -Filter "tpc_*.json" -File | Where-Object { $_.BaseName -notlike "*template*" })

                if ( $ConfigFiles.Count -eq 0 ) {
                    Write-Verbose "No configuration files found with 'tpc_' prefix in: $ConfigPath"
                    continue
                }

            } else {

                $ConfigFiles = Get-Item $ConfigPath

            }

            foreach ( $ConfigFile in $ConfigFiles ) {

                try {

                    $ConfigName    = $ConfigFile.BaseName -replace '^tpc_', ''

                    # Track duplicate configurations, 1st action for all files

                    $ConfigNameLower = $ConfigName.ToLower()

                    if ( $ConfigNamesFound.ContainsKey($ConfigNameLower) ) {
                        $ConfigNamesFound[$ConfigNameLower] += 1
                    } else {
                        $ConfigNamesFound[$ConfigNameLower] = 1
                    }

                    $JsonContent        = Get-Content -Path $ConfigFile.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -AsHashtable
                    $mergedJsonContent  = Merge-JsonConfigDefaultValues -CounterConfig $JsonContent
                    $rawJson            = $mergedJsonContent | ConvertTo-Json -Depth 10
                    $isEmpty            = [string]::IsNullOrWhiteSpace($rawJson)

                    # Determine if this is a duplicate
                    $IsDuplicate = $ConfigNamesFound[$ConfigNameLower] -gt 1

                    $SchemaValidation = @{IsValid = $true; Errors = @()}
                    $validationErrors = @()

                    if ( -not $isEmpty -and -not $skipSchemaValidation) {

                        try {

                            $isValid = Test-Json -Json $rawJson -Schema $configSchema -ErrorAction SilentlyContinue -ErrorVariable validationErrors

                            $SchemaValidation['IsValid']    = $isValid
                            $SchemaValidation['Errors']     = if ($validationErrors.Count -gt 0) {
                                @($validationErrors | ForEach-Object { $_.Exception.Message -replace '^.*?:\s', '' })
                            } else { @() }

                        } catch {

                            $SchemaValidation.IsValid     = $false
                            $SchemaValidation.Errors      = @("Schema validation failed: $($_.Exception.Message)")
                        }

                    }

                    $CounterDetails = [System.Collections.Generic.List[PSCustomObject]]::new()

                    if ( -not $isEmpty ) {

                        foreach ( $CounterConfig in $mergedJsonContent.counters ) {

                        try {

                            $Counter = [psTPCCLASSES.CounterConfiguration]::new(
                                $CounterConfig.counterID,
                                $CounterConfig.counterSetType,
                                $CounterConfig.counterInstance,
                                $CounterConfig.title,
                                $CounterConfig.format,
                                $CounterConfig.unit,
                                $CounterConfig.conversionFactor,
                                $CounterConfig.conversionExponent,
                                $CounterConfig.conversionType,
                                $CounterConfig.decimalPlaces,
                                $CounterConfig.colorMap,
                                $CounterConfig.graphConfiguration,
                                $false,
                                "",
                                $NULL,
                                $localCounterMap
                            )

                            $CounterDetail = [PSCustomObject]@{
                                Title           = $Counter.Title
                                CounterId       = $Counter.counterID
                                CounterPath     = $Counter.CounterPath
                                Unit            = $Counter.Unit
                                Format          = $Counter.Format
                                InstancePath    = $Counter.CounterPath
                                Valid           = $true
                                ErrorMessage    = $null
                            }

                            $CounterDetails.Add($CounterDetail)

                        } catch {

                            $CounterDetail = [PSCustomObject]@{
                                Title          = $CounterConfig.title
                                CounterId      = $CounterConfig.counterID
                                CounterPath    = "Invalid"
                                Unit           = $CounterConfig.unit
                                Format         = $CounterConfig.format
                                Valid          = $false
                                ErrorMessage   = $_.Exception.Message
                                InstancePath   = $($_.Exception.Message)
                            }

                            $CounterDetails.Add($CounterDetail)
                        }

                    }
                }

                    $ConfigOverview = [PSCustomObject]@{
                        ConfigName               = $ConfigName
                        ConfigPath               = $ConfigPath
                        Description              = if ($isEmpty) { "Error: Empty configuration file" } else { $mergedJsonContent.description }
                        ConfigFile               = $ConfigFile.FullName
                        JsonValid                = if ($isEmpty) { $false } else { $SchemaValidation.IsValid }
                        JsonValidationErrors     = if ($isEmpty) { @("Configuration file is empty or contains only whitespace") } else { $SchemaValidation.Errors }
                        CounterCount             = $CounterDetails.Count
                        Counters                 = $CounterDetails
                        IsDuplicate              = $IsDuplicate
                    }

                    $PathResults.Add($ConfigOverview)

                } catch {

                    Write-Error "Error processing configuration file '$($ConfigFile.Name)': $($_.Exception.Message)"

                    # Determine if this is a duplicate (also for error configs)
                    $ConfigNameLower = ($ConfigFile.BaseName -replace '^tpc_', '').ToLower()
                    $IsDuplicate = $ConfigNamesFound[$ConfigNameLower] -gt 1

                    $ErrorConfig = [PSCustomObject]@{
                        ConfigName               = $ConfigFile.BaseName -replace '^tpc_', ''
                        ConfigPath               = $ConfigPath
                        Description              = "Error loading configuration"
                        ConfigFile               = $ConfigFile.FullName
                        JsonValid                = $false
                        JsonValidationErrors     = @($_.Exception.Message)
                        CounterCount             = 0
                        Counters                 = @()
                        IsDuplicate              = $IsDuplicate
                    }

                    $PathResults.Add($ErrorConfig)

                }

            }

        # Add path results to overall results
        if ( $PathResults.Count -gt 0 ) {
            $AllResults.AddRange($PathResults)
        }
    }

    # Identify duplicates for summary
    $duplicateNames = @($ConfigNamesFound.Keys | Where-Object { $ConfigNamesFound[$_] -gt 1 })

        if ( $Raw ) {

            return $AllResults

        } else {

            # Group by path
            $groupedResults = $AllResults | Group-Object -Property ConfigPath

            foreach ( $pathGroup in $groupedResults ) {
                Write-Host "`nConfiguration Path: $($pathGroup.Name)" -ForegroundColor Cyan
                $separatorLine = "=" * $( $pathGroup.Name.Length + 22 )
                Write-Host $separatorLine -ForegroundColor Cyan

                foreach ( $result in $pathGroup.Group ) {

                        $configDisplay = $result.ConfigName
                        if ( $result.IsDuplicate ) {
                            $configDisplay += " [DUPLICATE]"
                        }
                        $configDisplay += " ($([System.IO.Path]::GetFileName($result.ConfigFile)))"

                        $separatorLine2 = "." * $( $pathGroup.Name.Length + 22 )
                        Write-Host ""
                        Write-Host $separatorLine2 -ForegroundColor Gray
                        Write-Host "`n$configDisplay" -ForegroundColor $(if ($result.IsDuplicate) { "Yellow" } else { "Green" })
                        Write-Host "Description: $($result.Description)" -ForegroundColor Gray

                        $JsonStatus    = if ( $result.JsonValid )    { "Valid JSON Schema" } else { "Invalid JSON Schema" }

                        if ( $result.JsonValid ) {
                            Write-Host "$JsonStatus" -ForegroundColor Green
                        } else {
                            Write-Host "$JsonStatus" -ForegroundColor Red
                            Write-Host "Schema Validation Errors:" -ForegroundColor Yellow
                            ForEach ( $errorMessage in $result.JsonValidationErrors ) {
                                Write-Host " - $errorMessage" -ForegroundColor Red
                            }
                        }

                        if ( $result.IsDuplicate ) {
                            Write-Warning "This configuration exists in multiple paths. Consider removing duplicates."
                        }

                        if ( $result.Counters.Count -gt 0)  {
                            Write-Host "Counters:" -ForegroundColor Gray
                            $result.Counters | Format-Table Title, CounterId, Unit, Format, InstancePath -AutoSize -Wrap
                        } else {
                            Write-Host "No counters found" -ForegroundColor Yellow
                        }
                }
            }

            # Show summary of duplicates if any found
            if ( $duplicateNames.Count -gt 0 ) {
                Write-Host "`nDUPLICATE CONFIGURATIONS DETECTED:" -ForegroundColor Red
                $separatorLine = "=" * 35
                Write-Host $separatorLine -ForegroundColor Red
                foreach ( $dupName in $duplicateNames ) {
                    $dupConfigs = @($AllResults | Where-Object { $_.ConfigName.ToLower() -eq $dupName })
                    Write-Host "`n'$dupName' found in:" -ForegroundColor Yellow
                    foreach ( $dupConfig in $dupConfigs ) {
                        Write-Host "- $($dupConfig.ConfigPath) ($([System.IO.Path]::GetFileName($dupConfig.ConfigFile)))" -ForegroundColor Gray
                    }
                }
                Write-Host "`nConsider removing duplicate configurations to avoid conflicts." -ForegroundColor Yellow
            }

        }

    } catch {

        Write-Error "Error in Test-tpcAvailableCounterConfig: $($_.Exception.Message)"

    }

}