Checking Horizon Cloud Services Connectivity from Horizon Edge Gateway

It is important to remember that the Horizon Edge Gateway is not only used for enabling integration with Horizon Cloud Services, but it is also required in many environments for Horizon subscription licensing.

In modern deployments of Omnissa Horizon, subscription licenses are delivered through the Horizon Cloud control plane rather than through traditional license keys. To enable this mechanism, each Horizon pod must be connected to the cloud service using a Horizon Edge Gateway appliance.

When using subscription-based licensing:

• The Horizon Edge Gateway connects the Horizon pod to Horizon Cloud Services.
• The control plane synchronizes and delivers the Horizon license entitlement.
• No manual license key entry is required on the Connection Server.

For this reason, outbound connectivity to the Horizon Cloud endpoints is critical, not only for cloud services but also for maintaining a valid licensing state in subscription-based environments.

If the Edge Gateway cannot reach the required endpoints due to firewall restrictions, proxy configuration, or SSL inspection, administrators may experience issues such as:

• License synchronization failures
• Horizon services reporting licensing errors
• Problems during onboarding or control plane communication

In contrast, environments using term/perpetual license keys entered directly in the Horizon Console do not require a Horizon Edge Gateway, because licensing is handled locally within the pod.

 

Checking the connectivity

When deploying environments based on Omnissa Horizon integrated with Horizon Cloud Services (HCS), proper outbound connectivity from the Horizon Edge Gateway is critical.

This is the link for Port and Protocol Requirements for Deploying Horizon 8 Edge:

Port and Protocol Requirements for Deploying Horizon 8 Edge

In many enterprise environments, outbound communication toward the internet is tightly controlled through firewalls, proxies, or SSL inspection systems. While this is perfectly reasonable from a security perspective, it often introduces connectivity issues if the required endpoints are not properly allowed.

Even more commonly, the configuration works initially but breaks later because firewall rules are modified, security appliances are upgraded, or SSL inspection policies change over time.

For this reason, whenever issues arise with Horizon Cloud integration, the first thing to verify is whether the Edge Gateway can still reach the required Horizon Cloud Services endpoints.

Fortunately, the Edge Gateway provides a built-in diagnostic tool to help with exactly this scenario.

.

.

.

Using diagnostic.sh to Test Connectivity

The Horizon Edge Gateway includes a script called diagnostic.sh

This script performs a series of connectivity checks toward the Horizon Cloud Services endpoints required for proper operation.

The script validates:

• DNS resolution
• HTTPS connectivity
• Reachability of the required Horizon Cloud endpoints
• TLS handshake validation

This makes it extremely useful when troubleshooting issues caused by:

• Missing firewall rules
• SSL inspection interfering with TLS connections
• DNS resolution problems
• Proxy misconfigurations

.

Running the Diagnostic Script

Log in to the Horizon Edge Gateway via VM Web Console with the root account:

And run this command:

The script will test connectivity against multiple Horizon Cloud endpoints and return the results directly to the console.

A successful test typically shows results like:

If a problem exists, the script will clearly indicate which test failed.

.

Why This Check Is Important

Connectivity to Horizon Cloud Services is essential for several platform features, including:

• Edge Gateway registration
• Horizon control plane communication
• Monitoring and service updates
• Validate the Horizon License

Because firewall and security policies frequently evolve in enterprise environments, it’s a good practice to periodically validate connectivity using the diagnostic tool.

Running the diagnostic script can quickly confirm whether the issue is related to networking or to another component of the Horizon environment.

.

.

.

.

 Spoiler:

There may be proxy problems. For which proxy it is used, you can read this configuration file:

/opt/horizon/var/data/proxy.conf

.

.

.

Checking Horizon Cloud Services Connectivity from Horizon Edge Gateway

Add icon to App Pool

 

# --- Step 1: Get admin credentials securely ---
$cred = Get-Credential
$domain = "yourdomain"

