I'm trying to automate downloading the latest PowerShell Windows x64 MSI installer with PowerShell. Invoke-WebRequest completes successfully, but launching the downloaded file produces: "This installation package could not be opened. Verify that the package exists and that you can access it, or contact the application vendor to verify that this is a valid Windows Installer package."
The strange part is that the downloaded file appears identical to the one obtained manually from the release page. Comparing the files byte-for-byte shows no differences, and their SHA-256 hashes match as well.
The script currently obtains release metadata, selects an asset matching `*-win*x64.msi`, extracts the filename, and saves the asset with Invoke-WebRequest:
`$repository = 'PowerShell/PowerShell'`
`$fileNamePattern = '*-win*x64.msi'`
`$downloadPath = 'C:Temp'`
`$releasesUri = "https://api.github.com/repos/$repository/releases/latest"`
`$downloadUrl = ((Invoke-RestMethod -Method Get -Uri $releasesUri).assets | Where-Object Name -Like $fileNamePattern).browser_download_url`
`$fileName = [System.IO.Path]::GetFileName($downloadUrl)`
`$destination = Join-Path $downloadPath $fileName`
`Invoke-WebRequest -Uri $downloadUrl -OutFile $destination`
Unblocking the file, running it elevated, double-clicking it, and invoking `msiexec /i` all produce the same result. What else could cause Windows Installer to reject this file when the downloaded and manually downloaded files have matching contents?
4 Answers
Compare hashes rather than relying only on a file comparison tool. `Get-FileHash $destination -Algorithm SHA256` should match the checksum published with the release and the manually downloaded installer. If the hashes match, Invoke-WebRequest did not corrupt or transform the MSI; the cause is probably elsewhere, such as the Windows installation environment, security software, or the way the installer is being launched.
First make sure the API call really targets `/releases/latest`. Without that suffix, you can get the full release list, and the filter may return multiple URLs instead of one. Select a single asset explicitly before building the destination path. For example, use `Where-Object Name -Like $fileNamePattern | Select-Object -First 1`, then verify that `$downloadUrl` contains exactly one URL and that the filename is the expected MSI.
That was one issue in an earlier version of the script. The actual request uses `/latest`, and I also verified that the selected URL points to the expected x64 MSI.
Try bypassing the shell association and run Windows Installer directly: `msiexec.exe /i "C:TempPowerShell-7.6.4-win-x64.msi" /L*V "C:Temppowershell-install.log"`. The verbose log may reveal whether the problem is path access, permissions, a Windows Installer service issue, or a prerequisite check. Testing the same file in a clean VM or Windows Sandbox can also distinguish an installer problem from something specific to the current machine.
The download itself should work with a straightforward scalar path, such as `$destination = Join-Path $env:TEMP $fileName; Invoke-WebRequest -Uri $downloadUrl -OutFile $destination`. The MSI does not normally need to be run as administrator, and `Unblock-File` only removes the downloaded-file security zone marker; it cannot repair an invalid package. Since the hashes match and the manual copy behaves differently only if launched on another system, investigate the local OS, antivirus, filesystem permissions, and Windows Installer logs.

The script-created file and the manually downloaded file have matching SHA-256 hashes, so the contents are definitely the same.