Konfigurieren der CI/CD-Pipeline mit YAML-Datei

In der folgenden Tabelle sind die verschiedenen MSBuild-Argumente aufgeführt, die für die Einrichtung der Buildpipeline definiert werden können.

MSBuild-Argument Wert Beschreibung
AppxPackageDir $(Build.ArtifactStagingDirectory)\AppxPackages Definiert den Ordner, in dem die generierten Artefakte gespeichert werden.
AppxBundlePlatforms $(Build.BuildPlatform) Ermöglicht es dir, die Plattformen zu definieren, die in das Bundle aufgenommen werden sollen.
AppxBundle Immer Erstellt ein .msixbundle/.appxbundle mit den MSIX-/APPX-Dateien für die angegebene Plattform.
UapAppxPackageBuildMode StoreUpload Generiert die .msixupload-/.appxupload-Datei und den Ordner _Test für das Querladen.
UapAppxPackageBuildMode CI Generiert nur die .msixupload-/.appxupload-Datei.
UapAppxPackageBuildMode SideloadOnly Generiert den Ordner _Test nur für das Querladen.
AppxPackageSigningEnabled wahr Aktiviert das Signieren von Paketen.
PackageCertificateThumbprint Zertifikatfingerabdruck Dieser Wert muss mit dem Fingerabdruck im Signaturzertifikat übereinstimmen oder eine leere Zeichenfolge sein.
PackageCertificateKeyFile Pfad Der Pfad zu dem zu verwendenden Zertifikat. Dieser wird aus den sicheren Dateimetadaten abgerufen.
PackageCertificatePassword Kennwort Das Kennwort für den privaten Schlüssel im Zertifikat. Es wird empfohlen, das Kennwort in Azure Key Vault zu speichern und es mit der Variablengruppe zu verknüpfen. Du kannst die Variable an dieses Argument übergeben.

Bevor das Verpackungsprojekt auf dieselbe Weise wie der Assistent in Visual Studio mit der MSBuild-Befehlszeile erstellt wird, kann der Buildvorgang für das zu erstellende MSIX-Paket durch Bearbeiten des Versionsattributs des Package-Elements in der Datei „Package.appxmanifest“ die Versionsverwaltung durchführen. In Azure Pipelines kann dies durch die Verwendung eines Ausdrucks zum Festlegen einer Zählervariablen erreicht werden, die bei jedem Build inkrementiert wird, sowie durch ein PowerShell-Skript, das die Klasse „System.Xml.Linq.XDocument“ in .NET verwendet, um den Wert des Attributs zu ändern.

YAML-Beispieldatei, die die MSIX-Buildpipeline definiert

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'

Nachfolgend werden die verschiedenen Buildaufgaben, die in der YAMl-Datei definiert sind, aufgeschlüsselt:

Konfigurieren der Eigenschaften der Paketerstellung

Die folgende Definition legt das Verzeichnis der Buildkomponenten und die Plattform fest und definiert, ob ein Bündel erstellt werden soll.

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

Konfigurieren der Paketsignierung

Die Pipeline muss zum Signieren des MSIX-Pakets (oder APPX-Pakets) das Signaturzertifikat abrufen. Füge dazu vor dem VSBuild-Task einen DownloadSecureFile-Task hinzu. Damit erhältst du über signingCert Zugriff auf das Signaturzertifikat.

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

Aktualisiere als nächstes den MSBuild-Task, um auf das Signaturzertifikat zu verweisen:

- 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)"'

Hinweis

Das PackageCertificateThumbprint-Argument wird vorsichtshalber absichtlich auf eine leere Zeichenfolge festgelegt. Wenn der Fingerabdruck im Projekt festgelegt ist, aber nicht mit dem Signaturzertifikat übereinstimmt, tritt der folgende Fehler beim Build auf: Certificate does not match supplied signing thumbprint.

Überprüfen der Parameter

Die mit der $()-Syntax definierten Parameter sind Variablen, die in der Builddefinition definiert sind und sich in anderen Buildsystemen unterscheiden.

Unter Vordefinierte Buildvariablen sind alle vordefinierten Variablen aufgeführt.

Konfigurieren des Tasks „Buildartefakte veröffentlichen“