# --- Step 2: Build login payload ---
$loginBody = @{
    username = $cred.UserName
    password = $cred.GetNetworkCredential().Password
    domain   = $domain
} | ConvertTo-Json

# --- Step 3: API base URL and cert bypass for testing ---
$restApiBaseUrl = "https://horizon.domain.com/rest"
Add-Type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate,
                                          WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

# --- Step 4: Authenticate and get token ---
$tokenResponse = Invoke-RestMethod -Method POST -Uri "$restApiBaseUrl/login" -Body $loginBody -ContentType "application/json"
$token = $tokenResponse.access_token
$headers = @{ "Authorization" = "Bearer $token" }

# --- Step 5: Load and encode icon file ---
$iconFilePath = "C:\path\file.png"
$iconBytes = [System.IO.File]::ReadAllBytes($iconFilePath)
$base64Icon = [System.Convert]::ToBase64String($iconBytes)

# --- Step 6: Upload the icon ---
$iconBody = @{
    data = $base64Icon
    height = 256
    width = 256
} | ConvertTo-Json -Depth 2

$response = Invoke-RestMethod -Method POST -Uri "$restApiBaseUrl/inventory/v1/application-icons" -Headers $headers -Body $iconBody -ContentType "application/json"

# --- Step 7: Retrieve the icon ID from the uploaded base64 data ---
$iconId = ((Invoke-RestMethod -Method GET -Uri "$restApiBaseUrl/inventory/v1/application-icons/custom-icons" -Headers $headers -ContentType "application/json") | Select-Object data,id | Where-Object {$_.data -eq $base64Icon}).id

# --- Step 8: Get application ID by name (e.g., Notepad) ---
$appFilterJSON = @{
    type = "Equals"
    name = "name"
    value = "Notepad"
}
$appFilterURLEncoded = [System.Web.HttpUtility]::UrlEncode(($appFilterJSON | ConvertTo-Json -Depth 2 -Compress))
$appId = (Invoke-RestMethod -Method GET -Uri "$restApiBaseUrl/inventory/v4/application-pools?filter=$appFilterURLEncoded" -Headers $headers).id

# --- Step 9: Associate the custom icon with the application ---
$appIconAssocBody = @{
    application_pool_ids = @("$appId")
    icon_id = "$iconId"
} | ConvertTo-Json -Depth 2

Invoke-RestMethod -Method POST -Uri "$restApiBaseUrl/inventory/v1/application-pools/action/associate" -Headers $headers -Body $appIconAssocBody -ContentType "application/json"

 

Add icon to App Pool

Script to export and import Application Pools e i loro entitlements

 

# Script per esportare e importare Application Pools e le loro entitlements da un HCS ad un altro.
# Si basa sulle API REST di Omnissa (HCS) e richiede le credenziali di un utente con privilegi di amministratore.
# Il file JSON esportato contiene i pool e le entitlements associate, che possono essere importati in un altro HCS.
# Le API utilizzate sono documentate nella sezione "API Reference" della documentazione di Omnissa. 
# https://developer.omnissa.com/horizon-apis/
# https://retouw.nl/2021/10/02/horizon-rest-api-powershell-7-paging-and-filtering-with-samples/

