<#
.SYNOPSIS
    Microsoft Deployment Toolkit Prometheus Metrics Exporter
.DESCRIPTION
    Prometheus exporter for MDT - active deployments, completed/failed task
    sequences, deployment duration, OS image count, task sequence count,
    application count, driver count, driver package count, boot image count,
    deployment share size. Exports metrics as Prometheus-compatible text format
    for the windows_exporter textfile collector.
.PARAMETER TextfilePath
    Full path to the .prom output file (default: C:\metrics\mdt.prom)
.PARAMETER DeploymentShare
    UNC or local path to the MDT deployment share
.PARAMETER MDTDatabase
    Path to MDT monitoring database file (optional)
.PARAMETER InstallScheduledTask
    Switch to create a scheduled task for periodic collection
.PARAMETER TaskIntervalMinutes
    Interval in minutes for the scheduled task (default: 5)
.NOTES
    Author:  Phil Connor
    Contact: contact@mylinux.work
    Website: https://mylinux.work
    License: MIT
    Version: 1.0.0

    Metrics Exported:
      Status:
        - mdt_up
        - mdt_info{version}

      Deployments:
        - mdt_deployment_active
        - mdt_deployment_completed_total
        - mdt_deployment_failed_total
        - mdt_deployment_duration_seconds_min
        - mdt_deployment_duration_seconds_max
        - mdt_deployment_duration_seconds_avg

      Inventory:
        - mdt_os_image_count
        - mdt_task_sequence_count
        - mdt_application_count
        - mdt_driver_count
        - mdt_driver_package_count

      Boot Images:
        - mdt_boot_image_count
        - mdt_boot_image_size_bytes

      Capacity:
        - mdt_deployment_share_size_bytes

      Exporter:
        - mdt_exporter_duration_seconds
        - mdt_exporter_last_run_timestamp
#>

param(
    [string]$TextfilePath = 'C:\metrics\mdt.prom',

    [string]$DeploymentShare = '\\localhost\DeploymentShare$',

    [string]$MDTDatabase = '',

    [switch]$InstallScheduledTask,

    [int]$TaskIntervalMinutes = 5
)

# ============================================================================
# SCHEDULED TASK INSTALLATION
# ============================================================================

if ($InstallScheduledTask) {
    $taskName = "MdtMetricsExporter"
    $existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue

    if (-not $existingTask) {
        $scriptPath = $MyInvocation.MyCommand.Path
        $taskAction = New-ScheduledTaskAction -Execute "powershell.exe" `
            -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" -DeploymentShare `"$DeploymentShare`""

        if (-not $TaskIntervalMinutes -or $TaskIntervalMinutes -le 0) {
            throw "TaskIntervalMinutes must be a positive integer"
        }

        $taskTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) `
            -RepetitionInterval (New-TimeSpan -Minutes $TaskIntervalMinutes) `
            -RepetitionDuration (New-TimeSpan -Days 365)
        $taskPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest

        try {
            Write-Host "Creating scheduled task: $taskName"
            Register-ScheduledTask -TaskName $taskName -Action $taskAction `
                -Trigger $taskTrigger -Principal $taskPrincipal `
                -Description "Exports MDT metrics for Prometheus every $TaskIntervalMinutes minutes"

            $createdTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
            if (-not $createdTask) {
                throw "Failed to verify scheduled task creation"
            }
            Write-Host "Successfully created scheduled task: $taskName" -ForegroundColor Green
        } catch {
            Write-Error "Failed to create scheduled task: $($_.Exception.Message)"
            throw
        }
    } else {
        Write-Host "Scheduled task '$taskName' already exists, skipping creation"
    }
    return
}

$ErrorActionPreference = 'SilentlyContinue'
$VERSION = '1.0.0'

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================

function Get-UnixTimestamp {
    [int][double]::Parse((Get-Date -UFormat '%s'))
}

function Format-MetricValue {
    param([double]$Value, [int]$Decimals = 2)
    [math]::Round($Value, $Decimals)
}

function Resolve-SharePath {
    param([string]$SharePath)
    if (Test-Path $SharePath) {
        return (Resolve-Path $SharePath).Path
    }
    return $SharePath
}

# ============================================================================
# DEPLOYMENT SHARE INVENTORY METRICS
# ============================================================================

