Classes/PatchMyPCClasses.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Classes and enums for PsPatchMyPC module .DESCRIPTION Defines typed objects for patch status, deferral state, and configuration #> # Deferral phase enum enum DeferralPhase { Initial # 5hr refresh, all deferral options Approaching # 100min refresh, limited options Imminent # 10min refresh, 1hr/4hr only Elapsed # 5min refresh, 1hr max (aggressive) } # Update priority enum enum UpdatePriority { Critical High Normal Low } # Installation status enum enum InstallationStatus { Pending InProgress Success Failed Deferred Cancelled } # Patch status class class PatchStatus { [string]$AppId [string]$AppName [string]$InstalledVersion [string]$AvailableVersion [bool]$UpdateAvailable [UpdatePriority]$Priority [string[]]$ConflictingProcesses [bool]$ProcessesRunning [datetime]$LastChecked PatchStatus() { $this.LastChecked = [datetime]::UtcNow $this.UpdateAvailable = $false $this.Priority = [UpdatePriority]::Normal $this.ConflictingProcesses = @() $this.ProcessesRunning = $false } [string] ToString() { return "$($this.AppName) [$($this.InstalledVersion) -> $($this.AvailableVersion)]" } } # Deferral state class class DeferralState { [string]$AppId [int]$DeferralCount [datetime]$FirstNotification [datetime]$LastDeferral [string]$TargetVersion [datetime]$DeadlineDate [DeferralPhase]$Phase [int]$MaxDeferrals DeferralState() { $this.DeferralCount = 0 $this.Phase = [DeferralPhase]::Initial $this.MaxDeferrals = 5 $this.TargetVersion = 'Latest' # Default to prevent empty strings } [bool] CanDefer() { if ($this.Phase -eq [DeferralPhase]::Elapsed) { return $false } if ($this.DeferralCount -ge $this.MaxDeferrals) { return $false } return $true } [int] GetRemainingDeferrals() { return [Math]::Max(0, $this.MaxDeferrals - $this.DeferralCount) } [string[]] GetAvailableOptions() { $options = @('1 Hour') switch ($this.Phase) { 'Initial' { $options = @('1 Hour', '4 Hours', 'Tomorrow', 'Custom') } 'Approaching' { $options = @('1 Hour', '4 Hours', 'Tomorrow') } 'Imminent' { $options = @('1 Hour', '4 Hours') } 'Elapsed' { $options = @('1 Hour') } } return $options } } # Installation result class class InstallationResult { [string]$AppId [string]$AppName [InstallationStatus]$Status [int]$ExitCode [string]$Message [datetime]$Timestamp [timespan]$Duration [bool]$RebootRequired InstallationResult() { $this.Timestamp = [datetime]::UtcNow $this.Status = [InstallationStatus]::Pending $this.RebootRequired = $false } } # Patch cycle result class class PatchCycleResult { [bool]$Success [string]$Message [int]$TotalUpdates [int]$Installed [int]$Failed [int]$Deferred [bool]$RebootRequired [datetime]$StartTime [datetime]$EndTime [timespan]$Duration [InstallationResult[]]$Results [string]$CorrelationId PatchCycleResult() { $this.CorrelationId = [guid]::NewGuid().ToString() $this.StartTime = [datetime]::UtcNow $this.Results = @() $this.Success = $true $this.RebootRequired = $false } [void] Complete() { $this.EndTime = [datetime]::UtcNow $this.Duration = $this.EndTime - $this.StartTime } } # Managed application class class ManagedApplication { [string]$Id [string]$Name [bool]$Enabled [UpdatePriority]$Priority [string[]]$ConflictingProcesses [string]$PreScript [string]$PostScript [string]$InstallArguments [bool]$RequiresReboot [hashtable]$DeferralOverride # New properties for install missing and version pinning [bool]$InstallIfMissing [bool]$DeferInitialInstall [string]$VersionPinMode # max, exact, freeze, or $null for normal [string]$PinnedVersion ManagedApplication() { $this.Enabled = $true $this.Priority = [UpdatePriority]::Normal $this.ConflictingProcesses = @() $this.RequiresReboot = $false $this.InstallIfMissing = $false $this.DeferInitialInstall = $false $this.VersionPinMode = $null $this.PinnedVersion = $null } static [ManagedApplication] FromHashtable([hashtable]$ht) { $app = [ManagedApplication]::new() $app.Id = $ht.Id $app.Name = $ht.Name if ($null -ne $ht.Enabled) { $app.Enabled = $ht.Enabled } if ($ht.Priority) { $app.Priority = [UpdatePriority]$ht.Priority } if ($ht.ConflictingProcesses) { $app.ConflictingProcesses = $ht.ConflictingProcesses } if ($ht.PreScript) { $app.PreScript = $ht.PreScript } if ($ht.PostScript) { $app.PostScript = $ht.PostScript } if ($ht.InstallArguments) { $app.InstallArguments = $ht.InstallArguments } if ($null -ne $ht.RequiresReboot) { $app.RequiresReboot = $ht.RequiresReboot } if ($ht.DeferralOverride) { $app.DeferralOverride = $ht.DeferralOverride } # New properties if ($null -ne $ht.InstallIfMissing) { $app.InstallIfMissing = $ht.InstallIfMissing } if ($null -ne $ht.DeferInitialInstall) { $app.DeferInitialInstall = $ht.DeferInitialInstall } if ($ht.VersionPinMode) { $app.VersionPinMode = $ht.VersionPinMode } if ($ht.PinnedVersion) { $app.PinnedVersion = $ht.PinnedVersion } return $app } } # Module configuration class class PsPatchMyPCConfig { [string]$LogPath [string]$StatePath [string]$ConfigPath [string]$EventLogName [string]$EventLogSource [string]$StateRegistryKey [hashtable]$Deferrals [hashtable]$Notifications [hashtable]$Updates [ManagedApplication[]]$Applications PsPatchMyPCConfig() { $this.Deferrals = @{ Mode = 'CountAndDeadline' MaxCount = 5 DeadlineDays = 7 ApproachingWindowHours = 72 ImminentWindowHours = 24 InitialRefreshSeconds = 18000 ApproachingRefreshSeconds = 6000 ImminentRefreshSeconds = 600 ElapsedRefreshSeconds = 300 } $this.Notifications = @{ EnableToasts = $true EnableDeferralDialog = $true EnableProgressDialog = $true EnableAggressiveMode = $true CompanyName = 'IT Department' AccentColor = '#0078D4' } $this.Updates = @{ CheckIntervalHours = 4 InstallWindowStart = '03:00' InstallWindowEnd = '05:00' SkipMeteredConnections = $true } $this.Applications = @() } } |