Public/Get-PatchMyPCConfig.ps1
|
function Get-PatchMyPCConfig { <# .SYNOPSIS Gets the current PsPatchMyPC module configuration .DESCRIPTION Returns the merged configuration from defaults, config file, and environment overrides .PARAMETER Path Optional path to a custom configuration file .EXAMPLE Get-PatchMyPCConfig Returns the current configuration .EXAMPLE Get-PatchMyPCConfig -Path "C:\Config\custom.psd1" Loads configuration from a custom file #> [CmdletBinding()] param( [Parameter()] [string]$Path ) try { $config = [PsPatchMyPCConfig]::new() $moduleConfig = Get-ModuleConfiguration # Set base paths from module config $config.LogPath = $moduleConfig.LogPath $config.StatePath = $moduleConfig.StatePath $config.ConfigPath = $moduleConfig.ConfigPath $config.EventLogName = $moduleConfig.EventLogName $config.EventLogSource = $moduleConfig.EventLogSource $config.StateRegistryKey = $moduleConfig.StateRegistryKey # Load config file $configFile = $Path if (-not $configFile) { $configFile = Join-Path $Script:ConfigPath 'config.psd1' } if (Test-Path $configFile) { $fileConfig = Import-PowerShellDataFile -Path $configFile # Merge deferrals if ($fileConfig.Deferrals) { foreach ($key in $fileConfig.Deferrals.Keys) { $config.Deferrals[$key] = $fileConfig.Deferrals[$key] } } # Merge notifications if ($fileConfig.Notifications) { foreach ($key in $fileConfig.Notifications.Keys) { $config.Notifications[$key] = $fileConfig.Notifications[$key] } } # Merge updates if ($fileConfig.Updates) { foreach ($key in $fileConfig.Updates.Keys) { $config.Updates[$key] = $fileConfig.Updates[$key] } } # Override paths if specified if ($fileConfig.Paths) { if ($fileConfig.Paths.LogDirectory) { $config.LogPath = $fileConfig.Paths.LogDirectory } if ($fileConfig.Paths.StateDirectory) { $config.StatePath = $fileConfig.Paths.StateDirectory } } } # Apply environment variable overrides if ($env:PSPMPC_LOG_PATH) { $config.LogPath = $env:PSPMPC_LOG_PATH } if ($env:PSPMPC_CONFIG_PATH) { $config.ConfigPath = $env:PSPMPC_CONFIG_PATH } # Load applications from catalog $config.Applications = Get-ManagedApplicationsInternal return $config } catch { Write-PatchLog "Failed to load configuration: $_" -Type Error throw } } function Get-ManagedApplicationsInternal { <# .SYNOPSIS Internal function to load managed applications from catalog #> [CmdletBinding()] param() $apps = @() $moduleConfig = Get-ModuleConfiguration $catalogPath = Join-Path $moduleConfig.ConfigPath 'applications.json' # Fallback to module's config folder if not in ProgramData if (-not (Test-Path $catalogPath)) { $catalogPath = Join-Path $Script:ConfigPath 'applications.json' } if (Test-Path $catalogPath) { try { $catalog = Get-Content -Path $catalogPath -Raw | ConvertFrom-Json foreach ($appData in $catalog.applications) { # Convert deferralOverride PSCustomObject to hashtable if present $deferralOverride = $null if ($appData.deferralOverride) { $deferralOverride = @{} $appData.deferralOverride.PSObject.Properties | ForEach-Object { $deferralOverride[$_.Name] = $_.Value } } $ht = @{ Id = $appData.id Name = $appData.name Enabled = $appData.enabled Priority = $appData.priority ConflictingProcesses = @($appData.conflictingProcesses) PreScript = $appData.preScript PostScript = $appData.postScript InstallArguments = $appData.installArguments RequiresReboot = $appData.requiresReboot DeferralOverride = $deferralOverride # New fields for install missing and version pinning InstallIfMissing = if ($null -ne $appData.installIfMissing) { $appData.installIfMissing } else { $false } DeferInitialInstall = if ($null -ne $appData.deferInitialInstall) { $appData.deferInitialInstall } else { $false } VersionPinMode = if ($appData.versionPin) { $appData.versionPin.mode } else { $null } PinnedVersion = if ($appData.versionPin) { $appData.versionPin.version } else { $null } } $apps += [ManagedApplication]::FromHashtable($ht) } } catch { Write-PatchLog "Failed to load applications catalog: $_" -Type Warning } } return $apps } function Get-PatchMyPCLogs { <# .SYNOPSIS Gets PsPatchMyPC log entries .DESCRIPTION Retrieves and parses log entries from the PsPatchMyPC log files .PARAMETER Days Number of days of logs to retrieve (default: 7) .PARAMETER Type Filter by log type: Info, Warning, Error, or All (default: All) .PARAMETER Tail Return only the last N entries .EXAMPLE Get-PatchMyPCLogs -Days 1 -Type Error Gets all error entries from the last day #> [CmdletBinding()] param( [Parameter()] [int]$Days = 7, [Parameter()] [ValidateSet('Info', 'Warning', 'Error', 'All')] [string]$Type = 'All', [Parameter()] [int]$Tail ) $config = Get-ModuleConfiguration $logPath = $config.LogPath if (-not (Test-Path $logPath)) { Write-Warning "Log directory not found: $logPath" return @() } # Get log files from the specified date range $startDate = (Get-Date).AddDays(-$Days) $logFiles = Get-ChildItem -Path $logPath -Filter "PsPatchMyPC_*.log" | Where-Object { $_.LastWriteTime -ge $startDate } | Sort-Object LastWriteTime -Descending $entries = @() foreach ($file in $logFiles) { $content = Get-Content -Path $file.FullName -Raw # Parse CMTrace format entries $pattern = '<!\[LOG\[(.*?)\]LOG\]!><time="(.*?)" date="(.*?)" component="(.*?)" context="(.*?)" type="(\d)" thread="(\d+)" file="(.*?)">' $matches = [regex]::Matches($content, $pattern) foreach ($match in $matches) { $typeNum = [int]$match.Groups[6].Value $typeStr = switch ($typeNum) { 1 { 'Info' } 2 { 'Warning' } 3 { 'Error' } default { 'Info' } } if ($Type -ne 'All' -and $typeStr -ne $Type) { continue } $entries += [PSCustomObject]@{ Timestamp = [datetime]::ParseExact( "$($match.Groups[3].Value) $($match.Groups[2].Value.Substring(0, 12))", 'MM-dd-yyyy HH:mm:ss.fff', $null ) Type = $typeStr Message = $match.Groups[1].Value Component = $match.Groups[4].Value Context = $match.Groups[5].Value Thread = $match.Groups[7].Value } } } # Sort by timestamp descending $entries = $entries | Sort-Object Timestamp -Descending # Apply tail if specified if ($Tail -gt 0) { $entries = $entries | Select-Object -First $Tail } return $entries } |