function Get-InventoryMetrics {
    param([string]$ShareRoot)

    $sb = [System.Text.StringBuilder]::new()

    # --- mdt_os_image_count ---
    [void]$sb.AppendLine('# HELP mdt_os_image_count Number of OS images in the deployment share')
    [void]$sb.AppendLine('# TYPE mdt_os_image_count gauge')
    try {
        $osPath = Join-Path $ShareRoot 'Operating Systems'
        if (Test-Path $osPath) {
            $wimFiles = Get-ChildItem -Path $osPath -Recurse -File -Filter '*.wim' -ErrorAction Stop
            $osCount = ($wimFiles | Measure-Object).Count
        } else {
            $osCount = 0
        }
        [void]$sb.AppendLine("mdt_os_image_count $osCount")
    } catch {
        [void]$sb.AppendLine("mdt_os_image_count 0")
    }
    [void]$sb.AppendLine('')

    # --- mdt_task_sequence_count ---
    [void]$sb.AppendLine('# HELP mdt_task_sequence_count Number of task sequences defined')
    [void]$sb.AppendLine('# TYPE mdt_task_sequence_count gauge')
    try {
        $tsPath = Join-Path $ShareRoot 'Control'
        if (Test-Path $tsPath) {
            $tsFolders = Get-ChildItem -Path $tsPath -Directory -ErrorAction Stop |
                Where-Object { Test-Path (Join-Path $_.FullName 'ts.xml') }
            $tsCount = ($tsFolders | Measure-Object).Count
        } else {
            $tsCount = 0
        }
        [void]$sb.AppendLine("mdt_task_sequence_count $tsCount")
    } catch {
        [void]$sb.AppendLine("mdt_task_sequence_count 0")
    }
    [void]$sb.AppendLine('')

    # --- mdt_application_count ---
    [void]$sb.AppendLine('# HELP mdt_application_count Number of applications in the deployment share')
    [void]$sb.AppendLine('# TYPE mdt_application_count gauge')
    try {
        $appPath = Join-Path $ShareRoot 'Applications'
        if (Test-Path $appPath) {
            $appFolders = Get-ChildItem -Path $appPath -Directory -ErrorAction Stop
            $appCount = ($appFolders | Measure-Object).Count
        } else {
            $appCount = 0
        }
        [void]$sb.AppendLine("mdt_application_count $appCount")
    } catch {
        [void]$sb.AppendLine("mdt_application_count 0")
    }
    [void]$sb.AppendLine('')

    # --- mdt_driver_count ---
    [void]$sb.AppendLine('# HELP mdt_driver_count Number of individual drivers imported')
    [void]$sb.AppendLine('# TYPE mdt_driver_count gauge')
    try {
        $driverPath = Join-Path $ShareRoot 'Out-of-Box Drivers'
        if (Test-Path $driverPath) {
            $infFiles = Get-ChildItem -Path $driverPath -Recurse -File -Filter '*.inf' -ErrorAction Stop
            $driverCount = ($infFiles | Measure-Object).Count
        } else {
            $driverCount = 0
        }
        [void]$sb.AppendLine("mdt_driver_count $driverCount")
    } catch {
        [void]$sb.AppendLine("mdt_driver_count 0")
    }
    [void]$sb.AppendLine('')

    # --- mdt_driver_package_count ---
    [void]$sb.AppendLine('# HELP mdt_driver_package_count Number of driver package folders')
    [void]$sb.AppendLine('# TYPE mdt_driver_package_count gauge')
    try {
        $driverPath = Join-Path $ShareRoot 'Out-of-Box Drivers'
        if (Test-Path $driverPath) {
            $driverPkgs = Get-ChildItem -Path $driverPath -Directory -ErrorAction Stop
            $driverPkgCount = ($driverPkgs | Measure-Object).Count
        } else {
            $driverPkgCount = 0
        }
        [void]$sb.AppendLine("mdt_driver_package_count $driverPkgCount")
    } catch {
        [void]$sb.AppendLine("mdt_driver_package_count 0")
    }
    [void]$sb.AppendLine('')

    $sb.ToString()
}

# ============================================================================
# BOOT IMAGE METRICS
# ============================================================================

function Get-BootImageMetrics {
    param([string]$ShareRoot)

    $sb = [System.Text.StringBuilder]::new()

    # --- mdt_boot_image_count ---
    [void]$sb.AppendLine('# HELP mdt_boot_image_count Number of boot images (WIM and ISO)')
    [void]$sb.AppendLine('# TYPE mdt_boot_image_count gauge')
    $bootCount = 0
    $bootSizeTotal = 0
    try {
        $bootPath = Join-Path $ShareRoot 'Boot'
        if (Test-Path $bootPath) {
            $bootFiles = Get-ChildItem -Path $bootPath -File -ErrorAction Stop |
                Where-Object { $_.Extension -in '.wim', '.iso' }
            $bootCount = ($bootFiles | Measure-Object).Count
            $bootSizeTotal = ($bootFiles | Measure-Object -Property Length -Sum).Sum
            if (-not $bootSizeTotal) { $bootSizeTotal = 0 }
        }
    } catch {
        $bootCount = 0
        $bootSizeTotal = 0
    }
    [void]$sb.AppendLine("mdt_boot_image_count $bootCount")
    [void]$sb.AppendLine('')

    # --- mdt_boot_image_size_bytes ---
    [void]$sb.AppendLine('# HELP mdt_boot_image_size_bytes Total size of boot images in bytes')
    [void]$sb.AppendLine('# TYPE mdt_boot_image_size_bytes gauge')
    [void]$sb.AppendLine("mdt_boot_image_size_bytes $bootSizeTotal")
    [void]$sb.AppendLine('')

    $sb.ToString()
}