# === LOGIN & TOKEN ===
function Get-HRToken {
    param([string]$Server, [string]$Domain, [string]$User, [string]$Password)
    $body = @{ domain=$Domain; username=$User; password=$Password } | ConvertTo-Json
    $uri  = "https://$Server/rest/login"   # POST /rest/login :contentReference[oaicite:0]{index=0}
    (Invoke-RestMethod -Method Post -Uri $uri -Body $body -ContentType 'application/json' `
                       -SkipCertificateCheck).access_token
}

# === UTILITY: rimuove campi read-only non clonabili ===
function Sanitize-Pool {
    param($Pool)
    $Pool | Select-Object * -ExcludeProperty id, avm_shortcut_id, global_application_entitlement_id
}


function Export-AppPoolsWithEntitlements {
    param(
        [string]$SrcServer,  [string]$Domain,
        [string]$User,       [string]$Password,
        [string]$OutFile 
    )

    $token = Get-HRToken $SrcServer $Domain $User $Password

    # Lista completa dei pool (max 1000) – GET /inventory/v4/application-pools :contentReference[oaicite:1]{index=1}
    $pools = Invoke-RestMethod -Method Get `
              -Uri "https://$SrcServer/rest/inventory/v3/application-pools?size=1000" `
              -Headers @{Authorization="Bearer $token"} -SkipCertificateCheck

    $export = foreach ($p in $pools) {
        # Entitlement del singolo pool – GET /entitlements/v1/application-pools/{id} :contentReference[oaicite:2]{index=2}
        $ents = Invoke-RestMethod -Method Get `
                -Uri "https://$SrcServer/rest/entitlements/v1/application-pools/$($p.id)" `
                -Headers @{Authorization="Bearer $token"} -SkipCertificateCheck

        [PSCustomObject]@{
            pool         = Sanitize-Pool $p
            entitlements = $ents.ad_user_or_group_ids
        }
    }

    $export | ConvertTo-Json -Depth 15 | Out-File $OutFile -Encoding UTF8
    Write-Host "✓ Esportati $($export.Count) pool in $OutFile"
}




function Import-AppPoolsWithEntitlements {
    param(
        [string]$DstServer,  [string]$Domain,
        [string]$User,       [string]$Password,
        [string]$JsonFile 
    )

    $token = Get-HRToken $DstServer $Domain $User $Password
    $data  = Get-Content $JsonFile | ConvertFrom-Json

    foreach ($item in $data) {
    Write-Host "Singolo item $item"
    $item.pool
    Read-Host -Prompt "Press Enter to continue"
        # 4.1  Crea il nuovo Application Pool – POST /inventory/v1/application-pools :contentReference[oaicite:3]{index=3}
        $bodyPool = $item.pool | ConvertTo-Json -Depth 15
        $newPool  = Invoke-RestMethod -Method Post `
                     -Uri "https://$DstServer/rest/inventory/v1/application-pools" `
                     -Headers @{Authorization="Bearer $token"} `
                     -ContentType 'application/json' -Body $bodyPool -SkipCertificateCheck 
        Write-Host "Pool creato $newPool"
        $nomepool = $($item.pool.name)
        Write-Host "Nome del application $nomepool"
        $poolid = Invoke-RestMethod -Method Get `
                     -Uri "https://hcs01.pollaio.lan/rest/inventory/v1/application-pools?filter=%7B%0A%09%22type%22%3A%20%22Equals%22%2C%0A%09%22name%22%3A%20%22name%22%2C%0A%09%22value%22%3A%20%22$nomepool%22%0A%7D" `
                     -Headers @{Authorization="Bearer $token"} `
                     -ContentType 'application/json' -SkipCertificateCheck
        $poolid
        $poolid.id

    Read-Host -Prompt "Press Enter to continue"
        Write-Host "✓ Creato pool '$($item.pool.name)' (nuovo id $($poolid.id))"

        # 4.2  Ripristina entitlement (se presenti) – POST /entitlements/v1/application-pools (bulk) :contentReference[oaicite:4]{index=4}
        if ($item.entitlements.Count) {
            $Psobj=New-Object -Type psobject
            $Psobj | Add-Member -MemberType NoteProperty -Name "id" -Value $poolid.id -Force
            $Psobj | Add-Member -MemberType NoteProperty -Name "ad_user_or_group_ids" -Value $item.entitlements -Force
            $entSpec ="["
            $entSpec += $Psobj | ConvertTo-Json
            $entSpec += "]"
        
            Invoke-RestMethod -Method Post `
                -Uri "https://$DstServer/rest/entitlements/v1/application-pools" `
                -Headers @{Authorization="Bearer $token"} `
                -ContentType 'application/json' -Body $entSpec -SkipCertificateCheck

            Write-Host "  └─► Entitlement ripristinati: $($item.entitlements.Count) SID - APP ID $($newPool.id)"
        }
    }
}
function DestListFarmsID {
    param(
        [string]$DstServer,  [string]$Domain,
        [string]$User,       [string]$Password,
        [string]$FarmDest
    )
    
    $token = Get-HRToken $DstServer $Domain $User $Password
    $FARMDETAILDST = Invoke-RestMethod -Method Get `
                -Uri "https://$DstServer/rest/inventory/v7/farms?filter=%7B%0A%09%22type%22%3A%20%22Equals%22%2C%0A%09%22name%22%3A%20%22name%22%2C%0A%09%22value%22%3A%20%22$FarmDest%22%0A%7D" `
                -Headers @{Authorization="Bearer $token"} `
                -ContentType 'application/json' 
    Write-host $FARMDETAILDST.id
}

function SourceListFarmsID {
    param(
        [string]$SrcServer,  [string]$Domain,
        [string]$User,       [string]$Password,
        [string]$FarmSrc
    )
$filterhashtable = [ordered]@{}
$filterhashtable.filters = @()
$userfilter= [ordered]@{}
$userfilter.add('type','Equals')
$userfilter.add('name','name')
$userfilter.add('value',$FarmSrc)
$filterhashtable.filters+=$userfilter
$filterflat = $filterhashtable | ConvertTo-Json -Compress
    $token = Get-HRToken $SrcServer $Domain $User $Password
    $FARMDETAILSRC = Invoke-RestMethod -Method Get `
               -Uri "https://$SrcServer/rest/inventory/v3/farms?$filterflat" `
                -Headers @{Authorization="Bearer $token"} `
                -ContentType 'application/json' -skipCertificateCheck
    Write-host $FARMDETAILSRC.id
}
######MAIN PROGRAM###
# Insert Source HCS
$SrcServer = Read-Host -Prompt "Insert Source HCS Server Name"
#$SrcServer = "hcs2111.pollaio.lan"
# Insert Dest HCS
$DstServer = Read-Host -Prompt "Insert Destination HCS Server Name"
#$DstServer = "hcs01.pollaio.lan"
#Insert Domain
$Domain = Read-Host -Prompt "Insert Domain Name (e.g. POLLAIO)"
#$Domain = "pollaio"
# Insert Path to JSON file
$JsonFile = Read-Host -Prompt "Insert Path to JSON file (e.g. c:\attimo\AppPoolsWithEntitlements.json)"
# Insert Credentials 
$Credentials = Get-Credential -Message "Insert Domain Credentials for $SrcServer and $DstServer"
$Username = $Credentials.UserName
$Pass = $Credentials.GetNetworkCredential().Password

