Bereitstellen von Anwendungen aus Partnerlösungen für Azure Virtual Desktop mit App Attach

Mehrere Partner bieten Lösungen zur Anwendungsbereitstellung für Azure Virtual Desktop über die Integration mit App Attach an. Dieser Artikel enthält Links zu diesen Partnern, in denen Sie mehr über das Herstellen einer Verbindung mit Azure Virtual Desktop erfahren können. Sie können auch unsere native App Attach-Lösung verwenden, um Anwendungen dynamisch an Ihre Sitzungshosts bereitzustellen.

Partneranwendungsbereitstellung

Die folgenden Partner verfügen über Lösungen zur Anwendungsbereitstellung, die für die Verwendung mit Azure Virtual Desktop genehmigt wurden. Lesen Sie die Dokumentation, um zu erfahren, wie Sie Anwendungen für Azure Virtual Desktop bereitstellen.

Partner Partnerdokumentation Partnersupport
Liquidware Liquidware FlexApp-Dokumentation Liquidware-Unterstützung
Numecent Numecent Cloudpager-Dokumentation Numecent-Unterstützung
Omnissa Dokumentation zu Omnissa App Volumes Omnissa-Unterstützung

Wichtig

Wenn beim Versuch, Anwendungen an Azure Virtual Desktop-Sitzungshosts bereitzustellen, ein Problem auftritt, müssen Sie überprüfen, ob es für Ihren genehmigten Partner eindeutig ist. Sie können überprüfen, ob es sich um ein eindeutiges Problem handelt, indem Sie versuchen, es in der App Attach-Übersicht zu reproduzieren. Wenn Sie das Problem beim Erstanbieter-App Attach nicht reproduzieren können, müssen Sie sich an den Anbieter Ihres Kunden wenden, um Unterstützung zu erhalten.

Integrationsanweisungen für Drittanbieter

Übersicht

Diese Anweisungen decken zwei Integrationsbereiche ab:

  • Steuerungsebene: Die Interaktionen zwischen der Steuerungsebene des Drittanbieters und der Azure Virtual Desktop (AVD)-Steuerungsebene.
  • Agent: Die Interaktionen zwischen dem Drittanbieter-Agent und dem AVD-Agent.

Die Steuerungsebene eines Drittanbieters erstellt Proxyobjekte in der AVD-Steuerungsebene. Der Administrator weist diese Objekte dann AVD-Hostpools und -Benutzern zu. Wenn sich ein Benutzer anmeldet, übergibt AVD die Proxyobjekte, die der Benutzer für den zugewiesenen Hostpool verwenden darf, zum Zeitpunkt der Anmeldung oder kurz vor der Anmeldung an den AVD-Agent. Der AVD-Agent sendet diese Informationen dann über eine Named Pipe an den Drittanbieter-Agent, wodurch die Apps für den Benutzer verfügbar sind.

Ablauf der Steuerungsebene

Voraussetzungen

Die Steuerungsebene eines Drittanbieters muss dem Administrator eine Möglichkeit bieten, ihn zum Erstellen und Lesen von App-Anfügungspaketobjekten in AVD in ihrem Namen zu autorisieren. Die Optionen umfassen einen Dienstprinzipal, Azure Lighthouse oder andere Autorisierungsmethoden. Um der Steuerungsebene eines Drittanbieters Berechtigungen zum Ausführen von CRUD-Aufrufen für App-Anfügungspakete zu erteilen, weisen Sie dem Drittanbieterdienstprinzipal die integrierte RBAC-Rolle Desktop Virtualization App Attach Contributor zu. Der Drittanbieter muss auch über ein Angebot im Azure Marketplace verfügen.

Erstellen von Paketen

Der Drittanbieter ruft die REST-API AVD App Attach Package - Create or Update auf, um eine AppAttachPackagezu erstellen. Der im REST-API-Aufruf festgelegte Name kann nicht geändert werden und muss innerhalb einer Ressourcengruppe eindeutig sein. Füllen Sie die Felder für das Objekt wie folgt aus:

