Обновление пакетов приложений, опубликованных не в Store, с помощью кода

При отправке приложения в виде пакета MSIX можно программным способом запустить обновление приложения. Если приложение развертывается за пределами Store, достаточно проверить наличие новой версии приложения на сервере и установить ее. Способ применения обновлений зависит от того, выполняется ли развертывание пакета приложения с помощью файла Установщика приложений. Чтобы применить обновления из кода, пакет приложения должен объявить возможность packageManagement. Обратите внимание, что это требуется для сценария взаимодействия между издателями, но управление собственным приложением должно работать без объявления этой возможности.

В этой статье приведены примеры, демонстрирующие способы объявления возможности packageManagement в манифесте пакета и применения обновления из кода. В первом разделе рассматривается, как сделать это с использованием файла Установщика приложений, а во втором разделе — как это сделать, когда файл Установщика приложений не используется. В последнем разделе рассматривается, как убедиться, что приложение перезапускается после применения обновления.

Добавление возможности PackageManagement Capability в манифест пакета

Чтобы использовать API PackageManager, в манифесте пакета приложения необходимо объявить ограниченную возможность packageManagement.

<Package>
...

  <Capabilities>
    <rescap:Capability Name="packageManagement" />
  </Capabilities>
  
...
</Package>

Обновление пакетов, развернутых с помощью файла Установщика приложений

При развертывании приложения с помощью файла Установщика приложений все обновления, управляемые кодом, должны использовать API файла Установщика приложений. Это гарантирует, что обычные обновления с помощью файла Установщика приложений будут работать и дальше. Чтобы инициировать обновление на основе Установщика приложений, вы можете использовать PackageManager.AddPackageByAppInstallerFileAsync или PackageManager.RequestAddPackageByAppInstallerFileAsync. Проверить доступность обновления можно с помощью API Package.CheckUpdateAvailabilityAsync. Ниже приведен пример кода:

using Windows.Management.Deployment;

public async void CheckForAppInstallerUpdatesAndLaunchAsync(string targetPackageFullName, PackageVolume packageVolume)
{
    // Get the current app's package for the current user.
    PackageManager pm = new PackageManager();
    Package package = pm.FindPackageForUser(string.Empty, targetPackageFullName);

    PackageUpdateAvailabilityResult result = await package.CheckUpdateAvailabilityAsync();
    switch (result.Availability)
    {
        case PackageUpdateAvailability.Available:
        case PackageUpdateAvailability.Required:
            //Queue up the update and close the current instance
            await pm.AddPackageByAppInstallerFileAsync(
            new Uri("https://trial3.azurewebsites.net/HRApp/HRApp.appinstaller"),
            AddPackageByAppInstallerOptions.ForceApplicationShutdown,
            packageVolume);
            break;
        case PackageUpdateAvailability.NoUpdates:
            // Close AppInstaller.
            await ConsolidateAppInstallerView();
            break;
        case PackageUpdateAvailability.Unknown:
        default:
            // Log and ignore error.
            Logger.Log($"No update information associated with app {targetPackageFullName}");
            // Launch target app and close AppInstaller.
            await ConsolidateAppInstallerView();
            break;
    }
}

Обновление пакетов, развернутых без файла Установщика приложений

Проверка наличия обновлений на сервере

Если вы не используете файл Установщика приложений для развертывания пакета приложения, сначала необходимо проверить доступность новой версии приложения. С помощью следующего примера можно проверить, используется ли сервере более поздняя версия пакета, чем текущая версия приложения (в этом примере для демонстрации используется тестовый сервер).

using Windows.Management.Deployment;

//check for an update on my server
private async void CheckUpdate(object sender, TappedRoutedEventArgs e)
{
    WebClient client = new WebClient();
    Stream stream = client.OpenRead("https://trial3.azurewebsites.net/HRApp/Version.txt");
    StreamReader reader = new StreamReader(stream);
    var newVersion = new Version(await reader.ReadToEndAsync());
    Package package = Package.Current;
    PackageVersion packageVersion = package.Id.Version;
    var currentVersion = new Version(string.Format("{0}.{1}.{2}.{3}", packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision));

    //compare package versions
    if (newVersion.CompareTo(currentVersion) > 0)
    {
        var messageDialog = new MessageDialog("Found an update.");
        messageDialog.Commands.Add(new UICommand(
            "Update",
            new UICommandInvokedHandler(this.CommandInvokedHandler)));
        messageDialog.Commands.Add(new UICommand(
            "Close",
            new UICommandInvokedHandler(this.CommandInvokedHandler)));
        messageDialog.DefaultCommandIndex = 0;
        messageDialog.CancelCommandIndex = 1;
        await messageDialog.ShowAsync();
    } else
    {
        var messageDialog = new MessageDialog("Did not find an update.");
        await messageDialog.ShowAsync();
    }
}

