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.0'

# 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
}