# ============================================================================
# DEPLOYMENT SHARE SIZE
# ============================================================================

function Get-ShareSizeMetrics {
    param([string]$ShareRoot)

    $sb = [System.Text.StringBuilder]::new()

    # --- mdt_deployment_share_size_bytes ---
    [void]$sb.AppendLine('# HELP mdt_deployment_share_size_bytes Total deployment share size in bytes')
    [void]$sb.AppendLine('# TYPE mdt_deployment_share_size_bytes gauge')
    try {
        if (Test-Path $ShareRoot) {
            $totalSize = (Get-ChildItem -Path $ShareRoot -Recurse -File -ErrorAction Stop |
                Measure-Object -Property Length -Sum).Sum
            if (-not $totalSize) { $totalSize = 0 }
        } else {
            $totalSize = 0
        }
        [void]$sb.AppendLine("mdt_deployment_share_size_bytes $totalSize")
    } catch {
        [void]$sb.AppendLine("mdt_deployment_share_size_bytes 0")
    }
    [void]$sb.AppendLine('')

    $sb.ToString()
}

# ============================================================================
# MDT MONITORING / DEPLOYMENT STATUS METRICS
# ============================================================================

function Get-DeploymentMetrics {
    $sb = [System.Text.StringBuilder]::new()

    $activeCount = 0
    $completedCount = 0
    $failedCount = 0
    $durations = @()
    $monitorAvailable = $false

    # Attempt to load MDT PowerShell module and query monitoring data
    try {
        $mdtModule = Get-Module -ListAvailable -Name MicrosoftDeploymentToolkit -ErrorAction Stop
        if ($mdtModule) {
            Import-Module MicrosoftDeploymentToolkit -ErrorAction Stop
            $monitorData = Get-MDTMonitorData -Path "DS001:" -ErrorAction Stop
            $monitorAvailable = $true

            foreach ($entry in $monitorData) {
                switch ($entry.DeploymentStatus) {
                    1 { $activeCount++
                        if ($entry.StartTime) {
                            $elapsed = ((Get-Date) - [datetime]$entry.StartTime).TotalSeconds
                            $durations += $elapsed
                        }
                    }
                    2 { $completedCount++ }
                    3 { $failedCount++ }
                }
            }
        }
    } catch {
        $monitorAvailable = $false
    }

    # If MDT module not available, try reading monitoring database directly
    if (-not $monitorAvailable -and $MDTDatabase -and (Test-Path $MDTDatabase)) {
        try {
            $connectionString = "Data Source=$MDTDatabase;Version=3;"
            $connection = New-Object System.Data.SQLite.SQLiteConnection($connectionString)
            $connection.Open()

            $cmd = $connection.CreateCommand()
            $cmd.CommandText = "SELECT DeploymentStatus, StartTime FROM MonitorData"
            $reader = $cmd.ExecuteReader()

            while ($reader.Read()) {
                $status = $reader['DeploymentStatus']
                switch ($status) {
                    1 {
                        $activeCount++
                        $startStr = $reader['StartTime']
                        if ($startStr) {
                            $elapsed = ((Get-Date) - [datetime]$startStr).TotalSeconds
                            $durations += $elapsed
                        }
                    }
                    2 { $completedCount++ }
                    3 { $failedCount++ }
                }
            }

            $reader.Close()
            $connection.Close()
            $monitorAvailable = $true
        } catch {
            $monitorAvailable = $false
        }
    }

    # --- mdt_deployment_active ---
    [void]$sb.AppendLine('# HELP mdt_deployment_active Number of currently active MDT deployments')
    [void]$sb.AppendLine('# TYPE mdt_deployment_active gauge')
    [void]$sb.AppendLine("mdt_deployment_active $activeCount")
    [void]$sb.AppendLine('')

    # --- mdt_deployment_completed_total ---
    [void]$sb.AppendLine('# HELP mdt_deployment_completed_total Total completed MDT deployments')
    [void]$sb.AppendLine('# TYPE mdt_deployment_completed_total counter')
    [void]$sb.AppendLine("mdt_deployment_completed_total $completedCount")
    [void]$sb.AppendLine('')

    # --- mdt_deployment_failed_total ---
    [void]$sb.AppendLine('# HELP mdt_deployment_failed_total Total failed MDT deployments')
    [void]$sb.AppendLine('# TYPE mdt_deployment_failed_total counter')
    [void]$sb.AppendLine("mdt_deployment_failed_total $failedCount")
    [void]$sb.AppendLine('')

    # --- deployment duration stats ---
    if ($durations.Count -gt 0) {
        $minDuration = Format-MetricValue ($durations | Measure-Object -Minimum).Minimum
        $maxDuration = Format-MetricValue ($durations | Measure-Object -Maximum).Maximum
        $avgDuration = Format-MetricValue ($durations | Measure-Object -Average).Average
    } else {
        $minDuration = 0
        $maxDuration = 0
        $avgDuration = 0
    }

    [void]$sb.AppendLine('# HELP mdt_deployment_duration_seconds_min Shortest active deployment duration')
    [void]$sb.AppendLine('# TYPE mdt_deployment_duration_seconds_min gauge')
    [void]$sb.AppendLine("mdt_deployment_duration_seconds_min $minDuration")
    [void]$sb.AppendLine('')

    [void]$sb.AppendLine('# HELP mdt_deployment_duration_seconds_max Longest active deployment duration')
    [void]$sb.AppendLine('# TYPE mdt_deployment_duration_seconds_max gauge')
    [void]$sb.AppendLine("mdt_deployment_duration_seconds_max $maxDuration")
    [void]$sb.AppendLine('')

    [void]$sb.AppendLine('# HELP mdt_deployment_duration_seconds_avg Average active deployment duration')
    [void]$sb.AppendLine('# TYPE mdt_deployment_duration_seconds_avg gauge')
    [void]$sb.AppendLine("mdt_deployment_duration_seconds_avg $avgDuration")
    [void]$sb.AppendLine('')

    $sb.ToString()
}