Gespeicherte Daten zum Anfügen von App-Paketen Entsprechendes Drittanbieterfeld
DisplayName [filterbar] Alternativer Name, der für den Administrator im Azure-Portal sichtbar ist
PackageRelativePath [filterbar] Pfad zur ausführbaren Datei zum Ausführen von Anwendungen für Remote-Apps
Pfad zur ausführbaren Datei zum Ausführen von Anwendungen für Remote-Apps Bestimmt, ob ein Paket einsatzbereit ist
Version Zur Verwendung geöffnet
LastUpdated Datum, an dem das Paket zuletzt von Drittanbietern aktualisiert wurde (Azure Standarddatum für die letzte Aktualisierung kann geändert werden, indem der Administrator dem Paket einen Hostpool hinzufügt)
FailHealthCheckOnStagingFailure Gibt an, ob bei der Vorbereitung des Pakets ein Fehler bei der Integritätsprüfung auftreten soll.
KeyVaultURL Zur Verwendung geöffnet
ImagePath [filterbar] Zur Verwendung geöffnet
PackageFullName [filterbar] Zur Verwendung geöffnet
PackageName [filterbar] Zur Verwendung geöffnet
PackageFamilyName [filterbar] Dieses Feld und die App-ID im Anwendungsobjektfeld müssen eine App für einen Benutzer eindeutig identifizieren (erforderlich für die Remote-App-Erstellung).
PackageApplications Von Dritten für denselben Zweck verwendet
PackageApplications:AppUserModelId Befehlszeilenargumente zum Ausführen der Anwendung als Remote-App
PackageApplications:AppId In Verbindung mit dem Paketfamiliennamen sollte eine App eindeutig identifizieren.
PackageApplications:Description Beschreibung der Anwendung
PackageApplications:FriendlyName Anzeigename der Anwendung
PackageApplications:IconImageName Name der Symboldatei
PackageApplications:RawIcon Nicht erforderlich
PackageApplications:RawPng Dies kann mithilfe des folgenden Codeausschnitts erstellt werden.
PackageDependencies Diese sind für den Administrator sichtbar, avd verwendet diese Informationen jedoch nicht.
PackageDependencies:DependencyName Name der Abhängigkeit
PackageDependencies:Publisher Herausgeber der Abhängigkeit
PackageDependencies:MinVersion Version der Abhängigkeit
IsRegularRegistration [filterbar] Zur Verwendung geöffnet
HostPoolReferences [filterbar] Weiterhin von AVD verwendet
CertificateExpiryDate Zur Verwendung geöffnet
CertificateName [filterbar] Zur Verwendung geöffnet
PackageOwnerName [filterbar] Name des Drittanbieters
PackageLookbackUrl URL der Steuerungsebene eines Drittanbieters, auf der das Paket gespeichert ist
CustomData [filterbar] Feld, in dem benutzerdefinierte Daten gespeichert werden können

Der Wert für die RawPng Felder in den Anwendungsobjekten kann mithilfe des folgenden Codes aus einer Symboldatei extrahiert werden:

using (MemoryStream iconInStream = new MemoryStream(iconByteArray))
            {
                using (Icon ic = new Icon(iconInStream, iconSize, iconSize))
                {
                    using (Bitmap bitmap = ic.ToBitmap())
                    {
                        using (MemoryStream pngOutStream = new MemoryStream())
                        {
                            bitmap.Save(pngOutStream, System.Drawing.Imaging.ImageFormat.Png);
                            pngOutStream.Close();
                            return new PngIcon(bitmap.Height, bitmap.Width, pngOutStream.ToArray());
                        }
                    }
                }
            }

Wenn ein Paket mit allen unformatierten PNGs zu groß wäre, erstellen Sie die Anwendungen mit NULL-PNGs. Aktualisieren Sie dann die Anwendungen einzeln, indem Sie die Update-API mit einem einzelnen Anwendungsimage aufrufen. Wiederholen Sie den Vorgang, bis alle Bilder aufgefüllt sind.

Pakete auf dem neuesten Stand halten

Dritte sind dafür verantwortlich, die Pakete in der AVD-Steuerungsebene mit den Paketen in der Steuerungsebene des Drittanbieters synchron zu halten. Verwenden Sie die REST-API App Attach Package – Update , um Pakete zu aktualisieren. Die Felder sind identisch mit der Erstellungs-API.

Remote-Apps

Ein Administrator kann Remote-Apps erstellen, die auf App-Anfügungspakete von Drittanbietern verweisen. Der Administrator gibt den Paketfamiliennamen und die App-ID in der Remote-App an. Dieser Ansatz ermöglicht es dem Administrator oder Dritten, auf eine neue Version zu aktualisieren, ohne den Feed zu ändern. Wenn der Benutzer die App im Feed auswählt, wird die im PackageRelativePath Feld angegebene ausführbare Datei auf dem Computer mit den Parametern ausgeführt, die AppUserModelId im Feld für die ausgewählte Anwendung angegeben sind.

