Entrega de aplicaciones de soluciones de asociados a Azure Virtual Desktop con App Attach

Varios asociados proporcionan soluciones de entrega de aplicaciones a Azure Virtual Desktop mediante la integración con App Attach. En este artículo se proporcionan vínculos a esos asociados, donde puede obtener más información sobre cómo conectarse a Azure Virtual Desktop. También puede usar nuestra solución nativa app attach para entregar aplicaciones dinámicamente a los hosts de sesión.

Entrega de aplicaciones asociadas

Los siguientes asociados tienen soluciones de entrega de aplicaciones que se han aprobado para su uso con Azure Virtual Desktop. Visite su documentación para obtener información sobre cómo entregar aplicaciones a Azure Virtual Desktop.

Partner Documentación del asociado Compatibilidad con asociados
Liquidware Documentación de Liquidware FlexApp Compatibilidad con Liquidware
Numecent Documentación de Numecent Cloudpager Compatibilidad con Numecent
Omnissa Documentación de Omnissa App Volumes Compatibilidad con Omnissa

Importante

Si se produce un problema al intentar entregar aplicaciones a Azure hosts de sesión de Virtual Desktop, debe comprobar si es único para el asociado aprobado. Para comprobar si se trata de un problema único, intente reproducirlo en Información general sobre la asociación de aplicaciones. Si no puede reproducir el problema en la asociación de aplicaciones de primera persona, debe ponerse en contacto con el proveedor de su cliente para obtener soporte técnico.

Instrucciones de integración de terceros

Información general

Estas instrucciones abarcan dos áreas de integración:

  • Plano de control: las interacciones entre el plano de control de terceros y el plano de control Azure Virtual Desktop (AVD).
  • Agente: interacciones entre el agente de terceros y el agente de AVD.

El plano de control de terceros crea objetos proxy en el plano de control AVD. A continuación, el administrador asigna esos objetos a los grupos de hosts de AVD y a los usuarios. Cuando un usuario inicia sesión, AVD pasa los objetos proxy que el usuario tiene permiso para usar en el grupo de hosts asignado al agente de AVD en o poco antes de la hora de inicio de sesión. A continuación, el agente de AVD envía esa información a través de una canalización con nombre al agente de terceros, lo que hace que las aplicaciones estén disponibles para el usuario.

Flujo del plano de control

Requisitos previos

El plano de control de terceros debe proporcionar una manera para que el administrador le autorice a crear y leer objetos de paquete de asociación de aplicaciones en AVD en su nombre. Las opciones incluyen una entidad de servicio, Azure Lighthouse u otros métodos de autorización. Para conceder permisos al plano de control de terceros para realizar llamadas CRUD para paquetes de asociación de aplicaciones, asigne el rol integrado RBAC Desktop Virtualization App Attach Contributor a la entidad de servicio de terceros. El tercero también debe tener una oferta en Azure Marketplace.

Creación de paquetes

El tercero llama al paquete de asociación de aplicaciones de AVD: cree o actualice la API REST para crear un AppAttachPackage. El nombre establecido en la llamada a la API REST no se puede cambiar y debe ser único dentro de un grupo de recursos. Rellene los campos del objeto de la siguiente manera:

