Public/Get-WinGetPackages.ps1

Function Get-WinGetPackages {
  <#
  .SYNOPSIS
      Retrieves a list of installed packages from WinGet package manager.
  .DESCRIPTION
      This function queries WinGet to retrieve information about installed packages.
      It can filter results based on package name and optionally check for available updates.
  .PARAMETER Name
      Specifies a wildcard pattern to filter the package names.
      Default is "*", which matches all packages.
  .PARAMETER IncludeAvailableUpdates
      If specified, checks for available updates for each package.
  .INPUTS
      None. This function does not accept pipeline input.
  .OUTPUTS
      System.Collections.Generic.List[PSCustomObject]
      Returns a list of custom objects representing WinGet packages, with properties such as:
      Name, Id, Version, Source, UpdateAvailable, PackageManager, and PSTypeName.
  .EXAMPLE
      PS> Get-WinGetPackages -Name "Microsoft*"
      Retrieves all WinGet packages with names starting with "Microsoft".
  .EXAMPLE
      PS> Get-WinGetPackages -IncludeAvailableUpdates
      Retrieves all WinGet packages and checks for available updates.
  .NOTES
      This function requires WinGet to be installed and available in the system PATH.
      WinGet must be properly configured and have access to package sources.
  #>

  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $false, Position = 0, ValueFromPipelineByPropertyName = $true)]
    [string]$Name = "*",
    [Parameter(Mandatory = $false)]
    [switch]$IncludeAvailableUpdates
  )

  Begin {
    Write-Verbose "[BEGIN]: Get-WinGetPackages"
    if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
      Write-Warning "WinGet is not installed or not in PATH"
      return
    }
  }

  Process {
    Write-Verbose "[PROCESS]: Get-WinGetPackages"
    try {
      $WinGetOutput = winget list --accept-source-agreements | Out-String
      $Lines = $WinGetOutput -split "`n" | Where-Object {
        $_ -and $_ -notmatch '^-+$' -and $_ -notmatch 'Name.*Id.*Version' -and $_.Trim()
      }

      $Results = foreach ($Line in $Lines[2..($Lines.Count - 1)]) {
        if ($Line -match '^\s*(.+?)\s{2,}(.+?)\s{2,}(.+?)(?:\s{2,}(.+?))?\s*$') {
          $PackageName = $Matches[1].Trim()
          $PackageId = $Matches[2].Trim()
          $Version = $Matches[3].Trim()
          $Source = if ($Matches[4]) { $Matches[4].Trim() } else { "Unknown" }

          if ($PackageName -like $Name) {
            [PSCustomObject]@{
              Name            = $PackageName
              Id              = $PackageId
              Version         = $Version
              Source          = $Source
              UpdateAvailable = $false
              PackageManager  = 'WinGet'
              PSTypeName      = 'PSSystemDiagnostics.WinGetPackage'
            }
          }
        }
      }

      if ($IncludeAvailableUpdates -and $Results) {
        Write-Verbose "Checking for available updates..."
        try {
          $UpgradeOutput = winget upgrade --accept-source-agreements | Out-String
          $UpgradeLines = $UpgradeOutput -split "`n"

          foreach ($Result in $Results) {
            $UpgradeLine = $UpgradeLines | Where-Object { $_ -match [regex]::Escape($Result.Id) }
            if ($UpgradeLine) {
              $Result.UpdateAvailable = $true
            }
          }
        } catch {
          Write-Warning "Failed to check for updates: $_"
        }
      }
    } catch {
      Write-Error "Failed to retrieve WinGet packages: $_"
      $Results = @()
    }
  }

  End {
    Write-Verbose "[END]: Get-WinGetPackages"
    if ($Results) {
      $Results = $Results | Sort-Object Name
    } else {
      Write-Warning "No WinGet packages found matching criteria."
    }
    Write-Verbose "Total WinGet packages found: $($Results.Count)"
    Write-Verbose "Get-WinGetPackages completed."
    return $Results
  }
}