Some Horizon use cases require dedicated assignments vs floating, but oftentimes the team or automation that is in charge of user provisioning/deprovisioning is not integrated with the Horizon assignments. Horizon also will not tell you if the assigned user account no longer exists or has been disabled. Therefore, it may be necessary to audit dedicated assignment pools.

At scale, this can be difficult. Thankfully, once again, the Horizon API combined with PowerShell can help automate this task. Simply save the script below as Get-OrphanedHVAssignments.ps1. You can either populate the default variables at the top or pass them as parameters, like so:

Get-OrphanedHVAssignments.ps1 -CS <connectionserver> -PoolName <poolname> -MultiAssignment <$true or $false>. You only need to use MultiAssignment if the pool has the ability to assign multiple users per desktop (multi-assignment pools use the users vs user property). Otherwise, you can leave it out. Note that you will need PowerCLI, HV.Helper, and AD PowerShell modules available to use this script.

Putting it all together, it will look like this:

# Gets orphaned full clones by checking if their AD account exists or is disabled in AD

param (
    [string]$CS = "defaultCS",
    [string]$PoolName = "defaultPool",
    [boolean]$MultiAssignment = $false
    )

    
try
{
Connect-HVServer $CS
}
catch
{
Write-Host "Login seems to have failed! Make sure to use DOMAIN\ username format and you have appropriate HV.Helper/PowerCLI modules installed!"
exit
}

$ViewAPI = $global:DefaultHVServers[0].ExtensionData

$Assignments = @()

$Desktops = Get-HVMachine -PoolName $poolName

# if pool is a multi-user pool, grab the first assigned user
if ($MultiAssignment -eq $true)
{

ForEach ($desktop in $desktops) {

  If ($desktop.base.users) 
  
  { 
  $User = ($ViewApi.AdUserOrGroup.AduserOrGroup_Get($desktop.base.users[0])).base.LoginName 
  } 
  Else 
  { 
  $User = ' '
  }

  $Assignments += [PSCUSTOMOBJECT] @{

        Desktop = $desktop.base.name

        User    = $user

      }

}
}

#otherwise, we assume it's a single user pool, which is uses base.user property vs base.users
else 
{

ForEach ($desktop in $desktops) {

  If ($desktop.base.user) 
  
  { 
  $User = ($ViewApi.AdUserOrGroup.AduserOrGroup_Get($desktop.base.user)).base.LoginName 
  } 
  Else 
  { 
  $User = ' '
  }

  $Assignments += [PSCUSTOMOBJECT] @{

        Desktop = $desktop.base.name

        User    = $user

      }

}

}

$Assignments | sort desktop

$usersToCleanup = @()
$disabledUsers = @()
$missingUsers = @()
foreach ($Assignment in $Assignments)
{
$error.clear()

    try {

          $disabledUsers += Get-ADUser $Assignment.user | Where-Object {$_.enabled -eq $false} | select SamAccountName
          }
    catch 
    {
    $missingUsers += $Assignment.user
    }
}

Write-Host "The following users are MISSING from AD and should have their desktops deleted!"
$missingUsers | Where-Object {$_ -ne " "}
Write-Host "The following users are DISABLED in AD and should have their desktops deleted!"
$disabledUsers.SamAccountName

I hope this script helps any poor soul that has to deal with dedicated assignments at scale in their Horizon deployment! As always, if you have any issues with the script or have any automation questions in general, shoot me an email.