Datos almacenados del paquete de asociación de aplicaciones Campo de terceros equivalente
DisplayName [filtrable] Nombre alternativo visible para el administrador en Azure Portal
PackageRelativePath [filtrable] Ruta de acceso al archivo ejecutable para ejecutar aplicaciones para aplicaciones remotas
Ruta de acceso al archivo ejecutable para ejecutar aplicaciones para aplicaciones remotas Determina si un paquete está listo para su uso
Versión Abrir para su uso
LastUpdated El paquete de fechas fue actualizado por última vez por terceros (Azure fecha de última actualización predeterminada podría cambiarse si el administrador agrega un grupo de hosts al paquete)
FailHealthCheckOnStagingFailure Indica si un problema al preparar el paquete debe producir un error en la comprobación de estado.
KeyVaultURL Abrir para su uso
ImagePath [filtrable] Abrir para su uso
PackageFullName [filtrable] Abrir para su uso
PackageName [filtrable] Abrir para su uso
PackageFamilyName [filterable] Este campo más el appid en el campo de objeto de aplicación deben identificar de forma única una aplicación para un usuario (necesario para la creación remota de la aplicación)
PackageApplications Utilizado por terceros con el mismo propósito
PackageApplications:AppUserModelId Argumentos de línea de comandos para ejecutar la aplicación como aplicación remota
PackageApplications:AppId Junto con el nombre de familia del paquete, debe identificar de forma única una aplicación
PackageApplications:Description Descripción de la aplicación
PackageApplications:FriendlyName Nombre descriptivo de la aplicación
PackageApplications:IconImageName Nombre del archivo de icono
PackageApplications:RawIcon No es necesario
PackageApplications:RawPng Esto se puede crear mediante el fragmento de código siguiente.
PackageDependencies Estos serán visibles para el administrador, pero AVD no usará esta información.
PackageDependencies:DependencyName Nombre de la dependencia
PackageDependencies:Publisher Publicador de dependencia
PackageDependencies:MinVersion Versión de la dependencia
IsRegularRegistration [filtrable] Abrir para su uso
HostPoolReferences [filtrable] Todavía lo usa AVD
CertificateExpiryDate Abrir para su uso
CertificateName [filtrable] Abrir para su uso
PackageOwnerName [filtrable] Nombre de terceros
PackageLookbackUrl Dirección URL del plano de control de terceros donde se almacena el paquete
CustomData [filtrable] Campo donde se pueden almacenar datos personalizados

El valor de los RawPng campos de los objetos de aplicación se puede extraer de un archivo de icono mediante el código siguiente:

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

Si un paquete con todos los PNG sin procesar sería demasiado grande, cree las aplicaciones con PNG null. A continuación, actualice las aplicaciones de una en una llamando a la API de actualización con una sola imagen de aplicación. Repita la operación hasta que se rellenen todas las imágenes.

Mantener actualizados los paquetes

Los terceros son responsables de mantener sincronizados los paquetes del plano de control avd con los paquetes del plano de control de terceros. Use el paquete de asociación de aplicaciones: actualice la API REST para actualizar los paquetes. Los campos son los mismos que la API de creación.

Aplicaciones remotas

Un administrador puede crear aplicaciones remotas que apunten a paquetes de asociación de aplicaciones de terceros. El administrador especifica el nombre de la familia del paquete y el identificador de la aplicación en la aplicación remota. Este enfoque permite al administrador o a terceros actualizar a una nueva versión sin cambiar la fuente. Cuando el usuario selecciona la aplicación en la fuente, el ejecutable especificado en el PackageRelativePath campo se ejecuta en la máquina con los parámetros especificados en el AppUserModelId campo para la aplicación elegida.

Flujo del agente

Requisitos previos

Un administrador instala el agente de terceros en la imagen.

Información general

Diagrama que muestra el flujo de comunicación del agente entre el agente de AVD y el agente de terceros

Cuando AVD elige el host en el que el usuario iniciará sesión (orquestación), AVD escribirá un mapa de usuario a paquetes en una canalización con nombre como un CMS firmado. El agente de terceros será el cliente de canalización con nombre y el agente de AVD será el servidor. Cuando se inicie el agente de terceros, enviará un mensaje al agente de AVD con el nombre del propietario del paquete y un identificador específico del host. El identificador específico del host se usa para evitar ataques de reproducción: un actor incorrecto podría usar el mensaje de derechos y reproducirlo en otro host, lo que le permite omitir los derechos de nivel de grupo de hosts. AVD incluirá este identificador en el CMS firmado al comunicar derechos al agente de terceros. A continuación, AVD enviará derechos durante la orquestación, lo que sucede poco antes del inicio de sesión. El mapa enviado indicará el derecho de los paquetes en el momento del inicio de sesión del usuario, pero animamos a los terceros a que también lo revaliden con Azure RBAC y el grupo de hosts haga referencia al objeto App Attach Package si desea realizar cambios en el paquete durante la sesión del usuario.

Arranque

En primer lugar, el agente de terceros determina si está en un escenario de AVD comprobando estas dos cosas:

  1. Un proceso denominado RDAgentBootLoader.exe se ejecuta en la máquina. Este servicio se ejecuta como SYSTEM en el host de sesión e inicia el servidor de canalización con nombre en el arranque.

    Captura de pantalla del Administrador de tareas que muestra el proceso RDAgentBootLoader que se ejecuta en el host de sesión.

  2. Presencia de esta clave del Registro: Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\RDAgentBootLoader