Agent-Flow

Voraussetzungen

Ein Administrator installiert den Drittanbieter-Agent im Image.

Übersicht

Diagramm des Agent-Kommunikationsflusses zwischen dem AVD-Agent und dem Drittanbieter-Agent

Wenn AVD den Host wählt, bei dem sich der Benutzer anmeldet (Orchestrierung), schreibt AVD eine Zuordnung von Benutzer-zu-Paketen als signiertes CMS in eine Named Pipe. Der Agent des Drittanbieters ist der Named Pipe-Client und der Agent von AVD der Server. Wenn der Drittanbieter-Agent gestartet wird, sendet er eine Nachricht mit seinem Paketbesitzernamen und einem hostspezifischen Bezeichner an den AVD-Agent. Der hostspezifische Bezeichner wird verwendet, um Replay-Angriffe zu verhindern. Ein ungültiger Akteur könnte die Berechtigungsnachricht verwenden und auf einem anderen Host wiedergeben, sodass er Berechtigungen auf Hostpoolebene umgehen kann. AVD schließt diesen Bezeichner in das signierte CMS ein, wenn Berechtigungen an den Drittanbieter-Agent kommuniziert werden. AVD sendet dann während der Orchestrierung Berechtigungen, was kurz vor der Anmeldung geschieht. Die gesendete Zuordnung gibt die Berechtigung von Paketen zum Zeitpunkt der Benutzeranmeldung an, aber wir empfehlen den Drittanbietern, dies auch mit Azure RBAC und den Hostpoolverweisen auf das App Attach Package-Objekt zu überprüfen, wenn sie paketänderungen während der Benutzersitzung vornehmen möchten.

Start

Zunächst ermittelt der Drittanbieter-Agent, ob er sich in einem AVD-Szenario befindet, indem er die folgenden beiden Dinge überprüft:

  1. Auf dem Computer wird ein Prozess mit dem Namen RDAgentBootLoader.exe ausgeführt. Dieser Dienst wird auf dem Sitzungshost als SYSTEM ausgeführt und startet den Named Pipe-Server beim Start.

    Screenshot des Task-Managers mit dem RDAgentBootLoader-Prozess, der auf dem Sitzungshost ausgeführt wird.

  2. Vorhandensein dieses Registrierungsschlüssels: Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\RDAgentBootLoader

Der Drittanbieter-Agent sendet eine Nachricht über die Named Pipe an den AVD-Agent. Die Meldung identifiziert den Agent anhand des Paketbesitzernamens, der für die Paketobjekte angegeben ist, die der Drittanbieter erstellt hat.

a) Pipename: "AppAttachThirdPartyServer_Pipe"

b) Herstellen einer Verbindung mit Named Pipe: NamedPipeClientStream.ConnectAsync-Methode (System.IO.Pipes) | Microsoft Learn

for (int attemptNumber = 1; attemptNumber <= _numberOfAttempts; attemptNumber++)
{
    try
    {
        _logger.LogInformation($"Connecting to Named pipe server - Attempt: #{attemptNumber}");
        await ConnectAsync(_pipe, _connectTimeoutMilliseconds, cancellationToken).ConfigureAwait(false);

    }
    catch (TimeoutException)
    {
        if (attemptNumber >= _numberOfAttempts)
        {
            _logger.LogError($"Failed to connect to the named pipe server in {_numberOfAttempts} attempts.");
            throw;
        }

        continue;
    }
    catch (Exception ex)
    {
        _logger.LogError($"Exception occurred: {ex}");
    }

    break;
}

Nachrichtenformat, das vom Drittanbieter während des Startvorgangs an AVD gesendet wird (im JSON-Format).

Verwenden der PipeStream.WriteAsync-Methode (System.IO.Pipes) | Microsoft Learn zum Schreiben von Daten aus dem Named Pipe-Clientstream.

{
  "PackageOwnerName": "packageOwnerName",
  "HostIdentifier": "hostIdentifier",
  "Version": "version"
}

PackageOwnerName – Muss mit dem PackageOwnerName Feld für das App-Anfügungspaketobjekt übereinstimmen.

HostIdentifier – Ein beliebiger Bezeichner (max. 100 Zeichen), der den Host eindeutig identifiziert. Wird verwendet, um Replay-Angriffe zu verhindern.

Version – Version des Kommunikationsprotokolls, derzeit 1.

