Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
@@ -0,0 +1,509 @@
|
||||
<#
|
||||
.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
|
||||
}
|
||||
Reference in New Issue
Block a user