Private/Core/Get-AvailableUpdate.ps1

function Get-AvailableUpdate {
    <#
    .SYNOPSIS
        Gets available updates for managed applications
    .DESCRIPTION
        Checks for available updates using winget and compares versions
    .PARAMETER AppId
        Optional app ID to check for specific application
    .PARAMETER ManagedOnly
        Only check applications in the managed catalog
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$AppId,
        
        [Parameter()]
        [switch]$ManagedOnly
    )
    
    $updates = @()
    
    if (-not (Test-WingetAvailableInternal)) {
        Write-PatchLog "Winget not available - cannot check for updates" -Type Warning
        return $updates
    }
    
    try {
        # Get managed applications if filtering
        $managedApps = @()
        if ($ManagedOnly) {
            $managedApps = Get-ManagedApplicationsInternal
            if ($managedApps.Count -eq 0) {
                Write-PatchLog "No managed applications configured" -Type Info
                return $updates
            }
        }
        
        # Get all packages with updates
        $packages = Get-WinGetPackage -ErrorAction SilentlyContinue | 
            Where-Object { $_.IsUpdateAvailable }
        
        foreach ($pkg in $packages) {
            # Filter by AppId if specified
            if ($AppId -and $pkg.Id -ne $AppId) { continue }
            
            # Filter by managed apps if specified
            if ($ManagedOnly) {
                $managed = $managedApps | Where-Object { $_.Id -eq $pkg.Id -and $_.Enabled }
                if (-not $managed) { continue }
            }
            
            # Get managed app config for priority and processes
            $appConfig = $managedApps | Where-Object { $_.Id -eq $pkg.Id } | Select-Object -First 1
            
            $status = [PatchStatus]::new()
            $status.AppId = $pkg.Id
            $status.AppName = $pkg.Name
            $status.InstalledVersion = $pkg.InstalledVersion
            # AvailableVersions is an array; pick the first non-empty entry (some packages return blank strings)
            $available = $null
            if ($pkg.AvailableVersions -and $pkg.AvailableVersions.Count -gt 0) {
                foreach ($v in $pkg.AvailableVersions) {
                    if (-not [string]::IsNullOrWhiteSpace([string]$v)) {
                        $available = [string]$v
                        break
                    }
                }
            }
            if ([string]::IsNullOrWhiteSpace($available)) {
                $available = 'Latest'
            }
            $status.AvailableVersion = $available
            $status.UpdateAvailable = $true
            
            if ($appConfig) {
                $status.Priority = $appConfig.Priority
                $status.ConflictingProcesses = $appConfig.ConflictingProcesses
                
                # Check if conflicting processes are running
                if ($appConfig.ConflictingProcesses.Count -gt 0) {
                    $status.ProcessesRunning = Test-ConflictingProcess -ProcessNames $appConfig.ConflictingProcesses
                }
                
                # Apply version pin filtering
                if ($appConfig.VersionPinMode) {
                    switch ($appConfig.VersionPinMode) {
                        'freeze' {
                            # Skip - don't offer updates for frozen apps
                            Write-PatchLog "Skipping update for $($pkg.Id) - version is frozen" -Type Info
                            continue
                        }
                        'max' {
                            # Only offer update if available version <= pinned max
                            if ($appConfig.PinnedVersion -and $available -ne 'Latest') {
                                $comparison = Compare-Version -Version1 $available -Version2 $appConfig.PinnedVersion
                                if ($comparison -gt 0) {
                                    Write-PatchLog "Skipping update for $($pkg.Id) - available version $available exceeds max pin $($appConfig.PinnedVersion)" -Type Info
                                    continue
                                }
                            }
                        }
                        'exact' {
                            # Only offer update if current version != pinned version
                            if ($appConfig.PinnedVersion) {
                                if ($pkg.InstalledVersion -eq $appConfig.PinnedVersion) {
                                    Write-PatchLog "Skipping update for $($pkg.Id) - already at pinned version $($appConfig.PinnedVersion)" -Type Info
                                    continue
                                }
                                # Set the target version to the pinned version
                                $available = $appConfig.PinnedVersion
                                $status.AvailableVersion = $available
                            }
                        }
                    }
                }
            }
            
            $updates += $status
        }
        
        Write-PatchLog "Found $($updates.Count) available updates" -Type Info
    }
    catch {
        Write-PatchLog "Failed to check for updates: $_" -Type Error
    }
    
    return $updates
}

function Compare-Version {
    <#
    .SYNOPSIS
        Compares two version strings
    .DESCRIPTION
        Returns -1 if Version1 < Version2, 0 if equal, 1 if Version1 > Version2
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Version1,
        
        [Parameter(Mandatory)]
        [string]$Version2
    )
    
    try {
        $v1 = [Version]$Version1
        $v2 = [Version]$Version2
        return $v1.CompareTo($v2)
    }
    catch {
        # Fall back to string comparison if not valid versions
        return [string]::Compare($Version1, $Version2, $true)
    }
}

function Get-MissingApplication {
    <#
    .SYNOPSIS
        Gets applications from the catalog that are not installed
    .DESCRIPTION
        Checks which managed applications are missing from the system and returns
        those that have InstallIfMissing enabled
    .EXAMPLE
        Get-MissingApplication
        Returns all missing applications that should be installed
    #>

    [CmdletBinding()]
    param()
    
    $missing = @()
    
    if (-not (Test-WingetAvailableInternal)) {
        Write-PatchLog "Winget not available - cannot check for missing applications" -Type Warning
        return $missing
    }
    
    try {
        $managedApps = Get-ManagedApplicationsInternal
        if ($managedApps.Count -eq 0) {
            Write-PatchLog "No managed applications configured" -Type Info
            return $missing
        }
        
        # Get all installed packages
        $installedPackages = Get-WinGetPackage -ErrorAction SilentlyContinue
        $installedIds = @()
        if ($installedPackages) {
            $installedIds = $installedPackages | ForEach-Object { $_.Id }
        }
        
        foreach ($app in $managedApps) {
            # Skip disabled apps or those not marked for install
            if (-not $app.Enabled -or -not $app.InstallIfMissing) { continue }
            
            # Check if installed
            $isInstalled = $installedIds -contains $app.Id
            if (-not $isInstalled) {
                $missingApp = [PSCustomObject]@{
                    AppId = $app.Id
                    AppName = $app.Name
                    Priority = $app.Priority
                    AppConfig = $app
                    TargetVersion = if ($app.VersionPinMode -eq 'exact' -and $app.PinnedVersion) { $app.PinnedVersion } else { 'Latest' }
                }
                $missing += $missingApp
                Write-PatchLog "Application $($app.Name) ($($app.Id)) is missing and marked for install" -Type Info
            }
        }
        
        Write-PatchLog "Found $($missing.Count) missing applications to install" -Type Info
    }
    catch {
        Write-PatchLog "Failed to check for missing applications: $_" -Type Error
    }
    
    return $missing
}