Примечание.

targetPackageFileName представляет полное имя упакованного приложения. (Пример: Contoso.HeadTrax_1.0.0.0_x64__PublisherHash)

Примените обновление

Определив, что обновление доступно, вы можете поставить его в очередь для скачивания и установки с помощью API AddPackageAsync. Кроме того, он подходит для установки дополнительного пакета при условии, что основной пакет уже установлен на устройстве. Обновление будет применено при следующем завершении работы приложения. После перезапуска приложения новая версия будет доступна пользователю. Ниже приведен пример кода:


// Queue up the update and close the current app instance.
private async void CommandInvokedHandler(IUICommand command)
{
    if (command.Label == "Update")
    {
        PackageManager packagemanager = new PackageManager();
        await packagemanager.AddPackageAsync(
            new Uri("https://trial3.azurewebsites.net/HRApp/HRApp.msix"),
            null,
            AddPackageOptions.ForceApplicationShutdown
        );
    }
}

Автоматический перезапуск приложения после обновления

Если приложение является приложением UWP, передача AddPackageByAppInstallerOptions.ForceApplicationShutdown ИЛИ AddPackageOptions.ForceTargetAppShutdown в ходе применения обновления приведет к перезапуску приложения после выключения и обновления. Если вы используете приложения, не являющиеся приложениями UWP, вам нужно вызвать RegisterApplicationRestart перед применением обновления.

Вам нужно вызвать RegisterApplicationRestart прежде чем ваше приложение начнет завершение работы. Ниже приведен пример использования служб взаимодействия для вызова собственного метода в C#:

 // Register the active instance of an application for restart in your Update method
 uint res = RelaunchHelper.RegisterApplicationRestart(null, RelaunchHelper.RestartFlags.NONE);

Пример вспомогательного класса для вызова собственного метода RegisterApplicationRestart в C#:

using System;
using System.Runtime.InteropServices;

namespace MyEmployees.Helpers
{
    class RelaunchHelper
    {
        #region Restart Manager Methods
        /// <summary>
        /// Registers the active instance of an application for restart.
        /// </summary>
        /// <param name="pwzCommandLine">
        /// A pointer to a Unicode string that specifies the command-line arguments for the application when it is restarted.
        /// The maximum size of the command line that you can specify is RESTART_MAX_CMD_LINE characters. Do not include the name of the executable
        /// in the command line; this function adds it for you.
        /// If this parameter is NULL or an empty string, the previously registered command line is removed. If the argument contains spaces,
        /// use quotes around the argument.
        /// </param>
        /// <param name="dwFlags">One of the options specified in RestartFlags</param>
        /// <returns>
        /// This function returns S_OK on success or one of the following error codes:
        /// E_FAIL for internal error.
        /// E_INVALIDARG if rhe specified command line is too long.
        /// </returns>
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        internal static extern uint RegisterApplicationRestart(string pwzCommandLine, RestartFlags dwFlags);
        #endregion Restart Manager Methods

        #region Restart Manager Enums
        /// <summary>
        /// Flags for the RegisterApplicationRestart function
        /// </summary>
        [Flags]
        internal enum RestartFlags
        {
            /// <summary>None of the options below.</summary>
            NONE = 0,

            /// <summary>Do not restart the process if it terminates due to an unhandled exception.</summary>
            RESTART_NO_CRASH = 1,
            /// <summary>Do not restart the process if it terminates due to the application not responding.</summary>
            RESTART_NO_HANG = 2,
            /// <summary>Do not restart the process if it terminates due to the installation of an update.</summary>
            RESTART_NO_PATCH = 4,
            /// <summary>Do not restart the process if the computer is restarted as the result of an update.</summary>
            RESTART_NO_REBOOT = 8
        }
        #endregion Restart Manager Enums

    }
}