# --- EXPORT ---
Export-AppPoolsWithEntitlements `
    -SrcServer $SrcServer -Domain $Domain `
    -User $Username -Password $Pass -OutFile $JsonFile

#
$FarmSrc = Read-Host -Prompt "Insert Source Farm Name"
$SFARM=SourceListFarmsID `
     -SrcServer $SrcServer -Domain $Domain `
    -User $Username -Password $Pass -FarmSrc $FarmSrc 6>&1

$SFARM


$FarmDest = Read-Host -Prompt "Insert Destination Farm Name"
$DFARM=DestListFarmsID `
     -DstServer $DstServer -Domain $Domain `
    -User $Username -Password $Pass -FarmDest $FarmDest 6>&1

$DFARM
(Get-Content -Path $JsonFile) -replace "$($SFARM)", "$($DFARM)" | Set-Content -Path $JsonFile

Read-Host -Prompt "WARNING!! If you import the application in the same HCS change the Name and diplay name in the Json file.  Press Enter to continue"

# --- IMPORT ---
Import-AppPoolsWithEntitlements `
    -DstServer $DstServer -Domain $Domain `
    -User $Username -Password $Pass -JsonFile $JsonFile

 

Script to export and import Application Pools e i loro entitlements

vCenter Server Preupgrade check result error: “VMDir Replication between partners is not working”