El agente de terceros envía un mensaje al agente avd a través de la canalización con nombre. El mensaje identifica al agente por el nombre de propietario del paquete especificado en los objetos de paquete que creó el tercero.

a) Nombre de canalización: "AppAttachThirdPartyServer_Pipe"

b) Conexión a una canalización con nombre: Método NamedPipeClientStream.ConnectAsync (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;
}

Formato de mensaje enviado por terceros a AVD durante el arranque (en formato JSON).

Uso del método PipeStream.WriteAsync (System.IO.Pipes) | Microsoft Learn para escribir datos desde el flujo de cliente de canalización con nombre.

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

PackageOwnerName : debe coincidir con el PackageOwnerName campo del objeto de paquete de asociación de aplicaciones.

HostIdentifier : cualquier identificador (máximo de 100 caracteres) que identifique de forma única el host. Se usa para evitar ataques de reproducción.

Version : versión del protocolo de comunicación, actualmente 1.

Flujo de orquestación e inicio de sesión del usuario

Cuando un usuario inicia sesión, el agente de AVD envía un mensaje a la canalización con nombre como un CMS firmado por el certificado RDP de AVD para especificar qué paquetes tiene permiso para usar el usuario. Si el usuario usa una aplicación remota, la aplicación seleccionada aparece primero en la lista. El tercero debe hacer que todas las aplicaciones incluidas en el mensaje estén disponibles porque un usuario puede abrir varias aplicaciones remotas en la misma sesión. Si el usuario no tiene permisos de aplicación, AVD envía una lista vacía.

{
   "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>”
}

Actualmente, pageNumber y totalPages son estáticos porque el límite de 64 KB para canalizaciones con nombre solo se aplica a versiones anteriores de Windows.

PackageSignature es la matriz de bytes de CMS firmada. Cuando se descodifica, tiene el siguiente formato:

{
  "UPN": "[email protected]",
  "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": ""
    }
  ]
}

El tercero puede validar y descodificar la matriz de bytes de CMS firmada con el código siguiente:

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

Telemetría

La solución de asociación de aplicaciones de primera persona notifica errores de registro y puntos de control en el contexto de una conexión. Este informe ayuda a los administradores a realizar un seguimiento de los errores de conexión causados por un problema con la asociación de aplicaciones. Las aplicaciones de terceros deben notificar errores de la misma manera para que los administradores tengan visibilidad sobre los errores.

Los terceros informan de los siguientes tipos de eventos:

  1. Invocación de la aplicación

  2. Salida de la aplicación

  3. Error o éxito de instalación (registro)

Los terceros usan el registro de seguimiento de eventos (ETL) para escribir eventos con la macro TraceLoggingWriteActivity. AVD consume esos eventos y envía información de diagnóstico en el contexto de la conexión del usuario a la que el administrador puede acceder.

 

Información del proveedor ETW de terceros

El tercero proporciona el nombre del proveedor ETW y el GUID en la siguiente ubicación del Registro en el arranque. Cuando se instala el agente de terceros, crea esta ruta de acceso del Registro si no existe y agrega información del proveedor como par clave-valor. El agente de AVD controla las devoluciones de llamada en los eventos emitidos por los proveedores ETW de terceros.

De forma predeterminada, AVD no consume telemetría del agente de terceros. AVD necesita el nombre del proveedor y el GUID del proveedor del tercero para que se pueda probar antes de la implementación en producción.

Captura de pantalla del editor del Registro que muestra la ruta de acceso del registro del proveedor ETW y los pares de valor de clave.

Información notificada por terceros a través de eventos ETL

Información necesaria Descripción Obligatorio
SessionID Identificador de sesión del usuario. AVD lo necesita para invertir la búsqueda del identificador de actividad para diagnósticos.
Nombre del evento Los analizadores de AVD controlan las devoluciones de llamada en estos eventos desde el proveedor de terceros.
Parameters Consulte a continuación cada tipo de evento.
  1. Error de registro

    El mensaje de error debe ser accionable para el administrador.

    El código de error debe documentarse en la documentación del tercero para que los administradores puedan diagnosticar el problema.

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

    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. Invocación de la aplicación

    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. Salida de la aplicación

     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"),
      );
    

Pasos siguientes

Obtenga más información sobre los clientes de Escritorio remoto en Introducción a App Attach.