Benutzerorchestrierung und Anmeldeflow

Wenn sich ein Benutzer anmeldet, sendet der AVD-Agent eine Nachricht an die Benannte Pipe als CMS, das vom AVD-RDP-Zertifikat signiert ist, um anzugeben, welche Pakete der Benutzer verwenden darf. Wenn der Benutzer eine Remote-App verwendet, wird die ausgewählte App zuerst in der Liste angezeigt. Der Drittanbieter sollte alle in der Nachricht enthaltenen Anwendungen verfügbar machen, da ein Benutzer mehrere Remote-Apps in derselben Sitzung öffnen kann. Wenn der Benutzer über keine App-Berechtigungen verfügt, sendet AVD eine leere Liste.

{
   "createdData": "date orchestration request was sent, older requests should be ignored",
   "scenario": "Remote", // can be “Remote” or “Desktop” depending on if the user invoked a remote app or using the session desktop
   “Version”: 1, // version of the data that AVD sends to third parties. We’ll increment this in case anything changes and communicate accordingly.
   "pageNumber": 1, 
   "totalPages": 1,
    HostIdentifier”: “<hostidentifier>” // third party agent sends this to AVD agent during bootup. We relay that information to you so a replay attack can be prevented.
   "PackageSignature": “<signed cms byte array>”
}

Derzeit sind und totalPages statisch, pageNumber da der Grenzwert von 64 KB für Named Pipes nur für frühere Versionen von Windows gilt.

PackageSignature ist das signierte CMS-Bytearray. Wenn sie decodiert ist, weist sie das folgende Format auf:

{
  "UPN": "user@domain.com",
  "HostIdentifier": "hostidentifier",
  "Packages": [
    {
      "OnDemandRegister": false,
      "FailHealthCheckOnStagingFailure": 1,
      "FamilyName": "Mozilla.MozillaFirefox_gmpnhwe7bv6081",
      "PackageName": "Mozilla.MozillaFirefox1",
      "PackageOwnerName": "packageownername",
      "CustomData": "",
      "ImagePath": ""
    },
    {
      "OnDemandRegister": false,
      "FailHealthCheckOnStagingFailure": 1,
      "FamilyName": "b0413c83-dbce-420e-8905-4444",
      "PackageName": "PuTTY",
      "PackageOwnerName": "packageownername",
      "CustomData": "",
      "ImagePath": ""
    }
  ]
}

Der Drittanbieter kann das signierte CMS-Bytearray mit dem folgenden Code überprüfen und decodieren:

// a) Validate certificate

// Build certificate from response
X509Certificate2 cert = new X509Certificate2(System.Text.Encoding.UTF8.GetBytes(payload), new SecureString(), X509KeyStorageFlags.EphemeralKeySet);

// Build certificate chain
X509Chain chain = new X509Chain();
chain.Build(cert);

foreach (X509ChainElement element in chain.ChainElements)
{
    // Here you could validate that the issuer is AVD.
    logger.LogInformation($"{funcName}: Element issuer: {element.Certificate.Issuer};");
    logger.LogInformation($"{funcName}: Element subject: {element.Certificate.Subject}");
    logger.LogInformation($"{funcName}: Element certificate valid until: {element.Certificate.NotAfter}");
    logger.LogInformation($"{funcName}: Element certificate is valid: {element.Certificate.Verify()}");

    if (!element.Certificate.Verify())
    {
        return false;
    }
}

// b) Decode the encoded bytes only if the certificate is valid

byte[] blob = Convert.FromBase64String(payload);
SignedCms signedCms = new SignedCms();
signedCms.Decode(blob);

string result = Encoding.UTF8.GetString(signedCms.ContentInfo.Content);

EntitlementJSONData data = JsonConvert.DeserializeObject<PackagesJSONData>(result);
DateTime utcNow = DateTime.UtcNow;

Telemetrie

Die App Attach-Lösung des Erstanbieters meldet Registrierungsfehler und Prüfpunkte im Kontext einer Verbindung. Diese Berichterstellung hilft Administratoren beim Nachverfolgen von Verbindungsfehlern, die durch ein Problem mit App Attach verursacht werden. Drittanbieter-Apps sollten Fehler auf die gleiche Weise melden, damit Administratoren Einblick in Fehler erhalten.

Drittanbieter melden die folgenden Ereignistypen:

  1. App-Aufruf

  2. App-Exit

  3. Erfolg oder Fehler bei der Installation (Registrierung)