vCenter upgrade Pre-Check fail with “VMDir replication is not working correctly”

When I tried to upgrade my vCenter to the last version of 8u3 on the pre-check command, I found this error

A screenshot of a computer error message

AI-generated content may be incorrect.

Because I attached another vCenter at the same PSC, it is now broken.

For deleting the missed vCenter:

Take a vCenter snapshot

Check the vCenter name:

The name is vcenter02.pollaio.lan

Remove the old vCenter:

A computer screen with white text

AI-generated content may be incorrect.

After automatic service restart

Now the upgrade pre-check does not have an alert message

A screenshot of a computer

AI-generated content may be incorrect.

Reference

vCenter Server Preupgrade check result error: “VMDir Replication between partners is not working”

vCenter Server Preupgrade check result error: “VMDir Replication between partners is not working”

How to differentiate Horizon Smart Policies with Unified Access Gateway Location information

There is a Unified Access Gateway (UAG) configuration that can help to apply different Horizon Smart Policies (Like clipboard) to Horizon VDI Sessions

A screenshot of a computer

Description automatically generated

Normally I can suggest using UAG not only for external access (Home Worker) but also for internal access (Office Worker)* and I deploy a UAG group (two or plus UAG) for external and another for internal.

The Gateway location can Help us for example to enable Clipboard for internal access and disable for external access, It is possible to integrate this information (Gateway location) with a Dynamic Environment Manager Condition

In my example, I have configured two Horizon Smart Policies:

Clipboard_From_Internet 🡪 Where I disabled the clipboard with this condition:

A screenshot of a computer

Description automatically generated

Clipboard_From_Internal 🡪 Where I enabled the clipboard with this condition:

A screenshot of a computer

Description automatically generated

With those values, I can configure different functions depending on where the client is trying to connect to the VDIs

*The use of UAG on internal access can help to not deploy an Internal Load balancing (We can use UAG HA) and not use a balancing for Horizon Connection Servers. (I always suggest to map 1:1 the UAG with Connection Server)

How to differentiate Horizon Smart Policies with Unified Access Gateway Location information

Configure Proxy Server for Horizon for SAML integration

When we need to integrate a Horizon infrastructure to the cloud identity provider (like Workspace One Access SaaS solution) sometimes we need to manage firewall and proxy configuration.

For the Firewall rule, there is much information (KB link) while for the proxy server, we are not able to use Windows server configuration because Horizon ignores it.

To use a proxy server to permit communication to the IdP URL from Horizon Connection servers we need to configure some values on ADAM DB:

pae-SAMLProxyName

pae-SAMLProxyPort

To connect to ADAM DB and where modify the correct value

  • Connect with RDP session to Connection Server OS
  • Start from PowerShell adsedit

A close-up of a computer screen

Description automatically generated

  • In the console tree select Connect to..

A screenshot of a computer

Description automatically generated

  • Configure the connection with this information.

dc=vdi,dc=vmware,dc=int

localhost:389

A screenshot of a computer

Description automatically generated

  • Expand ADAM ADSI tree under the object path: dc=vdi,dc=vmware,dc=int,ou=Properties,ou=Global
  • Click on value Common and modify the following value

pae-SAMLProxyName -> With Proxy URL

pae-SAMLProxyPort -> With Proxy Port

Now we can configure the SAML integration from Horizon and IdP

Some information about why we need to integrate and use Workspace One Access with Horizon:

