The current implementation fetches app icons by scraping Google Image Search (google.com/search?tbm=isch), which blocks automated/non-browser requests and returns no .Images array. This causes a null reference error on every app lookup, crashing the icon download step and leaving the icon field empty.
The fix replaces the single Google scraping call with a 4-source fallback chain:
- Winget manifest – checks the locale.en-US.yaml for a declared icon URL
- Clearbit Logo API – resolves a logo via the app's domain
- Google Favicon S2 API – fetches the publisher favicon (official API, no blocking)
- DuckDuckGo Image Search – last resort scraping fallback
The domain used for sources 2 and 3 is derived in priority order from PackageUrl (if present in the manifest), then the app name, then the publisher name — ensuring broad coverage even for packages with minimal manifest metadata.
I replaced lines 520 -524 with this code (AI generated):
#Get App icon - tries 4 sources in order, falls back gracefully
$AppInfo.Icon = "$Location\$($AppInfo.ID).jpg"
$IconUrl = $null
# Domain ermitteln - Priorität: 1) PackageUrl, 2) App-Name, 3) Publisher-Name
$PublisherDomain = $null
if ($AppInfo.PackageUrl) {
try { $PublisherDomain = ([System.Uri]$AppInfo.PackageUrl.Trim()).Host -replace '^www\.', '' } catch { }
}
if (!$PublisherDomain -and $AppInfo.PackageName) {
$AppDomainGuess = ($AppInfo.PackageName -replace '[^a-zA-Z0-9]', '').ToLower()
if ($AppDomainGuess.Length -ge 3) {
$PublisherDomain = "$AppDomainGuess.com"
}
}
if (!$PublisherDomain -and $AppInfo.Publisher) {
$DomainGuess = ($AppInfo.Publisher -replace '\s+(LLC|Inc|Ltd|GmbH|Corp|Co|AG|BV|SAS|SRL|AB)\.?$', '' -replace '[^a-zA-Z0-9]', '').ToLower()
if ($DomainGuess.Length -ge 3) {
$PublisherDomain = "$DomainGuess.com"
}
}
# Source 1: Winget-pkgs GitHub locale manifest (IconUrl field, no blocking)
if (!$IconUrl) {
try {
$IconManifestUrl = "https://raw.githubusercontent.com/microsoft/winget-pkgs/master/manifests/$($AppFirstChar)/$($AppUrl)/$($AppVersion)/$($AppInfo.ID).locale.en-US.yaml"
$LocaleManifest = (Invoke-WebRequest -UseBasicParsing -Uri $IconManifestUrl -ErrorAction Stop).Content
$IconLineMatch = $LocaleManifest | Select-String -Pattern "(?m)^\s*-\s*Url:\s*(https?://\S+\.(?:png|jpg|jpeg|ico|svg))" | Select-Object -First 1
if ($IconLineMatch) {
$IconUrl = $IconLineMatch.Matches.Groups[1].Value.Trim()
Write-Host "Icon source: Winget Manifest"
}
}
catch { }
}
# Source 2: Clearbit Logo API
if (!$IconUrl -and $PublisherDomain) {
try {
$ClearbitUrl = "https://logo.clearbit.com/$PublisherDomain"
$TestResponse = Invoke-WebRequest -UseBasicParsing -Uri $ClearbitUrl -ErrorAction Stop
if ($TestResponse.StatusCode -eq 200 -and $TestResponse.RawContentLength -gt 1000) {
$IconUrl = $ClearbitUrl
$AppInfo.Icon = "$Location\$($AppInfo.ID).png"
Write-Host "Icon source: Clearbit"
}
}
catch { }
}
# Source 3: Google Favicon S2 API
if (!$IconUrl -and $PublisherDomain) {
try {
$FaviconUrl = "https://www.google.com/s2/favicons?domain=$PublisherDomain&sz=128"
$FaviconResponse = Invoke-WebRequest -UseBasicParsing -Uri $FaviconUrl -ErrorAction Stop
if ($FaviconResponse.StatusCode -eq 200 -and $FaviconResponse.RawContentLength -gt 500) {
$IconUrl = $FaviconUrl
$AppInfo.Icon = "$Location\$($AppInfo.ID).png"
Write-Host "Icon source: Google Favicon S2"
}
}
catch { }
}
# Source 4: DuckDuckGo Image Search (last resort)
if (!$IconUrl) {
try {
$SearchQuery = [Uri]::EscapeDataString("$($AppInfo.PackageName) software logo")
$DDGUrl = "https://duckduckgo.com/?q=$SearchQuery&iax=images&ia=images"
$WebResponse = Invoke-WebRequest -Uri $DDGUrl -UseBasicParsing -ErrorAction Stop
$ImgMatches = [regex]::Matches($WebResponse.Content, '"thumbnail":"(https?://[^"]+)"')
if ($ImgMatches.Count -gt 1) {
$IconUrl = $ImgMatches[1].Groups[1].Value
Write-Host "Icon source: DuckDuckGo Image Search"
}
}
catch { }
}
# Download icon, validate min. 1KB
if ($IconUrl) {
try {
Invoke-WebRequest -Uri $IconUrl -OutFile $AppInfo.Icon -UseBasicParsing -ErrorAction Stop
if ((Get-Item $AppInfo.Icon -ErrorAction SilentlyContinue).Length -lt 1024) {
Remove-Item $AppInfo.Icon -Force -ErrorAction SilentlyContinue
$AppInfo.Icon = $null
}
}
catch {
$AppInfo.Icon = $null
}
}
else {
$AppInfo.Icon = $null
}
This fixed the issue for me
The current implementation fetches app icons by scraping Google Image Search (google.com/search?tbm=isch), which blocks automated/non-browser requests and returns no .Images array. This causes a null reference error on every app lookup, crashing the icon download step and leaving the icon field empty.
The fix replaces the single Google scraping call with a 4-source fallback chain:
The domain used for sources 2 and 3 is derived in priority order from PackageUrl (if present in the manifest), then the app name, then the publisher name — ensuring broad coverage even for packages with minimal manifest metadata.
I replaced lines 520 -524 with this code (AI generated):
This fixed the issue for me