Private/Get-InstallerFileInfo.ps1
|
function Get-InstallerFileInfo { [CmdletBinding()] param([string]$FilesPath) $installerInfo = @{ FileName = $null Type = $null FullPath = $null } # Check for MSI files first (Zero-Config MSI priority) $msiFiles = @(Get-ChildItem -Path $FilesPath -Filter "*.msi" -File) if ($msiFiles) { # There is NO content inspection here — with several candidates we simply take the first BY NAME. Say so # out loud: the file chosen here becomes AppConfig.Installer.FileName, i.e. the binary the generated # deploy script runs on every targeted device. Silently guessing is how you ship the wrong installer. if ($msiFiles.Count -gt 1) { Write-Warning "Files\ contains $($msiFiles.Count) .msi files ($(($msiFiles | ForEach-Object Name) -join ', ')) — using '$($msiFiles[0].Name)' as the installer (first by name; nothing here can tell which is the real one). If that is wrong, remove the extra .msi from Files\." } $installerInfo.FileName = $msiFiles[0].Name $installerInfo.Type = 'msi' $installerInfo.FullPath = $msiFiles[0].FullName return $installerInfo } # Check for EXE files. # Exclude only PSADT'S OWN binaries. There used to be a '*Setup*' exclusion here, which silently # discarded the installer for any app whose EXE is called setup.exe / acme-setup.exe / VLCSetup.exe — # i.e. the single most common name an installer has. Such a project failed outright with # "No installer (msi/exe/msix/appx) detected", and a manual app could not be packaged at all. $exeFiles = @(Get-ChildItem -Path $FilesPath -Filter "*.exe" -File | Where-Object { $_.Name -notlike "*Invoke-AppDeployToolkit*" -and $_.Name -notlike "*ServiceUI*" }) if ($exeFiles) { # Same ambiguity as MSI above — first BY NAME, no content inspection. Rename-InstallerFile now refuses # to collapse two same-extension files (it used to delete one), so this is where a genuinely ambiguous # Files\ folder surfaces. Name every candidate rather than quietly picking one. if ($exeFiles.Count -gt 1) { Write-Warning "Files\ contains $($exeFiles.Count) .exe files ($(($exeFiles | ForEach-Object Name) -join ', ')) — using '$($exeFiles[0].Name)' as the installer (first by name; nothing here can tell which is the real one). If that is wrong, remove the extra .exe from Files\." } # Visibility: an EXE outranks package files by design (detection order), but a vendor bundle # with App.msix + helper.exe would silently get the EXE flow (no identity uninstall, possibly # "hard app") — name the ignored package so the operator can remove the stray EXE if the # package is the real installer. $shadowed = @(Get-ChildItem -Path $FilesPath -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in (Get-Win32ToolkitInstallerExtension -PackagesOnly) }) if ($shadowed.Count -gt 0) { Write-Warning "Files\ contains both an EXE and a package file ($(($shadowed | ForEach-Object Name) -join ', ')) — using the EXE '$($exeFiles[0].Name)' as the installer. If the MSIX/APPX is the real installer, remove the stray EXE from Files\." } $installerInfo.FileName = $exeFiles[0].Name $installerInfo.Type = 'exe' $installerInfo.FullPath = $exeFiles[0].FullName return $installerInfo } # Check for Appx-family packages: .msix/.appx AND their bundles. A bundle installs and uninstalls # exactly like a plain package (Add-AppxProvisionedPackage takes a bundle path; removal is by the # same Name), so it reports the SAME Type — 'msix' for .msix/.msixbundle, 'appx' for .appx/.appxbundle. # Type is install semantics; bundle-ness is content-detected later by Get-Win32ToolkitMsixIdentity. # Families derived from the single extension->Type owner (Get-Win32ToolkitInstallerType), so this # and Download-OldVersionInstaller can never disagree about what a '.msixbundle' is. foreach ($family in @( @{ Type = 'msix'; Extensions = @((Get-Win32ToolkitInstallerExtension -PackagesOnly) | Where-Object { (Get-Win32ToolkitInstallerType -Extension $_) -eq 'msix' }) } @{ Type = 'appx'; Extensions = @((Get-Win32ToolkitInstallerExtension -PackagesOnly) | Where-Object { (Get-Win32ToolkitInstallerType -Extension $_) -eq 'appx' }) } )) { $pkgFiles = @(Get-ChildItem -Path $FilesPath -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in $family.Extensions } | Sort-Object Name) if ($pkgFiles.Count -eq 0) { continue } # Same first-BY-NAME ambiguity the .msi/.exe branches call out — and it bites harder here: # `winget download` fetches an app's framework dependencies (VCLibs, WinUI, .NET Native) into # Files\ alongside the app, so a dependency package can sort ahead of the real installer and # silently BECOME the installer. Name every candidate rather than quietly picking one. if ($pkgFiles.Count -gt 1) { Write-Warning "Files\ contains $($pkgFiles.Count) $($family.Type) package files ($(($pkgFiles | ForEach-Object Name) -join ', ')) — using '$($pkgFiles[0].Name)' as the installer (first by name; nothing here can tell which is the real one). Framework dependencies downloaded next to the app look exactly like this — if '$($pkgFiles[0].Name)' is not the app, remove the extra package(s) from Files\." } $installerInfo.FileName = $pkgFiles[0].Name $installerInfo.Type = $family.Type $installerInfo.FullPath = $pkgFiles[0].FullName return $installerInfo } return $installerInfo } |