Configuración de una canalización de CI/CD para automatizar las compilaciones e implementaciones de MSIXSet up a CI/CD pipeline to automate your MSIX builds and deployments

Puedes usar Azure Pipelines para crear compilaciones automatizadas para los proyectos de MSIX.You can use Azure Pipelines to create automated builds for your MSIX project. En este artículo se explica cómo hacerlo en Azure DevOps.This article takes a look at how to do so in Azure DevOps. También te mostraremos cómo realizar estas tareas mediante el uso de la línea de comandos para que puedas integrar con cualquier otro sistema de compilación.We’ll also show you how to perform these tasks by using the command line so that you can integrate with any other build system.

Creación de una nueva canalización de AzureCreate a new Azure Pipeline

Comienza por registrarte en Azure Pipelines si todavía no lo has hecho.Begin by signing up for Azure Pipelines if you haven't done so already.

Posteriormente, crea una canalización que puedas usar para compilar el código fuente.Next, create a pipeline that you can use to build your source code. Para ver un tutorial sobre la creación de una canalización para compilar un repositorio de GitHub, consulta Crea tu primera canalización.For a tutorial about building a pipeline to build a GitHub repository, see Create your first pipeline. Azure Pipelines admite los tipos de repositorios que aparecen en este artículo.Azure Pipelines supports the repository types listed in this article.

Para configurar la canalización de compilación en sí, dirígete al portal de Azure DevOps en dev.azure.com/<organization> y crea un nuevo proyecto.To set up the actual build pipeline, you browse to the Azure DevOps portal at dev.azure.com/<organization> and create a new project. Si no tienes una cuenta, puedes crear una de forma gratuita.If you don’t have an account, you can create one for free. Una vez que haya iniciado sesión y creado un proyecto, puede enviar el código fuente al repositorio de Git que encontrará en https://<organization>@dev.azure.com/<organización >/<project>/_git/<project>, o bien usar cualquier otro proveedor, como GitHub.Once you’ve signed in and created a project, you can either push the source code to the Git repository that’s set up for you at https://<organization>@dev.azure.com/<organization>/<project>/_git/<project>, or use any other provider, such as GitHub. Para elegir la ubicación del repositorio al crear una nueva canalización en el portal, primero haga clic en el botón Canalizaciones y, a continuación, en Nueva canalización.You’ll get to choose the location of your repository when you create a new pipeline in the portal by clicking first on the Pipelines button and then on New Pipeline.

En la pantalla Configuración que aparece a continuación, deberá seleccionar la opción Archivo YAML de Azure Pipelines existente y seleccionar la ruta de acceso al archivo YAML insertado en el repositorio.On the Configure screen that comes next, you should select the Existing Azure Pipelines YAML file option and select the path to the checked-in YAML file in your repository.

Agregar el certificado de proyecto a la biblioteca de archivos segurosAdd your project certificate to the Secure files library

Nota

Si es posible, debes evitar el envío de certificados al repositorio, ya que Git los ignorará de forma predeterminada.You should avoid submitting certificates to your repo if at all possible, and git ignores them by default. Para administrar el control seguro de archivos confidenciales como los certificados, Azure DevOps admite la característica de archivos seguros.To manage the safe handling of sensitive files like certificates, Azure DevOps supports the secure files feature.