# ============================================================================
# COLLECT ALL METRICS
# ============================================================================

function Get-AllMetrics {
    $scriptStart = Get-Date
    $sb = [System.Text.StringBuilder]::new()

    $shareRoot = Resolve-SharePath $DeploymentShare

    # --- mdt_up ---
    [void]$sb.AppendLine('# HELP mdt_up MDT exporter status (1=share accessible, 0=error)')
    [void]$sb.AppendLine('# TYPE mdt_up gauge')
    if (Test-Path $shareRoot) {
        [void]$sb.AppendLine("mdt_up 1")
    } else {
        [void]$sb.AppendLine("mdt_up 0")
    }
    [void]$sb.AppendLine('')

    # --- mdt_info ---
    [void]$sb.AppendLine('# HELP mdt_info MDT exporter version information')
    [void]$sb.AppendLine('# TYPE mdt_info gauge')
    [void]$sb.AppendLine("mdt_info{version=`"$VERSION`"} 1")
    [void]$sb.AppendLine('')

    # Deployment status metrics
    [void]$sb.Append((Get-DeploymentMetrics))

    # Inventory metrics
    [void]$sb.Append((Get-InventoryMetrics -ShareRoot $shareRoot))

    # Boot image metrics
    [void]$sb.Append((Get-BootImageMetrics -ShareRoot $shareRoot))

    # Share size metrics
    [void]$sb.Append((Get-ShareSizeMetrics -ShareRoot $shareRoot))

    # --- exporter duration ---
    $scriptEnd = Get-Date
    $duration = Format-MetricValue ($scriptEnd - $scriptStart).TotalSeconds
    $timestamp = Get-UnixTimestamp

    [void]$sb.AppendLine('# HELP mdt_exporter_duration_seconds Time to generate all metrics')
    [void]$sb.AppendLine('# TYPE mdt_exporter_duration_seconds gauge')
    [void]$sb.AppendLine("mdt_exporter_duration_seconds $duration")
    [void]$sb.AppendLine('')

    [void]$sb.AppendLine('# HELP mdt_exporter_last_run_timestamp Unix timestamp of last successful run')
    [void]$sb.AppendLine('# TYPE mdt_exporter_last_run_timestamp gauge')
    [void]$sb.AppendLine("mdt_exporter_last_run_timestamp $timestamp")
    [void]$sb.AppendLine('')

    $sb.ToString()
}

# ============================================================================
# MAIN EXECUTION - TEXTFILE OUTPUT
# ============================================================================

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

$tempFile = Join-Path $outputDir ".mdt_metrics.$PID.tmp"

try {
    $metrics = Get-AllMetrics
    $metrics | Out-File -FilePath $tempFile -Encoding utf8 -NoNewline

    $lineCount = ($metrics -split "`n").Count
    if ($lineCount -lt 10) {
        Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue
        Write-Error "Metrics file too small ($lineCount lines), keeping previous"
        exit 1
    }

    Move-Item -Path $tempFile -Destination $TextfilePath -Force
    Write-Host "Metrics written to $TextfilePath ($lineCount lines)" -ForegroundColor Green
}
catch {
    Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue
    Write-Error "Failed to generate metrics: $_"
    exit 1
}
