Private/Get-AppIconFromWinget.ps1
|
function Get-AppIconFromWinget { [CmdletBinding()] [OutputType([bool])] param( [string]$ProjectPath, [string]$FilesPath ) try { # Find IconUrl in any YAML file under FilesPath $iconUrl = $null $yamlFiles = Get-ChildItem -Path $FilesPath -Filter '*.yaml' -ErrorAction SilentlyContinue foreach ($yaml in $yamlFiles) { $content = Get-Content -Path $yaml.FullName -Raw -Encoding UTF8 if ($content -match 'IconUrl:\s*(.+)') { $iconUrl = $matches[1].Trim() break } } if (-not $iconUrl) { Write-Warning 'No WinGet IconUrl found in YAML — keeping default PSADT icon' return $false } # Only ever fetch over HTTPS. An http:// (or any non-https) URL is never contacted — # a plaintext fetch is trivially MITM'd and we'd be writing attacker bytes as SYSTEM's icon. if ($iconUrl -notmatch '^(?i)https://') { Write-Warning "WinGet IconUrl is not HTTPS ('$iconUrl') — refusing to fetch; keeping default PSADT icon" return $false } Write-Verbose "Downloading app icon from WinGet: $iconUrl" # Determine save paths $assetsPath = Join-Path $ProjectPath 'Assets' $psdtAssetsPath = Join-Path $ProjectPath 'PSAppDeployToolkit\Assets' foreach ($folder in @($assetsPath, $psdtAssetsPath)) { if (-not (Test-Path $folder)) { New-Item -ItemType Directory -Path $folder -Force | Out-Null } } $maxBytes = 5MB $wr = Invoke-WebRequest -Uri $iconUrl -UseBasicParsing -ErrorAction Stop # Pre-check the advertised size when present — Content-Length can lie or be absent, so this is # only an early-out; the authoritative check is on the real byte count below. $declaredLen = $null try { $declaredLen = [int64]($wr.Headers['Content-Length'] | Select-Object -First 1) } catch { $declaredLen = $null } if ($declaredLen -and $declaredLen -gt $maxBytes) { Write-Warning "App icon Content-Length ($declaredLen bytes) exceeds the 5 MB cap — keeping default PSADT icon" return $false } $bytes = [byte[]]$wr.Content # Authoritative size check on what actually arrived. if ($null -eq $bytes -or $bytes.Length -eq 0) { Write-Warning 'App icon download was empty — keeping default PSADT icon' return $false } if ($bytes.Length -gt $maxBytes) { Write-Warning "App icon ($($bytes.Length) bytes) exceeds the 5 MB cap — keeping default PSADT icon" return $false } # Validate by magic bytes, NOT the URL extension — never write a non-image over the PSADT default. $iconExt = $null $b = $bytes if ($b.Length -ge 4 -and $b[0] -eq 0x89 -and $b[1] -eq 0x50 -and $b[2] -eq 0x4E -and $b[3] -eq 0x47) { $iconExt = '.png' } # PNG 89 50 4E 47 elseif ($b.Length -ge 4 -and $b[0] -eq 0x00 -and $b[1] -eq 0x00 -and $b[2] -eq 0x01 -and $b[3] -eq 0x00) { $iconExt = '.ico' } # ICO 00 00 01 00 elseif ($b.Length -ge 3 -and $b[0] -eq 0xFF -and $b[1] -eq 0xD8 -and $b[2] -eq 0xFF) { $iconExt = '.jpg' } # JPEG FF D8 FF elseif ($b.Length -ge 2 -and $b[0] -eq 0x42 -and $b[1] -eq 0x4D) { $iconExt = '.bmp' } # BMP 42 4D elseif ($b.Length -ge 3 -and $b[0] -eq 0x47 -and $b[1] -eq 0x49 -and $b[2] -eq 0x46) { $iconExt = '.gif' } # GIF 47 49 46 if (-not $iconExt) { Write-Warning 'Downloaded app icon is not a recognised image (PNG/ICO/JPEG/BMP/GIF) — keeping default PSADT icon' return $false } # Keep the validated bytes as-is so the on-disk asset matches what winget served. (Intune's # largeIcon needs a GENUINE PNG, but that normalization happens once, at publish time, via # Get-Win32ToolkitLargeIconBytes → ConvertTo-Win32ToolkitPngBytes — not on every download here.) $iconDest = Join-Path $assetsPath 'AppIcon.png' [System.IO.File]::WriteAllBytes($iconDest, $bytes) # If the bytes are genuinely an ICO, also keep AppIcon.ico alongside it. if ($iconExt -eq '.ico') { $icoDest = Join-Path $assetsPath 'AppIcon.ico' [System.IO.File]::WriteAllBytes($icoDest, $bytes) } # Also copy to PSAppDeployToolkit\Assets so the toolkit's own default is replaced $psdtIconDest = Join-Path $psdtAssetsPath 'AppIcon.png' Copy-Item -Path $iconDest -Destination $psdtIconDest -Force # Record provenance so the capture-time icon reconcile (finalize) keeps this winget icon over the # one extracted from the install run (the winget-primary precedence decision). Set-Win32ToolkitIconSource -ProjectPath $ProjectPath -Source 'winget' Write-Host "✓ App icon downloaded and applied to Assets\" -ForegroundColor Green return $true } catch { Write-Warning "Could not download app icon: $($_.Exception.Message)" return $false } } |