Para cargar un certificado para la compilación automatizada:To upload a certificate for your automated build:

  1. En Azure Pipelines, expande Canalizaciones en el panel de navegación y haz clic en Biblioteca.In Azure Pipelines, expand Pipelines in the navigation pane and click Library.
  2. Haz clic en la pestaña Archivos seguros y, a continuación, haz clic en + Archivo seguro.Click the Secure files tab and then click + Secure file.
  3. Busca el archivo de certificado y haz clic en Aceptar.Browse to the certificate file and click OK.
  4. Después de cargar el certificado, selecciónalo para ver sus propiedades.After you upload the certificate, select it to view its properties. En Permisos de canalización, habilita la opción Autorizar para su uso en todas las canalizaciones.Under Pipeline permissions, enable the Authorize for use in all pipelines toggle.
  5. Si la clave privada del certificado tiene una contraseña, se recomienda que almacenes la contraseña en Azure Key Vault y, posteriormente, vincules la contraseña a un grupo de variables.If the private key in the certificate has a password, we recommend that you store your password in Azure Key Vault and then link the password to a variable group. Puedes usar las variables para acceder a la contraseña desde la canalización.You can use the variable to access the password from the pipeline. Ten en cuenta que una contraseña solo se admite para la clave privada; actualmente no se admite el uso de un archivo de certificado protegido por contraseña.Note that a password is only supported for the private key; using a certificate file that is itself password-protected is not currently supported.

Nota

A partir de Visual Studio 2019, ya no se genera un certificado temporal en los proyectos de MSIX.Starting in Visual Studio 2019, a temporary certificate is no longer generated in MSIX projects. Para crear o exportar certificados, usa los cmdlets de PowerShell que se describen en este artículo.To create or export certificates, use the PowerShell cmdlets described in this article.

Configuración de la compilación en el archivo YAMLConfigure the Build in your YAML file

En la siguiente tabla se muestran los diferentes argumentos de MSBuild que puedes definir para configurar una canalización de compilación.The table below lists the different MSBuild arguments you can define to setup your build pipeline.

Argumento de MSBuildMSBuild argument ValorValue DescripciónDescription
AppxPackageDirAppxPackageDir $(Build.ArtifactStagingDirectory)\AppxPackages$(Build.ArtifactStagingDirectory)\AppxPackages Define la carpeta en la que almacenar los artefactos generados.Defines the folder to store the generated artifacts.
AppxBundlePlatformsAppxBundlePlatforms $(Build.BuildPlatform)$(Build.BuildPlatform) Te permite definir las plataformas que se incluirán en el lote.Enables you to define the platforms to include in the bundle.
AppxBundleAppxBundle SiempreAlways Crea un archivo .msixbundle o .appxbundle con los archivos .msix o .appx para la plataforma especificada.Creates an .msixbundle/.appxbundle with the .msix/.appx files for the platform specified.
UapAppxPackageBuildModeUapAppxPackageBuildMode StoreUploadStoreUpload Genera el archivo .msixupload o .appxupload y la carpeta _Test para la instalación de prueba.Generates the .msixupload/.appxupload file and the _Test folder for sideloading.
UapAppxPackageBuildModeUapAppxPackageBuildMode CICI Genera el archivo .msixupload o .appxupload únicamente.Generates the .msixupload/.appxupload file only.
UapAppxPackageBuildModeUapAppxPackageBuildMode SideloadOnlySideloadOnly Genera la carpeta _Test solo para la instalación de prueba.Generates the _Test folder for sideloading only.
AppxPackageSigningEnabledAppxPackageSigningEnabled truetrue Habilita la firma del paquete.Enables package signing.
PackageCertificateThumbprintPackageCertificateThumbprint Huella digital del certificadoCertificate Thumbprint Este valor debe coincidir con la huella digital del certificado de firma o ser una cadena vacía.This value must match the thumbprint in the signing certificate, or be an empty string.
PackageCertificateKeyFilePackageCertificateKeyFile RutaPath La ruta de acceso al certificado que se utilizará.The path to the certificate to use. Se recupera de los metadatos del archivo seguro.This is retrieved from the secure file metadata.
PackageCertificatePasswordPackageCertificatePassword ContraseñaPassword La contraseña para la clave privada del certificado.The password for the private key in the certificate. Se recomienda que almacenes la contraseña en Azure Key Vault y vincules la contraseña a un grupo de variables.We recommend that you store your password in Azure Key Vault and link the password to variable group. Puedes transferir la variable a este argumento.You can pass the variable to this argument.