Die MSIX-Standardpipeline speichert die generierten Artefakte nicht. Um deiner YAML-Definition die Veröffentlichungsfunktionen hinzuzufügen, füge die folgenden Tasks hinzu.

- 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)'

Du kannst die generierten Artefakte in der Option Artefakte der Seite der Buildergebnisse sehen.

AppInstaller-Datei für die Verteilung außerhalb des Store

Wenn du deine Anwendung außerhalb des Store verteilst, kannst du die Vorteile der AppInstaller-Datei für deine Paketinstallation und Updates nutzen.

Eine .appinstaller-Datei, die auf „server\foo“ nach aktualisierten Dateien sucht

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

Das UpdateSettings-Element wird verwendet, um dem System mitzuteilen, wann es nach Updates suchen soll und ob der Benutzer zur Aktualisierung verpflichtet werden soll. Die vollständige Schemareferenz, einschließlich der unterstützten Namespaces für jede Version von Windows 10, findest du in den Dokumenten unter bit.ly/2TGWnCR.

Wenn du die .appinstaller-Datei zum Verpackungsprojekt hinzufügst und ihre „Paketaktion“-Eigenschaft auf „Inhalt“ und die „In Ausgabeverzeichnis kopieren“-Eigenschaft auf „Kopieren, wenn neuer“ festlegst, kannst du dann der YAML-Datei einen weiteren PowerShell-Task hinzufügen, der die Versionsattribute der Stamm- und MainPackage-Elemente aktualisiert und die aktualisierte Datei im Stagingverzeichnis speichert:

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

Du würdest dann die .appinstaller-Datei an deine Endbenutzer verteilen und sie auf diese anstelle der MSIX-Datei doppelklicken lassen, um die verpackte Anwendung zu installieren.

Continuous Deployment

Die App-Installationsdatei selbst ist eine nicht kompilierte XML-Datei, die bei Bedarf nach der Erstellung bearbeitet werden kann. Dadurch kann sie einfach verwendet werden, wenn du deine Software in mehreren Umgebungen bereitstellst und die Buildpipeline vom Releaseprozess trennen möchtest.

Wenn du eine Releasepipeline im Azure-Portal mit der Vorlage „Leerer Auftrag“ erstellst und die kürzlich eingerichtete Buildpipeline als Quelle des bereitzustellenden Artefakts verwendest, kannst du dann die PowerShell-Aufgabe zur Releasephase hinzufügen, um die Werte der beiden URI-Attribute in der .appinstaller-Datei dynamisch zu ändern, damit sie den Speicherort widerspiegeln, an dem die Anwendung veröffentlicht wird.

Ein Releasepipelinetask, der die URIs in der .appinstaller-Datei ändert

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

Im obigen Task wird der URI auf den UNC-Pfad einer Azure-Dateifreigabe festgelegt. Da das Betriebssystem hier nach dem MSIX-Paket sucht, wenn du die Anwendung installierst und aktualisierst, habe ich auch ein weiteres Befehlszeilenskript zur Releasepipeline hinzugefügt, das zuerst die Dateifreigabe in der Cloud dem lokalen Laufwerk Z:\ des Build-Agents zuordnet, bevor es den Befehl xcopy verwendet, um die .appinstaller- und .msix-Dateien dorthin zu kopieren:

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

Wenn du deinen eigenen lokalen Azure DevOps Server hostest, kannst du die Dateien natürlich auf deiner eigenen internen Netzwerkfreigabe veröffentlichen.

Wenn du dich für die Veröffentlichung auf einem Webserver entscheidest, kannst du MSBuild anweisen, eine .appinstaller-Datei mit Versionsangabe und eine HTML-Seite zu generieren, die einen Downloadlink und einige Informationen über die verpackte App enthält, indem du ein paar zusätzliche Argumente in der YAML-Datei angibst:

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

Die generierte HTML-Datei enthält einen Link, dem das browseragnostische ms-appinstaller-Protokollaktivierungsschema vorangestellt ist:

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

Wenn du eine Releasepipeline einrichtest, die den Inhalt des Ablageordners in deinem Intranet oder auf einer anderen Website veröffentlicht, und der Webserver Bytebereichsanforderungen unterstützt und richtig konfiguriert ist, können deine Endbenutzer diesen Link verwenden, um die App direkt zu installieren, ohne zuerst das MSIX-Paket herunterzuladen.