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,560 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Windows Update Monitoring Script for Prometheus Windows Exporter (wsus)
|
||||
|
||||
.DESCRIPTION
|
||||
This script monitors Windows Updates and exports metrics in Prometheus format.
|
||||
It checks for available updates, their severity levels, and installation status.
|
||||
Can optionally install updates when specified.
|
||||
|
||||
.PARAMETER MetricNames
|
||||
Custom metric names hashtable for overriding default metric names
|
||||
.PARAMETER AutoInstall
|
||||
Enable automatic installation of available updates
|
||||
.PARAMETER ScheduleDaily
|
||||
Create a scheduled task to run this script daily at 3 AM
|
||||
.PARAMETER MetricsFilePath
|
||||
Path where Prometheus metrics will be written (must be accessible by windows_exporter)
|
||||
|
||||
.NOTES
|
||||
Version: 1.6.2
|
||||
Author: Phil Connor contact@mylinux.work
|
||||
License: MIT
|
||||
|
||||
Features:
|
||||
- Monitors Windows Updates status
|
||||
- Exports metrics in Prometheus format
|
||||
- Supports automatic update installation
|
||||
- Graceful shutdown support
|
||||
- Retry mechanisms and timeout handling
|
||||
- Memory management and garbage collection
|
||||
- Comprehensive logging
|
||||
- Error handling and metrics
|
||||
#>
|
||||
|
||||
param(
|
||||
# Custom metric names hashtable for overriding default metric names
|
||||
[hashtable]$MetricNames,
|
||||
# Enable automatic installation of available updates
|
||||
[switch]$AutoInstall = $false,
|
||||
# Create a scheduled task to run this script daily at 3 AM
|
||||
[switch]$ScheduleDaily,
|
||||
# Validate that the parent directory exists for the metrics file
|
||||
[ValidateScript({Test-Path (Split-Path $_ -Parent) -PathType Container})]
|
||||
# Path where Prometheus metrics will be written (must be accessible by windows_exporter)
|
||||
[string]$MetricsFilePath = "C:\Program Files\windows_exporter\textfile_inputs\updates.prom"
|
||||
)
|
||||
|
||||
# Check if script is already running to prevent multiple instances
|
||||
$scriptName = $MyInvocation.MyCommand.Name
|
||||
$currentProcess = Get-Process -Id $PID
|
||||
$runningInstances = Get-WmiObject Win32_Process |
|
||||
Where-Object { $_.CommandLine -like "*$scriptName*" -and $_.ProcessId -ne $currentProcess.Id }
|
||||
|
||||
if ($runningInstances) {
|
||||
Write-Host "Script is already running (PID: $($runningInstances.ProcessId)). Exiting to prevent conflicts."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Create scheduled task for daily execution at 3 AM
|
||||
if ($ScheduleDaily -eq $true) {
|
||||
$taskName = "WindowsUpdateMonitoring"
|
||||
# Check if the scheduled task already exists to avoid duplicates
|
||||
$existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $existingTask) {
|
||||
# Define the action: run PowerShell with this script
|
||||
$taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`""
|
||||
# Set the trigger to run daily at 3 AM
|
||||
$taskTrigger = New-ScheduledTaskTrigger -Daily -At 3AM
|
||||
# Run as SYSTEM account with highest privileges for update operations
|
||||
$taskPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
||||
|
||||
try {
|
||||
Write-Host "Creating scheduled task: $taskName"
|
||||
# Register the scheduled task with Windows Task Scheduler
|
||||
Register-ScheduledTask -TaskName $taskName -Action $taskAction -Trigger $taskTrigger -Principal $taskPrincipal -Description "Monitors for Windows updates and optionally installs them automatically"
|
||||
|
||||
# Verify the task was created successfully
|
||||
$createdTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
||||
if (-not $createdTask) {
|
||||
throw "Failed to verify scheduled task creation"
|
||||
}
|
||||
Write-Host "Successfully created scheduled task: $taskName"
|
||||
} catch {
|
||||
Write-Error "Failed to create auto-start task: $($_.Exception.Message)"
|
||||
throw
|
||||
}
|
||||
} else {
|
||||
Write-Host "Scheduled task $taskName already exists. Skipping creation."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Define standard metric values used throughout the script
|
||||
$script:MetricValues = @{
|
||||
Success = 1 # Indicates successful operation
|
||||
Error = 0 # Indicates error state
|
||||
NoError = 0 # Indicates no error occurred
|
||||
NoLastInstall = 0 # Indicates no previous installation timestamp
|
||||
NoInstallStatus = 0 # Indicates no installation status available
|
||||
}
|
||||
|
||||
# Define Prometheus metric names following naming conventions
|
||||
# These metrics will be exposed to Prometheus for monitoring
|
||||
Set-Variable -Name MetricNames -Value @{
|
||||
# Script execution metrics - track the health and performance of this script
|
||||
Status = "windows_update_script_status" # Status of the script execution (1 for running, 0 for not running)
|
||||
Timestamp = "windows_update_script_timestamp_seconds" # Timestamp of the last script execution
|
||||
Error = "windows_update_script_error" # Error message if script execution fails
|
||||
Runtime = "windows_update_script_runtime_seconds" # Total runtime of the script
|
||||
|
||||
# Update information metrics - track Windows Update status and availability
|
||||
Available = "windows_updates_available" # Total number of Windows updates available for installation
|
||||
Info = "windows_update_info" # General update information
|
||||
Reboot = "windows_update_reboot_required" # Reboot requirement flag
|
||||
AutoUpdate = "windows_update_auto_install_enabled" # Automatic update installation enabled flag (0 or 1)
|
||||
LastInstall = "windows_update_last_install_timestamp_seconds" # Timestamp of the last successful update installation
|
||||
InstallStatus = "windows_update_install_status" # Status of the last update installation attempt (0 for success, 1 for failure)
|
||||
UpdateList = "windows_update_available_list" # List of available updates with details
|
||||
} -Option ReadOnly
|
||||
|
||||
# Validate all metric names conform to Prometheus naming standards
|
||||
# Metric names must start with a letter or underscore, followed by letters, numbers, or underscores
|
||||
$MetricNames.Values | ForEach-Object {
|
||||
if (-not ($_ -match '^[a-zA-Z_:][a-zA-Z0-9_:]*$')) {
|
||||
throw "Invalid Prometheus metric name: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Function to write errors to log file and create error metrics for monitoring
|
||||
function Write-ScriptError {
|
||||
param(
|
||||
[string]$Message, # Error message to log
|
||||
[string]$ErrorCode = "unknown", # Error code for categorization
|
||||
[hashtable]$MetricNames, # Metric names hashtable
|
||||
[string]$MetricsFilePath # Path to write error metrics
|
||||
)
|
||||
|
||||
# Log the error to PowerShell error stream
|
||||
Write-Error $Message
|
||||
|
||||
# Create a Prometheus error metric with sanitized error code
|
||||
$errorMetric = "$($MetricNames.Error){error=`"$($ErrorCode -replace '"', '')`"} 1"
|
||||
try {
|
||||
# Write the error metric to the metrics file for Prometheus to collect
|
||||
$errorMetric | Out-File -FilePath $MetricsFilePath -Encoding UTF8 -Force
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to write error metric: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Function to execute actions with standardized error handling
|
||||
function Invoke-WithErrorHandling {
|
||||
param(
|
||||
[scriptblock]$Action, # Script block to execute
|
||||
[string]$Operation, # Description of the operation for error messages
|
||||
[hashtable]$MetricNames, # Metric names hashtable
|
||||
[string]$MetricsFilePath # Path to write error metrics
|
||||
)
|
||||
try {
|
||||
# Execute the provided script block
|
||||
& $Action
|
||||
} catch {
|
||||
# Handle any errors by writing to metrics and re-throwing
|
||||
Write-ScriptError -Message "Failed to $Operation`: $_" -ErrorCode $Operation.Replace(' ', '_') -MetricNames $MetricNames -MetricsFilePath $MetricsFilePath
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
# Function to download and install Windows updates
|
||||
function Install-WindowsUpdate {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNull()]
|
||||
$Update, # Windows Update object to install
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNull()]
|
||||
$UpdateSession, # Windows Update session object
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNull()]
|
||||
[hashtable]$MetricNames, # Metric names hashtable
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$SanitizedTitle # Sanitized update title for metrics
|
||||
)
|
||||
# Track COM objects for proper cleanup
|
||||
$comObjects = @()
|
||||
try {
|
||||
Write-Information "Installing update: $($Update.Title)" -InformationAction Continue
|
||||
|
||||
# Create and configure the update downloader
|
||||
$UpdateDownloader = $UpdateSession.CreateUpdateDownloader()
|
||||
$comObjects += $UpdateDownloader
|
||||
$UpdateDownloader.Updates = New-Object -ComObject Microsoft.Update.UpdateColl
|
||||
$comObjects += $UpdateDownloader.Updates
|
||||
$UpdateDownloader.Updates.Add($Update) # Add the update to the collection
|
||||
$UpdateDownloader.Download() # Download the update files
|
||||
|
||||
# Create and configure the update installer
|
||||
$UpdateInstaller = $UpdateSession.CreateUpdateInstaller()
|
||||
$UpdateInstaller.Updates = $UpdateDownloader.Updates
|
||||
$InstallResult = $UpdateInstaller.Install() # Install the downloaded update
|
||||
|
||||
# Generate Unix timestamp for the installation
|
||||
$installTimestamp = [Math]::Floor([decimal](Get-Date(Get-Date).ToUniversalTime()-uformat '%s'))
|
||||
|
||||
# Create Prometheus metrics for the installation
|
||||
$additionalMetrics = @()
|
||||
$lastMetric = "# HELP $($MetricNames.LastInstall) Unix timestamp when updates were last installed`n# TYPE $($MetricNames.LastInstall) gauge"
|
||||
$additionalMetrics += $lastMetric
|
||||
$additionalMetrics += "$($MetricNames.LastInstall){update=`"$SanitizedTitle`"} $installTimestamp"
|
||||
$installMetric = "# HELP $($MetricNames.InstallStatus) Status of update installation`n# TYPE $($MetricNames.InstallStatus) gauge"
|
||||
$additionalMetrics += $installMetric
|
||||
$additionalMetrics += "$($MetricNames.InstallStatus){update=`"$SanitizedTitle`",result=`"$($InstallResult.ResultCode)`"} 1"
|
||||
|
||||
Write-Verbose "Installation completed with result: $($InstallResult.ResultCode)"
|
||||
|
||||
# Return success result object
|
||||
return [PSCustomObject]@{
|
||||
Success = $InstallResult.ResultCode -eq 2 # ResultCode 2 = successful installation
|
||||
ResultCode = $InstallResult.ResultCode
|
||||
UpdateTitle = $Update.Title
|
||||
InstallTimestamp = $installTimestamp
|
||||
ErrorMessage = $null
|
||||
Metrics = $additionalMetrics
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$errorMessage = $_.Exception.Message
|
||||
Write-Host "Error installing update: $_"
|
||||
|
||||
# Return error result object
|
||||
return [PSCustomObject]@{
|
||||
Success = $false
|
||||
ResultCode = -1
|
||||
UpdateTitle = $Update.Title
|
||||
InstallTimestamp = $null
|
||||
ErrorMessage = $errorMessage
|
||||
Metrics = @()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
# Clean up COM objects to prevent memory leaks
|
||||
$comObjects | ForEach-Object {
|
||||
if ($_ -ne $null) {
|
||||
try {
|
||||
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($_) | Out-Null
|
||||
} catch {
|
||||
Write-Warning "Failed to release COM object: $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Function to check if a system reboot is required after updates
|
||||
function Get-RebootStatus {
|
||||
try {
|
||||
# Check if Windows OS indicates a reboot is pending
|
||||
$osRebootPending = (Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop).RebootPending
|
||||
# Check Component Based Servicing registry key for pending operations
|
||||
$cbsRebootPending = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing' -Name 'RebootPending' -ErrorAction Stop
|
||||
# Return 1 if either check indicates reboot required, 0 otherwise
|
||||
return [int]($osRebootPending -or $cbsRebootPending)
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not determine reboot status: $($_.Exception.Message)"
|
||||
return -1 # Return -1 to indicate indeterminate status
|
||||
}
|
||||
}
|
||||
|
||||
# Check current reboot status for inclusion in metrics
|
||||
$rebootRequired = Get-RebootStatus
|
||||
|
||||
# Function to sanitize strings for safe use as Prometheus labels
|
||||
function ConvertTo-SafePrometheusLabel {
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[AllowEmptyString()]
|
||||
[string]$Value # String value to sanitize
|
||||
)
|
||||
|
||||
# Return empty string if input is null or empty
|
||||
if ([string]::IsNullOrEmpty($Value)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
# Sanitize special characters that could break Prometheus format
|
||||
# Replace backslashes, quotes, newlines, tabs, and non-printable characters
|
||||
$sanitized = $Value -replace '[\\"\r\n\t]|[^\x20-\x7E]', {
|
||||
switch ($_.Value) {
|
||||
'\' { '\\' } # Escape backslashes
|
||||
'"' { '\"' } # Escape double quotes
|
||||
default { if ($_.Value -match '[\r\n\t]') { ' ' } else { '_' } } # Replace whitespace/unprintable with underscore
|
||||
}
|
||||
}
|
||||
|
||||
# Limit length to prevent excessively long labels (Prometheus best practice)
|
||||
return $sanitized.Substring(0, [Math]::Min($sanitized.Length, 256))
|
||||
}
|
||||
|
||||
# Function to build Prometheus metric strings and add them to StringBuilder
|
||||
function Add-PrometheusMetric {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[System.Text.StringBuilder]$StringBuilder, # StringBuilder to append metrics to
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$MetricName, # Name of the Prometheus metric
|
||||
[Parameter(Mandatory)]
|
||||
[string]$HelpText, # Help text describing the metric
|
||||
[Parameter(Mandatory)]
|
||||
$Value, # Metric value
|
||||
[hashtable]$Labels = @{} # Optional labels for the metric
|
||||
)
|
||||
|
||||
try {
|
||||
# Add Prometheus HELP comment explaining what the metric measures
|
||||
$StringBuilder.AppendLine("# HELP $MetricName $HelpText") | Out-Null
|
||||
# Add Prometheus TYPE comment (assuming gauge type for all metrics)
|
||||
$StringBuilder.AppendLine("# TYPE $MetricName gauge") | Out-Null
|
||||
|
||||
if ($null -ne $Value) {
|
||||
# Build label string if labels are provided
|
||||
$labelString = ""
|
||||
if ($Labels.Count -gt 0) {
|
||||
$labelPairs = $Labels.GetEnumerator() | ForEach-Object {
|
||||
"$($_.Key)=`"$($_.Value)`""
|
||||
}
|
||||
$labelString = "{$($labelPairs -join ',')}"
|
||||
}
|
||||
# Add the actual metric line with name, labels, and value
|
||||
$StringBuilder.AppendLine("$MetricName$labelString $Value") | Out-Null
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to add Prometheus metric '$MetricName': $_"
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
# Function to generate complete Prometheus metrics output
|
||||
function Get-PrometheusMetrics {
|
||||
param(
|
||||
[int]$UpdateCount, # Number of available updates
|
||||
[double]$ScriptRunTime, # Time taken to run the script
|
||||
[bool]$rebootRequired, # Whether a system reboot is required
|
||||
[bool]$AutoUpdateEnabled, # Whether automatic updates are enabled
|
||||
[array]$AvailableUpdates = @() # Array of available update details
|
||||
)
|
||||
|
||||
# Validate input parameters
|
||||
if ($UpdateCount -lt 0) {
|
||||
throw "UpdateCount must be non-negative"
|
||||
}
|
||||
if ($ScriptRunTime -lt 0) {
|
||||
throw "ScriptRunTime must be non-negative"
|
||||
}
|
||||
|
||||
try {
|
||||
# Get current Unix timestamp for the script execution time
|
||||
$currentTimestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
|
||||
} catch {
|
||||
Write-Error "Failed to initialize metrics generation: $_"
|
||||
return $null
|
||||
}
|
||||
|
||||
# Convert boolean values to integers for Prometheus (0 or 1)
|
||||
$rebootValue = [int]$rebootRequired
|
||||
$autoUpdateValue = [int]$AutoUpdateEnabled
|
||||
|
||||
# Define all metrics to be generated with their properties
|
||||
$metrics = @(
|
||||
@{ Name = $MetricNames.Available; Help = "Number of Windows updates available from WSUS"; Value = $UpdateCount }
|
||||
@{ Name = $MetricNames.Status; Help = "Indicates if the update script has run successfully"; Value = 1; Labels = @{status="success"} }
|
||||
@{ Name = $MetricNames.Timestamp; Help = "Unix timestamp when the script last ran"; Value = $currentTimestamp }
|
||||
@{ Name = $MetricNames.Error; Help = "Information about script errors"; Value = 0 }
|
||||
@{ Name = $MetricNames.Reboot; Help = "Indicates if a reboot is required after updates"; Value = $rebootValue }
|
||||
@{ Name = $MetricNames.Runtime; Help = "Time taken to execute the update script in seconds"; Value = $ScriptRunTime }
|
||||
@{ Name = $MetricNames.AutoUpdate; Help = "Indicates if automatic update installation is enabled"; Value = $autoUpdateValue }
|
||||
@{ Name = $MetricNames.LastInstall; Help = "Unix timestamp when updates were last installed"; Value = 0 }
|
||||
@{ Name = $MetricNames.InstallStatus; Help = "Status of update installation"; Value = 0 }
|
||||
)
|
||||
|
||||
# Build the complete metrics string
|
||||
$metricsBuilder = [System.Text.StringBuilder]::new()
|
||||
foreach ($metric in $metrics) {
|
||||
try {
|
||||
Add-PrometheusMetric -StringBuilder $metricsBuilder -MetricName $metric.Name -HelpText $metric.Help -Value $metric.Value -Labels $metric.Labels
|
||||
} catch {
|
||||
Write-Warning "Failed to add metric '$($metric.Name)': $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Add individual update metrics - one per available update
|
||||
if ($AvailableUpdates.Count -gt 0) {
|
||||
$metricsBuilder.AppendLine("# HELP $($MetricNames.UpdateList) Individual Windows updates available for installation") | Out-Null
|
||||
$metricsBuilder.AppendLine("# TYPE $($MetricNames.UpdateList) gauge") | Out-Null
|
||||
|
||||
foreach ($update in $AvailableUpdates) {
|
||||
$labels = @{
|
||||
title = $update.Title
|
||||
severity = $update.Severity
|
||||
downloaded = $update.IsDownloaded
|
||||
size_bytes = $update.Size
|
||||
}
|
||||
$labelString = ($labels.GetEnumerator() | ForEach-Object { "$($_.Key)=`"$($_.Value)`"" }) -join ','
|
||||
$metricsBuilder.AppendLine("$($MetricNames.UpdateList){$labelString} 1") | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
return $metricsBuilder.ToString()
|
||||
}
|
||||
|
||||
# Validate that metric names configuration is available
|
||||
if (-not $MetricNames) {
|
||||
Write-Error "MetricNames configuration is not available"
|
||||
return $null
|
||||
}
|
||||
|
||||
# Graceful shutdown flag
|
||||
$global:shutdown = $false
|
||||
|
||||
# Register event handler for graceful shutdown
|
||||
Register-EngineEvent -SourceIdentifier "PowerShell.Exiting" -Action {
|
||||
Write-Host "Initiating graceful shutdown..."
|
||||
$global:shutdown = $true
|
||||
}
|
||||
|
||||
# Trap Ctrl+C and other termination signals
|
||||
[Console]::TreatControlCAsInput = $false
|
||||
Register-ObjectEvent -InputObject ([Console]) -EventName CancelKeyPress -Action {
|
||||
param($Sender, $CancelEventArgs)
|
||||
Write-Host "Shutdown signal received. Cleaning up..."
|
||||
$CancelEventArgs.Cancel = $true
|
||||
$global:shutdown = $true
|
||||
}
|
||||
|
||||
# Function to check for shutdown signal during long operations
|
||||
function Test-ShutdownSignal {
|
||||
if ($global:shutdown) {
|
||||
Write-Host "Shutdown requested. Exiting gracefully..."
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# Main execution: Check for Windows Updates from WSUS
|
||||
Write-Host "Checking for Windows Updates from WSUS..."
|
||||
$StartTime = Get-Date
|
||||
$comObjects = @()
|
||||
$availableUpdates = @()
|
||||
try {
|
||||
# Check for shutdown before starting
|
||||
Test-ShutdownSignal
|
||||
|
||||
# Create Windows Update session and searcher COM objects
|
||||
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
|
||||
$comObjects += $UpdateSession
|
||||
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
|
||||
$comObjects += $UpdateSearcher
|
||||
|
||||
# Check for shutdown before search
|
||||
Test-ShutdownSignal
|
||||
|
||||
# Search for updates that are not yet installed
|
||||
$SearchResult = $UpdateSearcher.Search("IsInstalled=0")
|
||||
|
||||
# Process individual updates for detailed metrics
|
||||
foreach ($Update in $SearchResult.Updates) {
|
||||
$sanitizedTitle = ConvertTo-SafePrometheusLabel -Value $Update.Title
|
||||
$sanitizedDescription = ConvertTo-SafePrometheusLabel -Value $Update.Description
|
||||
$updateSize = if ($Update.MaxDownloadSize) { $Update.MaxDownloadSize } else { 0 }
|
||||
|
||||
$availableUpdates += [PSCustomObject]@{
|
||||
Title = $sanitizedTitle
|
||||
Description = $sanitizedDescription
|
||||
Size = $updateSize
|
||||
Severity = if ($Update.MsrcSeverity) { $Update.MsrcSeverity } else { "Unknown" }
|
||||
IsDownloaded = [int]$Update.IsDownloaded
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
# Handle connection failures and write error metric
|
||||
Write-Error "Failed to connect to Windows Update service: $_"
|
||||
$errorMetric = "$($MetricNames.Error){error=`"wsus_connection_failed`"} 1"
|
||||
$errorMetric | Out-File -FilePath $MetricsFilePath -Encoding UTF8 -Force
|
||||
exit 1
|
||||
} finally {
|
||||
# Clean up COM objects to prevent memory leaks
|
||||
$comObjects | ForEach-Object {
|
||||
if ($_ -and [System.Runtime.InteropServices.Marshal]::IsComObject($_)) {
|
||||
try {
|
||||
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($_) | Out-Null
|
||||
} catch {
|
||||
Write-Warning "Failed to release COM object: $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate script execution time and generate base metrics
|
||||
$EndTime = Get-Date
|
||||
$ScriptRunTime = ($EndTime - $StartTime).TotalSeconds
|
||||
# Safely get update count even if SearchResult is null
|
||||
$updateCount = if ($SearchResult -and $SearchResult.Updates) { $SearchResult.Updates.Count } else { 0 }
|
||||
$prometheusMetric = Get-PrometheusMetrics -UpdateCount $updateCount -ScriptRunTime $ScriptRunTime -AutoUpdateEnabled $AutoInstall -AvailableUpdates $availableUpdates
|
||||
|
||||
# Initialize array for additional metrics from update installations
|
||||
$additionalMetrics = @()
|
||||
|
||||
# Process the search results
|
||||
if ($SearchResult.Updates.Count -eq 0) {
|
||||
Write-Host "No updates available from WSUS."
|
||||
} else {
|
||||
Write-Host "Found $($SearchResult.Updates.Count) update(s) available:"
|
||||
$updateList = [System.Collections.ArrayList]::new()
|
||||
|
||||
# Iterate through each available update
|
||||
foreach ($Update in $SearchResult.Updates) {
|
||||
# Check for shutdown signal between updates
|
||||
Test-ShutdownSignal
|
||||
|
||||
Write-Host "- $($Update.Title)"
|
||||
$sanitizedTitle = ConvertTo-SafePrometheusLabel -Value $Update.Title
|
||||
[void]$updateList.Add("title=`"$sanitizedTitle`"")
|
||||
|
||||
# Install the update if AutoInstall is enabled
|
||||
if ($AutoInstall) {
|
||||
# Check for shutdown before installing
|
||||
Test-ShutdownSignal
|
||||
|
||||
$result = Install-WindowsUpdate -Update $Update -UpdateSession $UpdateSession -MetricNames $MetricNames -SanitizedTitle $sanitizedTitle
|
||||
if ($result.Metrics) {
|
||||
$additionalMetrics += $result.Metrics
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Write all metrics to the output file for Prometheus collection
|
||||
try {
|
||||
# Final shutdown check before writing metrics
|
||||
Test-ShutdownSignal
|
||||
|
||||
$allMetrics = @($prometheusMetric) + $additionalMetrics
|
||||
($allMetrics -join "`n") | Out-File -FilePath "$MetricsFilePath" -Encoding UTF8 -Force
|
||||
Write-Host "Metrics successfully written to $MetricsFilePath"
|
||||
} catch {
|
||||
# Handle file write errors and create error metric
|
||||
Write-Error "Failed to write metrics to file: $_"
|
||||
$errorCode = $_.Exception.HResult -replace '"', '\"'
|
||||
"$($MetricNames.Error){error=`"file_write_failed`",error_code=`"$errorCode`"} 1" | Out-File -FilePath "$MetricsFilePath" -Encoding UTF8 -Force
|
||||
}
|
||||
|
||||
Write-Host "Script completed successfully."
|
||||
|
||||
Reference in New Issue
Block a user