Antes de compilar el proyecto de empaquetado de la misma manera que lo hace el asistente en Visual Studio mediante la línea de comandos de MSBuild, el proceso de compilación puede crear una versión del paquete MSIX que se está generando por medio de la edición del atributo Version del elemento de paquete en el archivo Package.appxmanifest del paquete.Before building the packaging project the same way the wizard in Visual Studio does using the MSBuild command line, the build process can version the MSIX package that’s being produced by editing the Version attribute of the Package element in the Package.appxmanifest file. En Azure Pipelines, esto se puede lograr mediante el uso de una expresión para establecer una variable de contador que se incremente para cada compilación y un script de PowerShell que use la clase System.Xml.Linq.XDocument en .NET para cambiar el valor del atributo.In Azure Pipelines, this can be achieved by using an expression for setting a counter variable that gets incremented for every build, and a PowerShell script that uses the System.Xml.Linq.XDocument class in .NET to change the value of the attribute.

Archivo YAML de ejemplo que define la canalización de compilación de MSIXSample YAML File that defines the MSIX Build Pipeline

pool: 
  vmImage: windows-2019
  
variables:
  buildPlatform: 'x86'
  buildConfiguration: 'release'
  major: 1
  minor: 0
  build: 0
  revision: $[counter('rev', 0)]
  
steps:
 - powershell: |
     # Update appxmanifest. This must be done before the build.
     [xml]$manifest= get-content ".\Msix\Package.appxmanifest"
     $manifest.Package.Identity.Version = "$(major).$(minor).$(build).$(revision)"    
     $manifest.save("Msix/Package.appxmanifest")
  displayName: 'Version Package Manifest'
  
- task: MSBuild@1
  inputs:
    solution: Msix/Msix.wapproj
    platform: $(buildPlatform)
    configuration: $(buildConfiguration)
    msbuildArguments: '/p:OutputPath=NonPackagedApp
     /p:UapAppxPackageBuildMode=SideLoadOnly  /p:AppxBundle=Never /p:AppxPackageOutput=$(Build.ArtifactStagingDirectory)\MsixDesktopApp.msix /p:AppxPackageSigningEnabled=false'
  displayName: 'Package the App'
  
- task: DownloadSecureFile@1
  inputs:
    secureFile: 'certificate.pfx'
  displayName: 'Download Secure PFX File'
  
- script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\signtool"
    sign /fd SHA256 /f $(Agent.TempDirectory)/certificate.pfx /p secret $(
    Build.ArtifactStagingDirectory)/MsixDesktopApp.msix'
  displayName: 'Sign MSIX Package'
  
- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: drop'

A continuación se muestran los desgloses de las diferentes tareas de compilación definidas en el archivo YAML:Below are breakdowns of the different Build tasks defined in the YAMl file:

Configuración de propiedades de generación de paquetesConfigure package generation properties

La siguiente definición establece el directorio de componentes de compilación, la plataforma y define si se debe compilar o no un lote.The definition below sets the directory of Build components, the platform and defines whether to build a bundle or not.

/p:AppxPackageDir="$(Build.ArtifactStagingDirectory)\AppxPackages\"
/p:UapAppxPackageBuildMode=SideLoadOnly
/p:AppxBundlePlatforms="$(Build.BuildPlatform)"
/p:AppxBundle=Never

Configuración de la firma de paquetesConfigure package signing

Para firmar un paquete MSIX (o appx), la canalización debe recuperar el certificado de firma.To sign the MSIX (or APPX) package the pipeline needs to retrieve the signing certificate. Para ello, agrega una tarea DownloadSecureFile antes de la tarea VSBuild.To do this, add a DownloadSecureFile task prior to the VSBuild task. Esto te proporcionará acceso al certificado de firma a través de signingCert.This will give you access to the signing certificate via signingCert.

