Actualización de paquetes de aplicaciones no publicadas en Store a partir del código

Al enviar la aplicación como MSIX, puedes iniciar mediante programación su actualización. Si implementas la aplicación fuera de Store, solo tienes que buscar una nueva versión de la aplicación en el servidor e instalarla. La forma de aplicar la actualización depende de si va a implementar el paquete de la aplicación mediante un archivo de Instalador de aplicación o no. Para aplicar actualizaciones desde el código, el paquete de la aplicación debe declarar la funcionalidad packageManagement. Tenga en cuenta que esto es necesario para un escenario entre editores, pero la administración de su propia aplicación debería funcionar sin tener que declarar la funcionalidad.

En este artículo se proporcionan ejemplos que muestran cómo declarar la funcionalidad packageManagement en el manifiesto del paquete y cómo aplicar una actualización desde el código. La primera sección examina cómo hacerlo si usa el archivo Instalador de aplicación y la segunda sección trata sobre cómo hacerlo cuando no se usa el archivo Instalador de aplicación. En la última sección se examina cómo asegurarse de que la aplicación se reinicia después de aplicar una actualización.

Adición de la funcionalidad PackageManagement al manifiesto del paquete

Para usar las API de PackageManager, la aplicación debe declarar la packageManagementfuncionalidad restringida en el manifiesto de paquete.

<Package>
...

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

Actualización de paquetes implementada mediante un archivo de Instalador de aplicación

Si va a implementar la aplicación mediante el archivo Instalador de aplicación, las actualizaciones basadas en código que realice deben usar las API de archivo de Instalador de aplicación. De este modo, se garantiza que las actualizaciones del archivo de Instalador de aplicación normales seguirán funcionando. Para iniciar una actualización basada en el Instalador de aplicación desde el código, puede usar PackageManager.AddPackageByAppInstallerFileAsync o PackageManager.RequestAddPackageByAppInstallerFileAsync. Puede comprobar si hay una actualización disponible mediante la API Package.CheckUpdateAvailabilityAsync. A continuación se muestra el código de ejemplo:

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;
    }
}

Actualización de paquetes implementada sin un archivo de Instalador de aplicación

Búsqueda de actualizaciones en el servidor

Si no usa el archivo de Instalador de aplicación para implementar el paquete de la aplicación, el primer paso es comprobar directamente si hay disponible una nueva versión de la aplicación. En el ejemplo siguiente se comprueba si la versión del paquete en un servidor es mayor que la versión actual de la aplicación (en este ejemplo se hace referencia a un servidor de prueba con fines de demostración).

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();
    }
}

Nota:

targetPackageFileName es representativo del nombre completo de la aplicación empaquetada. (Ejemplo: Contoso.HeadTrax_1.0.0.0_x64__PublisherHash)

Aplicar la actualización

Una vez que determine que hay una actualización disponible, puede ponerla en cola para su descarga e instalación mediante la API AddPackageAsync. También debería funcionar para instalar un paquete opcional siempre y cuando el paquete principal ya esté instalado en el dispositivo. La actualización se aplicará la próxima vez que se cierre la aplicación. Después de reiniciar la aplicación, la nueva versión estará disponible para el usuario. A continuación se muestra el código de ejemplo:


// 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
        );
    }
}

Reinicio automático de la aplicación después de una actualización

Si se trata de una aplicación para UWP, al pasar AddPackageByAppInstallerOptions.ForceApplicationShutdown O AddPackageOptions.ForceTargetAppShutdown al aplicar una actualización, se debe programar el reinicio de la aplicación después del apagado y la actualización. En el caso de las aplicaciones que no son para UWP, debe llamar a RegisterApplicationRestart antes de aplicar la actualización.

Debe llamar a RegisterApplicationRestart antes de que la aplicación comience a apagarse. A continuación se muestra un ejemplo de cómo hacerlo mediante servicios de interoperabilidad para llamar al método nativo en C#:

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

Un ejemplo de la clase auxiliar para llamar al método nativo RegisterApplicationRestart en 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

    }
}