microsoft / msix-packaging

MSIX SDK
MIT License
963 stars 163 forks source link

[Question] Distribute appinstaller and MSIX via Azure blob storage? #623

Closed MoodieG closed 3 months ago

MoodieG commented 3 months ago

Hey,

I have an automated CD pipeline that signs the MSIX package and uploads both the MSIX package and appinstaller file to an Azure blob storage.

Since my users do not have direct access to the blob storage, I have created a web application that has access to the blob storage with an API that can download the appinstaller or MSIX package. I am able to download both the appinstaller and MSIX package when entering the URL manually in the browser. However, when I launch the appinstaller it displays an error that an app package could not be opened. The reason is "An error occurred while accessing the file from the web. Please try downloading and opening the file locally."

The API returns a FileContentResult and uses the correct MIME. "application/appinstaller" for appinstaller and "application/msix" for the MSIX package.

Any idea what could be wrong?

florelis commented 3 months ago

You can get more details on the error by looking at the logs from App Installer. These are located in %LocalAppData%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\, either as TempState\AILog.txt or LocalState\DiagOutputDir\AppInstaller-<timestamp>.log

Does the .appinstaller file point to your web application or the Azure blob storage? If it is pointing to the blob storage, please try changing it to point to the web application. Regardless of the initial URI used to access the file, App Installer will then request the files from the URIs listed in the file to check for updates.

MoodieG commented 3 months ago

Thanks for the reply.

The .appinstaller file points to the web application for both, the AppInstaller Uri and the MainPackage Uri.

I looked into the logs, and it seems like the app installer is failing due to error code "0x80070138"

Any idea what that error code means?

For reference, I was able to confirm that the .appinstaller is hitting my web application endpoint by placing a break point in the API implementation. If it helps, here is how my implementation looks like:

[HttpGet("{toolName}")]
public async Task<IActionResult> Get(string toolName)
{
    var blobServiceClient = this.storageSettings.GetBlobServiceClient();
    var toolBlob = blobServiceClient.GetBlobContainerClient("tools").GetBlobClient(toolName);

    if (!toolBlob.Exists())
    {
        return this.NotFound();
    }

    var tool = await toolBlob.DownloadContentAsync();
    var contentType = Path.GetExtension(toolName).ToUpperInvariant() switch
    {
        ".APPINSTALLER" => "application/appinstaller",
        ".MSIX" => "application/msix",
        _ => throw new NotSupportedException()
    };

    return this.File(tool.Value.Content.ToArray(), contentType);
}
florelis commented 3 months ago

That error code is ERROR_NO_RANGES_PROCESSED "No ranges for the specified operation were able to be processed."

Does your web application support HTTP range requests?

Generally, the package is not downloaded completely and instead only specific pieces of the file are downloaded. For example, the manifest file and icon for showing the install prompt. Then, during actual installation, only the relevant architecture is fetched. For example, if you have a bundle that contains x86, x64 in English and French, only one arch and locale would be downloaded. To do that, HTTP range requests are used.

The error code sounds to me like an issue with those range requests, that could happen if your web application doesn't support them.

MoodieG commented 3 months ago

Thanks so much! That indeed was the issue. I just had to enable range by changing the last line to

return this.File(tool.Value.Content.ToArray(), contentType, enableRangeProcessing: true);

Unfortunately, now I am getting a new error "Method not allowed". Here are the lines in the log that produced the error:

[Thu Mar 14 14:45:05 2024]{49192} [InstallFromUriAsync] -> Deploying AppInstaller extension file
[Thu Mar 14 14:45:05 2024]{49192} [InstallFromUriAsync] -> Starting AddPackageByUriAsync()
[Thu Mar 14 14:45:05 2024]{49192} DeploymentCompletionCallback
[Thu Mar 14 14:45:05 2024]{49192} DeploymentCompletionCallback -> Install Operation Failed. Error Code: 0x80190195, Extended Error Code: 0x80190195
[Thu Mar 14 14:45:05 2024]{49192} DeploymentCompletionCallback -> fallbackErrorText: Appinstaller operation failed with error code 0x80190195. Detail: Method not allowed (405). (0x80190195)

Any ideas on what could be wrong now?

florelis commented 3 months ago

That is HTTP_E_STATUS_BAD_METHOD "Method not allowed (405)." So it sounds to me like the deployment stack is trying to do some request to your web application that the server does not support. I don't know off the top of my head what it could be, but you may be able to find out by looking at the requests your server is receiving.

MoodieG commented 3 months ago

Hey, thanks a lot for your help.

It turned out that my API did not support an HTTP HEAD request. After fixing that, the "Method not allowed" error got resolved.

I did run into another error, where the app installer was stuck in installing the package. After some debugging, I noticed that my API implementation downloads the MSIX package fully on every range request call. I changed the implementation to cache the package in memory so the API does not keep redownloading the MSIX package when not needed.

A better implementation of my API may be to download only the bytes requested in the range header that I may look into improving in the future.