- task: DownloadSecureFile@1
  name: signingCert
  displayName: 'Download CA certificate'
  inputs:
    secureFile: '[Your_Pfx].pfx'

Posteriormente, actualiza la tarea MSBuild para hacer referencia al certificado de firma:Next, update the MSBuild task to reference the signing certificate:

- task: MSBuild@1
  inputs:
    platform: 'x86'
    solution: '$(solution)'
    configuration: '$(buildConfiguration)'
    msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" 
                  /p:AppxPackageDir="$(appxPackageDir)" 
                  /p:AppxBundle=Never 
                  p:UapAppxPackageBuildMode=SideLoadOnly 
                  /p:AppxPackageSigningEnabled=true
                  /p:PackageCertificateThumbprint="" 
                  /p:PackageCertificateKeyFile="$(signingCert.secureFilePath)"'

Nota

El argumento PackageCertificateThumbprint se establece intencionadamente en una cadena vacía como precaución.The PackageCertificateThumbprint argument is intentionally set to an empty string as a precaution. Si la huella digital está establecida en el proyecto, pero no coincide con el certificado de firma, se producirá un error en la compilación: Certificate does not match supplied signing thumbprint.If the thumbprint is set in the project but does not match the signing certificate, the build will fail with the error: Certificate does not match supplied signing thumbprint.

Revisión de parámetrosReview parameters

Los parámetros definidos con la sintaxis $() son variables definidas en la definición de compilación y cambiarán en otros sistemas de compilación.The parameters defined with the $() syntax are variables defined in the build definition, and will change in other build systems.

Para ver todas las variables predefinidas, consulta Variables de compilación predefinidas.To view all predefined variables, see Predefined build variables.

Configuración de la tarea para publicar artefactos de compilaciónConfigure the Publish Build Artifacts task

La canalización de MSIX predeterminada no guarda los artefactos generados.The default MSIX pipeline does not save the generated artifacts. Para agregar las capacidades de publicación a la definición de YAML, agrega las siguientes tareas.To add the publish capabilities to your YAML definition, add the following tasks.

- task: CopyFiles@2
  displayName: 'Copy Files to: $(build.artifactstagingdirectory)'
  inputs:
    SourceFolder: '$(system.defaultworkingdirectory)'
    Contents: '**\bin\$(BuildConfiguration)\**'
    TargetFolder: '$(build.artifactstagingdirectory)'

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: drop'
  inputs:
    PathtoPublish: '$(build.artifactstagingdirectory)'

Puedes ver los artefactos generados en la opción Artefactos de la página de resultados de la compilación.You can see the generated artifacts in the Artifacts option of the build results page.

Archivo AppInstaller para la distribución fuera de la StoreAppInstaller file for non-store distribution

Si vas a distribuir la aplicación fuera de la Store, puedes aprovechar el archivo AppInstaller para la instalación y actualización de tu paqueteIf you're distributing your application outside the Store you can take advantage of the AppInstaller file for your package install and updates

Un archivo .appinstaller que buscará archivos actualizados en \server\fooAn .appinstaller File That Will Look for Updated Files on \server\foo

<?xml version="1.0" encoding="utf-8"?>
<AppInstaller xmlns="http://schemas.microsoft.com/appx/appinstaller/2018"
              Version="1.0.0.0"
              Uri="\\server\foo\MsixDesktopApp.appinstaller">
  <MainPackage Name="MyCompany.MySampleApp"
               Publisher="CN=MyCompany, O=MyCompany, L=Stockholm, S=N/A, C=Sweden"
               Version="1.0.0.0"
               Uri="\\server\foo\MsixDesktopApp.msix"
               ProcessorArchitecture="x86"/>
  <UpdateSettings>
    <OnLaunch HoursBetweenUpdateChecks="0" />
  </UpdateSettings>
</AppInstaller>

