WindowsUpdateTools.psm1
#requires -Version 5.1 <# .SYNOPSIS WindowsUpdateTools PowerShell Module .DESCRIPTION Main module file for WindowsUpdateTools - loads all public and private functions and handles module initialization. .NOTES Module: WindowsUpdateTools Author: Anthony Balloi - CSOLVE Version: 1.0.0 This module provides comprehensive Windows Update diagnostics and remediation capabilities for enterprise environments. #> #region Module Variables # Set module-level variables $script:ModuleRoot = $PSScriptRoot $script:ModuleName = 'WindowsUpdateTools' $script:ModuleVersion = '1.1.1' # Default paths for common operations $script:DefaultLogPath = "C:\Windows\Temp" $script:DefaultOutputPath = "C:\Windows\Temp" $script:SetupDiagUrl = "https://go.microsoft.com/fwlink/?linkid=870142" # Exit codes used throughout the module $script:ExitCodes = @{ Success = 0 Failure = 1 PartialSuccess = 1641 RebootRequired = 3010 PartialFailure = 1 MajorFailure = 2 } # Common Windows Setup log paths $script:SetupLogPaths = @( "$env:WINDIR\Panther", "$env:WINDIR\Panther\NewOS", "$env:WINDIR\Panther\UnattendGC", "$env:WINDIR\inf\setupapi.dev.log", "$env:WINDIR\logs\CBS", "C:\`$Windows.~BT\Sources\Panther", "C:\`$Windows.~WS\Sources\Panther" ) # Windows Update services $script:WindowsUpdateServices = @( @{ Name = 'wuauserv'; DisplayName = 'Windows Update'; Critical = $true }, @{ Name = 'BITS'; DisplayName = 'Background Intelligent Transfer Service'; Critical = $true }, @{ Name = 'cryptsvc'; DisplayName = 'Cryptographic Services'; Critical = $true }, @{ Name = 'msiserver'; DisplayName = 'Windows Installer'; Critical = $false }, @{ Name = 'TrustedInstaller'; DisplayName = 'Windows Modules Installer'; Critical = $false } ) #endregion Module Variables #region Helper Functions function Write-ModuleMessage { <# .SYNOPSIS Internal module logging function #> param( [string]$Message, [ValidateSet('Info', 'Warning', 'Error', 'Verbose')] [string]$Level = 'Info' ) $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $logEntry = "[$timestamp] [$script:ModuleName] [$Level] $Message" switch ($Level) { 'Error' { Write-Error $logEntry } 'Warning' { Write-Warning $logEntry } 'Verbose' { Write-Verbose $logEntry } 'Info' { Write-Information $logEntry -InformationAction Continue } } } function Test-ModulePrerequisites { <# .SYNOPSIS Test module prerequisites and system compatibility #> [CmdletBinding()] param() $issues = @() # Check PowerShell version if ($PSVersionTable.PSVersion -lt [Version]'5.1') { $issues += "PowerShell 5.1 or later required (found: $($PSVersionTable.PSVersion))" } # Check Windows version $osVersion = [Environment]::OSVersion.Version if ($osVersion -lt [Version]'10.0.17763') { # Windows 10 1809 / Server 2019 $issues += "Windows 10 1809+ or Windows Server 2019+ required" } # Check if running as administrator for remediation functions $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) $script:IsElevated = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) # Check .NET Framework version for SetupDiag compatibility try { $netVersions = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP" -Recurse -ErrorAction SilentlyContinue | Get-ItemProperty -Name Version, Release -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^(?!S)\d+' } | Sort-Object Version -Descending | Select-Object -First 1 if ($netVersions.Release -lt 461808) { # .NET 4.7.2 $issues += ".NET Framework 4.7.2+ recommended for SetupDiag functionality" } $script:DotNetVersion = if ($netVersions.Release -ge 461808) { [Version]"4.7.2" } else { [Version]"4.6.1" } } catch { $issues += "Could not determine .NET Framework version" $script:DotNetVersion = [Version]"0.0" } if ($issues.Count -gt 0) { Write-ModuleMessage "Module prerequisite warnings:" -Level Warning foreach ($issue in $issues) { Write-ModuleMessage " - $issue" -Level Warning } } return $issues.Count -eq 0 } #endregion Helper Functions #region Module Loading # Get the directory paths $PublicPath = Join-Path $PSScriptRoot 'Public' $PrivatePath = Join-Path $PSScriptRoot 'Private' # Initialize module Write-ModuleMessage "Loading $script:ModuleName module v$script:ModuleVersion" -Level Verbose # Test prerequisites Test-ModulePrerequisites # Load private functions first (these are internal helper functions) if (Test-Path $PrivatePath) { Write-ModuleMessage "Loading private functions from: $PrivatePath" -Level Verbose $privateFiles = Get-ChildItem -Path $PrivatePath -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue foreach ($file in $privateFiles) { try { Write-ModuleMessage "Loading private function: $($file.BaseName)" -Level Verbose . $file.FullName } catch { Write-ModuleMessage "Failed to load private function $($file.Name): $($_.Exception.Message)" -Level Error } } Write-ModuleMessage "Loaded $($privateFiles.Count) private functions" -Level Verbose } else { Write-ModuleMessage "Private functions directory not found: $PrivatePath" -Level Warning } # Load public functions (these will be exported) if (Test-Path $PublicPath) { Write-ModuleMessage "Loading public functions from: $PublicPath" -Level Verbose $publicFiles = Get-ChildItem -Path $PublicPath -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue foreach ($file in $publicFiles) { try { Write-ModuleMessage "Loading public function: $($file.BaseName)" -Level Verbose . $file.FullName } catch { Write-ModuleMessage "Failed to load public function $($file.Name): $($_.Exception.Message)" -Level Error } } Write-ModuleMessage "Loaded $($publicFiles.Count) public functions" -Level Verbose # Export public functions $functionsToExport = $publicFiles | ForEach-Object { $_.BaseName } Export-ModuleMember -Function $functionsToExport } else { Write-ModuleMessage "Public functions directory not found: $PublicPath" -Level Error throw "Cannot load module: Public functions directory missing" } # Create and export aliases $aliases = @{ 'Test-WUHealth' = 'Test-WindowsUpdateHealth' 'Repair-WU' = 'Repair-WindowsUpdate' } foreach ($alias in $aliases.GetEnumerator()) { try { New-Alias -Name $alias.Key -Value $alias.Value -Force -ErrorAction Stop Write-ModuleMessage "Created alias: $($alias.Key) -> $($alias.Value)" -Level Verbose } catch { Write-ModuleMessage "Failed to create alias $($alias.Key): $($_.Exception.Message)" -Level Warning } } Export-ModuleMember -Alias @($aliases.Keys) # Export module variables that might be useful to users Export-ModuleMember -Variable 'ExitCodes' #endregion Module Loading #region Module Cleanup # Register module removal event to clean up any resources $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Write-ModuleMessage "Unloading $script:ModuleName module" -Level Verbose # Clean up any temporary files or resources if needed # This runs when Remove-Module is called } #endregion Module Cleanup # Final module initialization message Write-ModuleMessage "$script:ModuleName module loaded successfully" -Level Info if (-not $script:IsElevated) { Write-ModuleMessage "Module loaded without administrator privileges - remediation functions will be limited" -Level Warning } if ($script:DotNetVersion -lt [Version]"4.7.2") { Write-ModuleMessage "SetupDiag functionality may be limited (.NET Framework $script:DotNetVersion detected)" -Level Warning } |