Configuración de la canalización de CI/CD con el archivo YAML

En la siguiente tabla se muestran los diferentes argumentos de MSBuild que puedes definir para configurar una canalización de compilación.

Argumento de MSBuild Valor Descripción
AppxPackageDir $(Build.ArtifactStagingDirectory)\AppxPackages Define la carpeta en la que almacenar los artefactos generados.
AppxBundlePlatforms $(Build.BuildPlatform) Te permite definir las plataformas que se incluirán en el lote.
AppxBundle Siempre Crea un archivo .msixbundle o .appxbundle con los archivos .msix o .appx para la plataforma especificada.
UapAppxPackageBuildMode StoreUpload Genera el archivo .msixupload o .appxupload y la carpeta _Test para la instalación de prueba.
UapAppxPackageBuildMode CI Genera el archivo .msixupload o .appxupload únicamente.
UapAppxPackageBuildMode SideloadOnly Genera la carpeta _Test solo para la instalación de prueba.
AppxPackageSigningEnabled true Habilita la firma del paquete.
PackageCertificateThumbprint Huella digital del certificado Este valor debe coincidir con la huella digital del certificado de firma o ser una cadena vacía.
PackageCertificateKeyFile Ruta La ruta de acceso al certificado que se utilizará. Se recupera de los metadatos del archivo seguro.
PackageCertificatePassword Contraseña La contraseña para la clave privada del certificado. Se recomienda que almacenes la contraseña en Azure Key Vault y vincules la contraseña a un grupo de variables. Puedes transferir la variable a este argumento.

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. 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.

Archivo YAML de ejemplo que define la canalización de compilación de MSIX

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:

Configuración de propiedades de generación de paquetes

La siguiente definición establece el directorio de componentes de compilación, la plataforma y define si se debe compilar o no un lote.

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

Configuración de la firma de paquetes

Para firmar un paquete MSIX (o appx), la canalización debe recuperar el certificado de firma. Para ello, agrega una tarea DownloadSecureFile antes de la tarea VSBuild. Esto te proporcionará acceso al certificado de firma a través de 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:

- 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. 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.

Revisión de parámetros

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.

Para ver todas las variables predefinidas, consulta Variables de compilación predefinidas.

Configuración de la tarea para publicar artefactos de compilación

La canalización de MSIX predeterminada no guarda los artefactos generados. Para agregar las capacidades de publicación a la definición de YAML, agrega las siguientes tareas.

- 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.

Archivo AppInstaller para la distribución fuera de la Store

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 paquete

Un archivo .appinstaller que buscará archivos actualizados en \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. 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.

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:

- 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.

Implementación continua

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. 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.

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.

Una tarea de canalización de versión que modifica los URI del archivo .appinstaller

- 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. 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í:

- 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.

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:

- 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:

<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.