El elemento UpdateSettings se usa para indicar al sistema cuándo debe buscar actualizaciones y si debe obligar al usuario a actualizar.The UpdateSettings element is used to tell the system when to check for updates and whether to force the user to update. La referencia de esquema completa, incluidos los espacios de nombres admitidos para cada versión de Windows 10, se puede encontrar en la página de documentación en bit.ly/2TGWnCR.The full schema reference, including the supported namespaces for each version of Windows 10, can be found in the docs at bit.ly/2TGWnCR.

Si agregas el archivo .appinstaller al proyecto de empaquetado y estableces la propiedad Acción del paquete en Contenido y la propiedad Copiar en el directorio de salida en Copiar si es posterior, puedes agregar otra tarea de PowerShell al archivo YAML que actualice los atributos de versión de la raíz. y los elementos MainPackage y guarde el archivo actualizado en el directorio de la copia intermedia:If you add the .appinstaller file to the packaging project and set its Package Action property to Content and the Copy to Output Directory property to Copy if newer, you can then add another PowerShell task to the YAML file that updates the Version attributes of the root and MainPackage elements and saves the updated file to the staging directory:

- powershell: |
  [Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq")
  $doc = [System.Xml.Linq.XDocument]::Load(
    "$(Build.SourcesDirectory)/Msix/Package.appinstaller")
  $version = "$(major).$(minor).$(build).$(revision)"
  $doc.Root.Attribute("Version").Value = $version;
  $xName =
    [System.Xml.Linq.XName]
      "{http://schemas.microsoft.com/appx/appinstaller/2018}MainPackage"
  $doc.Root.Element($xName).Attribute("Version").Value = $version;
  $doc.Save("$(Build.ArtifactStagingDirectory)/MsixDesktopApp.appinstaller")
displayName: 'Version App Installer File'

Después distribuirás el archivo .appinstaller a los usuarios finales y les permitirás hacer doble clic en él en lugar del archivo .msix para instalar la aplicación empaquetada.You’d then distribute the .appinstaller file to your end users and let them double-click on this one instead of the .msix file to install the packaged app.

Implementación continuaContinuous Deployment

El propio archivo del instalador de la aplicación es un archivo XML sin compilar que se puede editar después de la compilación, si es necesario.The app installer file itself is an uncompiled XML file that can be edited after the build, if required. Esto facilita su uso al implementar el software en varios entornos y cuando se desea separar la canalización de compilación del proceso de lanzamiento.This makes it easy to use when you deploy your software to multiple environments and when you want to separate the build pipeline from the release process.

Si creas una canalización de versión en el portal de Azure mediante la plantilla "Fase vacía" y usas la canalización de compilación configurada recientemente como origen del artefacto que se va a implementar, podrás agregar la tarea de PowerShell a la fase de versión para cambiar dinámicamente los valores de los dos atributos URI del archivo .appinstaller para reflejar la ubicación en la que se publica la aplicación.If you create a release pipeline in the Azure Portal using the “Empty job” template and use the recently set up build pipeline as the source of the artifact to be deployed, you can then add the PowerShell task to the release stage in order to dynamically change the values of the two Uri attributes in the .appinstaller file to reflect the location to which the app is published.

Una tarea de canalización de versión que modifica los URI del archivo .appinstallerA Release Pipeline Task That Modifies the Uris in the .appinstaller File

- powershell: |
  [Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq")
  $fileShare = "\\filesharestorageccount.file.core.windows.net\myfileshare\"
  $localFilePath =
    "$(System.DefaultWorkingDirectory)\_MsixDesktopApp\drop\MsixDesktopApp.appinstaller"
  $doc = [System.Xml.Linq.XDocument]::Load("$localFilePath")
  $doc.Root.Attribute("Uri").Value = [string]::Format('{0}{1}', $fileShare,
    'MsixDesktopApp.appinstaller')
  $xName =
    [System.Xml.Linq.XName]"{http://schemas.microsoft.com/appx/appinstaller/2018}MainPackage"
  $doc.Root.Element($xName).Attribute("Uri").Value = [string]::Format('{0}{1}',
    $fileShare, 'MsixDesktopApp.appx')
  $doc.Save("$localFilePath")
displayName: 'Modify URIs in App Installer File'

En la tarea anterior, el URI se establece en la ruta de acceso UNC de un recurso compartido de archivos de Azure.In the task above, the URI is set to the UNC path of an Azure file share. Dado que aquí es donde el sistema operativo buscará el paquete MSIX al instalar y actualizar la aplicación, también he agregado otro script de línea de comandos a la canalización de versión que primero asigna el recurso compartido de archivos en la nube a la unidad local Z:\ en el agente de compilación antes de usar el comando xcopy para copiar los archivos .appinstaller y .msix allí:Because this is where the OS will look for the MSIX package when you install and update the app, I’ve also added another command-line script to the release pipeline that first maps the file share in the cloud to the local Z:\ drive on the build agent before it uses the xcopy command to copy the .appinstaller and .msix files there:

- script: |
  net use Z: \\filesharestorageccount.file.core.windows.net\myfileshare
    /u:AZURE\filesharestorageccount
    3PTYC+ociHIwNgCnyg7zsWoKBxRmkEc4Aew4FMzbpUl/
    dydo/3HVnl71XPe0uWxQcLddEUuq0fN8Ltcpc0LYeg==
  xcopy $(System.DefaultWorkingDirectory)\_MsixDesktopApp\drop Z:\ /Y
  displayName: 'Publish App Installer File and MSIX package'

Si hospedas tu propia instancia local de Azure DevOps Server, podrás, lógicamente, publicar los archivos en tu propio recurso compartido de red interno.If you host your own on-premises Azure DevOps Server, you may of course publish the files to your own internal network share.

Si decides publicarlos en un servidor web, puedes indicar a MSBuild que genere un archivo .appinstaller con versiones y una página HTML que contenga un vínculo de descarga y otra información sobre la aplicación empaquetada proporcionando algunos argumentos adicionales en el archivo YAML:If you choose to publish to a Web server, you can tell MSBuild to generate a versioned .appinstaller file and an HTML page that contains a download link and some information about the packaged app by supplying a few additional arguments in the YAML file:

- task: MSBuild@1
  inputs:
    solution: Msix/Msix.wapproj
    platform: $(buildPlatform)
    configuration: $(buildConfiguration)
    msbuildArguments: '/p:OutputPath=NonPackagedApp /p:UapAppxPackageBuildMode=SideLoadOnly  /p:AppxBundle=Never /p:GenerateAppInstallerFile=True
/p:AppInstallerUri=http://yourwebsite.com/packages/ /p:AppInstallerCheckForUpdateFrequency=OnApplicationRun /p:AppInstallerUpdateFrequency=1 /p:AppxPackageDir=$(Build.ArtifactStagingDirectory)/'
  displayName: 'Package the App'

El archivo HTML generado incluye un hipervínculo con el prefijo del esquema de activación del protocolo ms-appinstaller independiente del explorador:The generated HTML file includes a hyperlink prefixed with the browser-agnostic ms-appinstaller protocol activation scheme:

<a href="ms-appinstaller:?source=
  http://yourwebsite.com/packages/Msix_x86.appinstaller ">Install App</a>

Si configuras una canalización de versión que publique el contenido de la carpeta de entrega en la intranet o en cualquier otro sitio web, y el servidor web admite solicitudes de intervalo de bytes y está configurado correctamente, los usuarios finales podrán usar este vínculo para instalar directamente la aplicación sin descargar el paquete MSIX primero.If you set up a release pipeline that publishes the contents of the drop folder to your intranet or any other Web site, and the Web server supports byte-range requests and is configured properly, your end users can use this link to directly install the app without downloading the MSIX package first.