Integration between VMware Horizon and VMware Workspace ONE Access (formerly called Workspace ONE) uses the SAML 2.0 standard to establish mutual trust, which is essential for single sign-on (SSO) functionality. When SSO is enabled, users who log in to VMware Workspace ONE Access or Workspace ONE with Active Directory credentials can launch remote desktops and applications without having to go through a second login procedure.

Configure Proxy Server for Horizon for SAML integration

VMware Pre Broadcom vs VMware Broadcom – Primi dati reali

Attenzione è una mia valutazione….quindi non sparate sul sistemista

Broadcom ha acquisito VMware, ormai lo sappiamo tutti.
La situazione di incertezza aleggia ovunque soprattutto sul mondo vSphere e sui costi (con tanti competitor che provano a ritagliarsi la loro fetta di mercato togliendole al leader indiscusso di questi anni)


Finalmente in questi giorni incomincio ad avere i primi dati effettivi (Prezzi ecc…) su cui iniziare a fare i primi ragionamenti.
!!Attenzione non voglio dare giudizi ma voglio solo paragonare due offerte fatte allo stesso cliente che abbiamo dovuto rivedere a seguito del nuovo listino (E parliamo di prezzi di listino.. senza eventuali scontistiche)!!

Ragioniamo su un cluster vSphere con 3 nodi da 2 processori ciascuno da 16 core.

Con le precedenti licenze e il vecchio listino nello scenaro ipotizzato dovevamo considerare:

  • Licenza VMWARE VCENTER SERVER 8 STANDARD
  • Licenza VMWARE VSPHERE 8 STANDARD FOR 1 PROCESSOR
  • Support/Subscription
  • Support/Subscription

Con il nuovo listino e le nuove tipologie di licenze invece dobbiamo considerare:

  • VMWARE VSPHERE STANDARD per core (Che comprende la licenza di vCenter)

Entrambe le soluzioni con 5 anni.

Da una prima analisi le prime valutazioni sono:
Con il nuovo listino si viene a pagare circa 30-35% in meno.
Ho una semplificazione nella quotazione (una sola voce rispetto alle 4 precedenti)

Ovviamente:

  • Non abbiamo le licenze perpetue (comunque chi non vuole il supporto sul proprio ambiente di produzione o la possibilità di effettuare aggiornamenti?)
  • é una prima offerta e la quotazione può dipendere da vari fattori e i prezzi potrebbero nuovamente cambiare
  • Le funzionalità all’interno dei bundle possono essere leggermente differenti (il link per vedere le funzionalità presenti nei nuovi bundle VMware vSphere® Product Line Comparison)
  • Posso aver sbagliato i calcoli 🙂
  • Possono avermi dato dei prezzi sbagliati 🙂 spero di no per il cliente 🙂

ma aspettavo di avere due informazioni reali per fare le mie prime considerazioni.

L’unica cosa che posso dire è di valutare con attenzione il cambio …. (io sono il primo che accetta nuove sfide..) ma attenzione a tutti i prezzi nascosti e valutate bene!

P.S. se qualcuno ha delle esperienze in merito … condividiamole.

VMware Pre Broadcom vs VMware Broadcom – Primi dati reali

Failed to save domains. Resolving domains with the directory server failed with reason: [MyDomain – Kerberos authentication failed for domain.]

If we have a problem with the Identity directory on Workspace One Access like that:

Failed to save domains. Resolving domains with the directory server failed with reason: [fienile.lan – Kerberos authentication failed for domain.]

A close-up of a computer screen

Description automatically generated

In the situation where we have an Active Directory with Multi-Forest Active Directory Environment with Trust Relationships (The trust needs to be two-way and direct (non-transitive)).

There may be a problem with the Trust configuration, and you can find this error on DC (the DC of the user configuration for the connection)

The solution is to change the trust configuration and add:

A screenshot of a computer

Description automatically generated

Failed to save domains. Resolving domains with the directory server failed with reason: [MyDomain – Kerberos authentication failed for domain.]