Drittanbieter verwenden die Ereignisablaufverfolgungsprotokollierung (Event Trace Logging, ETL), um Ereignisse mit dem Makro TraceLoggingWriteActivity zu schreiben. AVD nutzt diese Ereignisse und sendet Diagnoseinformationen im Kontext der Benutzerverbindung, auf die der Administrator zugreifen kann.

 

Informationen zu ETW-Drittanbietern

Der Drittanbieter stellt den ETW-Anbieternamen und die GUID am folgenden Registrierungsspeicherort beim Start bereit. Wenn der Drittanbieter-Agent installiert ist, erstellt er diesen Registrierungspfad, falls er nicht vorhanden ist, und fügt Anbieterinformationen als Schlüssel-Wert-Paar hinzu. Der AVD-Agent verarbeitet Rückrufe für die Ereignisse, die von etw-Drittanbietern ausgegeben werden.

Standardmäßig nutzt AVD keine Telemetriedaten vom Drittanbieter-Agent. AVD benötigt den Anbieternamen und die Anbieter-GUID des Drittanbieters, damit sie vor der Bereitstellung in der Produktion getestet werden können.

Screenshot des Registrierungs-Editors mit dem Registrierungspfad des ETW-Anbieters und den Schlüssel-Wert-Paaren.

Informationen, die von Drittanbietern über ETL-Ereignisse gemeldet werden

Erforderliche Informationen Beschreibung Erforderlich
SessionID Die Sitzungs-ID des Benutzers. AVD benötigt es, um die Aktivitäts-ID für Diagnose. Ja
Ereignisname Die AVD-Parser verarbeiten Rückrufe für diese Ereignisse vom Drittanbieter. Ja
Parameter Die einzelnen Ereignistypen finden Sie unten. Ja
  1. Registrierungsfehler

    Die Fehlermeldung muss für den Administrator handlungsfähig sein.

    Der Fehlercode sollte in der Dokumentation des Drittanbieters dokumentiert werden, damit Administratoren das Problem diagnostizieren können.

    TraceLoggingWriteActivity(
               hProvider,
                "Error",
               "{AnyGuid}", // Use Guid.NewGuid() to generate this
               NULL, // The related activity ID for the event, or NULL for no related activity ID.
               TraceLoggingString("SessionID", "SessionID"),
               TraceLoggingString("RegistrationFailure", "Operation "),
               TraceLoggingString("{ThirdPartyName}", "Source"),
               TraceLoggingString("{PackageName}", "PackageName"),
        TraceLoggingString({errorMessage}, "ErrorMessage"),
        TraceLoggingUInt32({code}, "ErrorCode"),
        TraceLoggingBool({InternalOrExternal}, "IsInternalErrorCode"),
    )
    
  2. Registrierung erfolgreich

    TraceLoggingWriteActivity(
             hProvider,
             "Checkpoint",
             "{AnyGuid}", // Use Guid.NewGuid() to generate this
             NULL, // The related activity ID for the event, or NULL for no related activity ID.
             TraceLoggingString("SessionID", "SessionID"),
             TraceLoggingString("RegistrationSuccess", "Operation"),
             TraceLoggingString("{ThirdPartyName}", "Source"),
             TraceLoggingString("{PackageName}", "PackageName"),
        );
    
  3. App-Aufruf

    TraceLoggingWriteActivity(
             hProvider,
             "Checkpoint",
             "{AnyGuid}", // Use Guid.NewGuid() to generate this
             NULL, // The related activity ID for the event, or NULL for no related activity ID.
             TraceLoggingString("SessionID", "SessionID"),
             TraceLoggingString("RegistrationSuccess", "Operation"),
             TraceLoggingString("{ThirdPartyName}", "Source"),
             TraceLoggingString("{PackageName}", "PackageName"),
        );
    
  4. App-Exit

     TraceLoggingWriteActivity(
              hProvider,
              "Checkpoint",
              "{AnyGuid}", // Use Guid.NewGuid() to generate this
              NULL, // The related activity ID for the event, or NULL for no related activity ID.
       TraceLoggingString("SessionID", "SessionID"),
              TraceLoggingString("AppExit", "Operation"),
              TraceLoggingString("{ThirdPartyName}", "Source"),
              TraceLoggingString("{AppName}", "AppName"),
      );
    

Nächste Schritte

Weitere Informationen zu Remotedesktopclients finden Sie unter App Attach (Übersicht).