Added Horizon Tools

This commit is contained in:
ackitsme committed 2026-07-24 17:37:31 -07:00
1 parent fc7cfeb008
commit f13f6e1105
3 files changed
+1213

No files matched your search

+315
View File
@@ -0,0 +1,315 @@
param(
[string]$HorizonServer = "",
[string]$OutFile = "",
[string]$InputXML = "",
[switch]$SaveCred = $false,
[switch]$debug = $false,
[switch]$help = $false
)
Write-Host $global:kishorizondebug
# Variables
$fileVersion = "1.1.0"
$fileDate = "2026-06-24"
$scriptName = Split-Path -Path $PSCommandPath -Leaf
$mypath = split-path -parent $MyInvocation.MyCommand.Definition
$now = (Get-Date).ToString("yyyy-MM-dd_HH:mm")
$userfolder = $env:USERPROFILE
# Display Script Banner
Write-Host "--------------------------------------------------" -ForegroundColor White
Write-Host "Export Pool Settings to Excel File" -ForegroundColor Green
Write-Host "Version $($FileVersion) ($($FileDate))" -ForegroundColor Green
Write-Host "Written by Charlie Silverman" -ForegroundColor Green
Write-Host "--------------------------------------------------" -ForegroundColor White
function CheckModuleInstalled($moduleName, $command) {
Write-Host "Checking that package $moduleName is installed..." -NoNewLine
if (-not(get-command $command -erroraction silentlycontinue)) {
Write-Host " Missing" -ForegroundColor Red
if (-not (Get-Module -ListAvailable -Name $moduleName)) {
$response = ""
while (($response -ne "yes") -and ($response -ne "no")) {
$response = (Read-Host "$moduleName module not instaled. Install now? (yes/no)").ToLower()
}
if ($response -eq "no") {
Write-Host "Operation aborted." -ForegroundColor Yellow
exit
}
Write-Host "Installing PowerShell module $moduleName..." -NoNewLine
Install-Module -Name $moduleName -Scope CurrentUser -Force
Write-Host " Success" -ForegroundColor Green
}
Write-Host "Loading PowerShell module $moduleName..." -NoNewLine
Import-Module $moduleName
Write-Host " Success" -ForegroundColor Green
} else {
Write-Host " Found" -ForegroundColor Green
}
}
# Check for ImportExcel and install if missing
CheckModuleInstalled -moduleName "VCF.PowerCLI" -command "connect-viserver"
CheckModuleInstalled -moduleName "ImportExcel" -command "Open-ExcelPackage"
Import-Module $mypath\KISHorizon.psm1
$abyss = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false
if ($Help) {
Write-Host
Write-Host "Purpose: Resolve Horizon VMs with errors."
Write-Host "Command Syntax:"
Write-Host " $scriptName (-Horizon ""[cluster]"") (-SaveCred)" -ForegroundColor Green
Write-Host "Command Line Options:"
Write-Host
Write-Host -NoNewLine " -Horizon " -ForegroundColor Green
Write-Host "- The FQDN or IP address of a Horizon connection broker."
Write-Host -NoNewLine " -SaveCred " -ForegroundColor Green
Write-Host "- Ask for credentials and save them to current user's profile directory."
Write-Host
Write-Host "The Horizon server will be queried for all vCenter servers it is configured for and credentials will be required for each."
Write-Host "Any information not provided will be prompted for during script execution."
exit
}
if ($OutFile -eq "") {
$OutFile = "KISHorizonExport-$($HorizonServer).xlsx"
}
if (Test-Path -path $Outfile -PathType Leaf) {
Remove-Item -Path $Outfile
}
if ($inputXML -eq "") {
if ($HorizonServer -eq "") {
$HorizonServer = Read-Host "Enter FQDN or IP address of Horizon Connection Broker"
}
if ($SaveCred) {
$hzcred = Get-Credential -Message "Enter credentials for Horizon server $HorizonServer in the format DOMAIN\USERNAME"
$hzcred | Export-Clixml -path $userfolder\kistools-$HorizonServer.cred
Write-Host "Saving credentials."
}
if (Test-Path -path $userfolder\kistools-$HorizonServer.cred -PathType Leaf) {
$hzcred = Import-Clixml -path $userfolder\kistools-$HorizonServer.cred
Write-Host "- Using stored Horizon credentials"
} else {
$hzcred = Get-Credential -Message "Enter credentials for Horizon server $HorizonServer in the format DOMAIN\USERNAME"
if (!$hzcreds) {
Write-Host "ERROR: No credentials were entered" -foregroundcolor yellow
exit
}
}
#Establish connection to Horizon
try {
Write-Host -NoNewLine "Connecting to Horizon Connection Broker $HorizonServer... "
$hzconn = Connect-HorizonServer -server $HorizonServer -credential $hzcred
Write-Host "Success" -ForegroundColor green
} catch {
Write-Host "Failure" -ForegroundColor Yellow -BackgroundColor Red -NoNewLine
Write-Host
exit
}
# Establish connection to all vCenter servers configured on this Horizon system
$vcenterlist = $hzconn | Get-HorizonvCenter
foreach ($thisvc in $vcenterlist) {
$vcname = $thisvc.server_name
if ($SaveCred) {
$vccred = Get-Credential -Message "Enter credentials for vCenter server $vcname"
$vccred| Export-Clixml -path $userfolder\kistools-$vcname.cred
Write-Host "Saving credentials."
}
if (Test-Path -path $userfolder\kistools-$vcname.cred -PathType Leaf) {
$vccred = Import-Clixml -path $userfolder\kistools-$vcname.cred
Write-Host "- Using stored vCenter credentials"
} else {
$vccred = Get-Credential -Message "Enter credentials for vCenter server $vcname"
if (!$vccred) {
Write-Host "ERROR: No credentials were entered" -foregroundcolor yellow
exit
}
}
#Establish connection to vCenter
try {
Write-Host -NoNewLine "Connecting to vCenter $vcname... "
$vcconn = Connect-VIServer -server $vcname -Credential $vccred -Force -ErrorAction Stop
Write-Host "Success" -ForegroundColor green
} catch {
Write-Host "Failure" -ForegroundColor Yellow -BackgroundColor Red -NoNewLine
Write-Host
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
exit
}
}
$pools = $hzconn | Get-HorizonDesktopPool
} else {
$pools = Import-CliXml -path $inputXML
}
foreach ($thisvc in $vcenterlist) {
Write-Host -NoNewLine "Disconnecting from vCenter $vcname... "
try {
$abyss = Disconnect-ViServer $thisvc.server_name -confirm:$false
Write-Host "Success" -ForegroundColor green
} catch {
Write-Host "Failure" -ForegroundColor Yellow -BackgroundColor Red -NoNewLine
Write-Host
}
}
Write-Host -NoNewLine "Generating Excel File $OutFile... "
$workbook = Open-ExcelPackage -Path $OutFile -Create
#Create Initial Data Worksheet
$thissheet = Add-Worksheet -ExcelPackage $workbook -WorksheetName "General Horizon Data"
Set-ExcelRange -worksheet $thissheet -Range "A:A" -VerticalAlignment Top
Set-Format -Address $thissheet.Cells["B:B"] -WrapText
$thissheet.column(1).Width = 40
$thissheet.column(2).Width = 40
$thissheet.column(3).Width = 40
Set-ExcelRange -Worksheet $thissheet -Range "A1:E1" -Merge:$true
$domains = Get-HorizonADDomains -Horizon $hzconn
$vcenterservers = Get-HorizonvCenter -Horizon $hzconn
$goldimages = $pools.provisioning_settings.base_vm_name | Select-Object -Unique
$row = 1
$thissheet.cells["A1"].value = "General data for Horizon server $HorizonServer"
Set-Format -Address $thissheet.Cells["A1"] -Bold
Set-ExcelRange -Worksheet $thissheet -Range "A1" -FontSize 20
$row += 2
$thissheet.cells["A" + $row].value = "vCenter Servers"
Set-Format -Address $thissheet.Cells["A" + $row] -Bold
Set-ExcelRange -Worksheet $thissheet -Range "A$row" -FontSize 16
$row += 1
$vctabletop = $row
$thissheet.cells["A" + $row].value = "vCenter Server"
$thissheet.cells["B" + $row].value = "Version"
$thissheet.cells["C" + $row].value = "User Account"
$row += 1
foreach ($vc in $vcenterlist) {
$thissheet.cells["A" + $row].value = $vc.server_name
$thissheet.cells["B" + $row].value = $vc.version
$thissheet.cells["C" + $row].value = $vc.user_name
$row += 1
}
$vcTableRange = $thissheet.Cells[$vctabletop, 1, ($row-1), 3]
$vcTable = $thissheet.Tables.Add($vcTableRange, "vCenterList")
$vcTable.StyleName = "TableStyleMedium15"
$row += 1
$thissheet.cells["A" + $row].value = "Active Directory Domains"
Set-Format -Address $thissheet.Cells["A" + $row] -Bold
Set-ExcelRange -Worksheet $thissheet -Range "A$row" -FontSize 16
$row += 1
$adtabletop = $row
$thissheet.cells["A" + $row].value = "DNS Name"
$thissheet.cells["B" + $row].value = "NetBIOS Name"
$row += 1
foreach ($add in $domains) {
$thissheet.cells["A" + $row].value = $add.dns_name
$thissheet.cells["B" + $row].value = $add.netbios_name
$row += 1
}
$adTableRange = $thissheet.Cells[$adtabletop, 1, ($row-1), 2]
$adTable = $thissheet.Tables.Add($adTableRange, "ADDomains")
$adTable.StyleName = "TableStyleMedium15"
$row += 1
$thissheet.cells["A" + $row].value = "Published Gold Images"
Set-Format -Address $thissheet.Cells["A" + $row] -Bold
Set-ExcelRange -Worksheet $thissheet -Range "A$row" -FontSize 16
$row += 1
$adtabletop = $row
$thissheet.cells["A" + $row].value = "Gold Image VMs"
$thissheet.cells["B" + $row].value = "Pools"
$row += 1
foreach ($image in $goldimages) {
$thissheet.cells["A" + $row].value = $image
$thissheet.cells["B" + $row].value = (($pools | Where-Object {$_.provisioning_settings.base_vm_name -eq $image}).Name -Join "`r`n")
$row += 1
}
$giTableRange = $thissheet.Cells[$adtabletop, 1, ($row-1), 2]
$giTable = $thissheet.Tables.Add($giTableRange, "GoldImages")
$giTable.StyleName = "TableStyleMedium15"
foreach ($pool in $pools) {
$thissheet = Add-Worksheet -ExcelPackage $workbook -WorksheetName $pool.name
$entitlements = $pool.entitlements | ForEach-Object {
$_.Domain + "\" + $_.SamAccountName
}
$data = @(
[pscustomobject]@{Name = "Pool ID";Value=$pool.name}
[pscustomobject]@{Name = "Display Name";Value=$pool.display_name}
[pscustomobject]@{Name = "Description";Value=$pool.description}
[pscustomobject]@{Name = "Type";Value=$pool.session_type}
[pscustomobject]@{Name = "User Assignment";Value=$pool.user_assignment}
[pscustomobject]@{Name = "vCenter Server";Value=$pool.vcenter}
[pscustomobject]@{Name = "Naming Pattern";Value=$pool.pattern_naming_settings.naming_pattern}
[pscustomobject]@{Name = "Max Machines";Value=$pool.pattern_naming_settings.max_number_of_machines}
[pscustomobject]@{Name = "Spare Machines";Value=$pool.pattern_naming_settings.number_of_spare_machines}
[pscustomobject]@{Name = "Min Machines";Value=$pool.pattern_naming_settings.min_number_of_machines}
[pscustomobject]@{Name = "Datacenter";Value=$pool.provisioning_settings.datacenter_name}
[pscustomobject]@{Name = "Resource Pool";Value=$pool.provisioning_settings.resource_pool_name}
[pscustomobject]@{Name = "Parent VM";Value=$pool.provisioning_settings.base_vm_name}
[pscustomobject]@{Name = "Snapshot Path";Value=$pool.provisioning_settings.snapshot_path}
[pscustomobject]@{Name = "VM Folder Path";Value=$pool.provisioning_settings.base_folder_path}
[pscustomobject]@{Name = "Cluster";Value=$pool.cluster}
[pscustomobject]@{Name = "Resource Pool";Value=$pool.provisioning_settings.resource_pool_name}
[pscustomobject]@{Name = "Datacenter";Value=$pool.provisioning_settings.datacenter_name}
[pscustomobject]@{Name = "Datastores";Value=$pool.storage_settings.datastores.name -join "`r`n"}
[pscustomobject]@{Name = "Network";Value=($pool.machines.managed_machine_data.network_labels.network_label_name | Select-Object -Unique | Sort-Object) -join "`r`n"}
[pscustomobject]@{Name = "Use vSAN";Value=$pool.storage_settings.use_vsan}
[pscustomobject]@{Name = "Add Virtual TPM";Value=$pool.provisioning_settings.add_virtual_tpm}
[pscustomobject]@{Name = "vGPU Enabled";Value=$pool.GpuEnabled}
[pscustomobject]@{Name = "Customization Type";Value=$pool.customization_settings.customization_type}
[pscustomobject]@{Name = "Ad Container";Value=$pool.customization_settings.ad_container_rdn}
[pscustomobject]@{Name = "Reuse Existing AD Object";Value=$pool.customization_settings.reuse_pre_existing_accounts}
[pscustomobject]@{Name = "Cloneprep Script Settings";Value=$pool.customization_settings.cloneprep_script_settings}
[pscustomobject]@{Name = "Power Off Script Name";Value=$pool.customization_settings.cloneprep_customization_settings.power_off_script_name}
[pscustomobject]@{Name = "Power Off Script Parameters";Value=$pool.customization_settings.cloneprep_customization_settings.power_off_script_parameters}
[pscustomobject]@{Name = "Post Sync Script Name";Value=$pool.customization_settings.cloneprep_customization_settings.post_synchronization_script_name}
[pscustomobject]@{Name = "Post Sync Script Parameters";Value=$pool.customization_settings.cloneprep_customization_settings.post_synchronization_script_parameters}
[pscustomobject]@{Name = "Automatic Logoff Policy";Value=$pool.session_settings.disconnected_session_timeout_policy}
[pscustomobject]@{Name = "Automatic Logoff Minutes";Value=$pool.session_settings.disconnected_session_timeout_minutes}
[pscustomobject]@{Name = "Allow Users to Reset Machines";Value=$pool.session_settings.allow_users_to_reset_machines}
[pscustomobject]@{Name = "Allow Multiple Sessions Per User";Value=$pool.session_settings.allow_multiple_sessions_per_user}
[pscustomobject]@{Name = "Used VM Policy";Value=$pool.session_settings.delete_or_refresh_machine_after_logoff}
[pscustomobject]@{Name = "Logoff After Timeout";Value=$pool.session_settings.disconnected_session_timeout_policy}
[pscustomobject]@{Name = "Supported Display Protocols";Value=$pool.display_protocol_settings.display_protocols -join "`r`n"}
[pscustomobject]@{Name = "Default Display Protocol";Value=$pool.display_protocol_settings.default_display_protocol}
[pscustomobject]@{Name = "Allow Users to Choose Protocol";Value=$pool.display_protocol_settings.allow_users_to_choose_protocol}
[pscustomobject]@{Name = "External HTML Access";Value=$pool.display_protocol_settings.html_access_enabled}
[pscustomobject]@{Name = "Enable Collaboration";Value=$pool.display_protocol_settings.session_collaboration_enabled}
[pscustomobject]@{Name = "Entitlements";Value=$entitlements}
)
$rowcount = 1
foreach ($datum in $data) {
$thissheet.cells["A" + $rowcount].Value = $datum.name
$thissheet.cells["B" + $rowcount].Value = $datum.value
$rowcount += 1
}
$thissheet.Column(1).Style.HorizontalAlignment = "Right"
$thissheet.Column(1).Width = 40
$thissheet.Column(2).Width = 200
Set-Column -ExcelPackage $workbook -WorksheetName $pool.Name -Column 2 -WrapText
Set-Column -ExcelPackage $workbook -WorksheetName $pool.Name -Column 2 -HorizontalAlignment Left
Set-Column -ExcelPackage $workbook -WorksheetName $pool.Name -Column 1 -VerticalAlignment Top
Set-ExcelRange -Worksheet $thissheet -Range "A:B" -FontSize 14
$thissheet.cells["A:A"].Style.Indent = 2
Set-ExcelRange -Worksheet $thissheet -Range "B:B" -Bold
}
Close-ExcelPackage $workbook
Write-Host "Success" -ForegroundColor green
+688
View File
@@ -0,0 +1,688 @@
$global:request_header = @{
"accept"="*/*"
"content-type"="application/json"
}
function Test-Server {
param (
[Parameter(Mandatory)] [string]$server
)
try {
$result = (new-object System.Net.Sockets.TcpClient).ConnectAsync($server,443).Wait(500)
} catch {
$result = $false
}
return $result
}
function Get-HorizonKeyAgreement {
param (
[Parameter(Mandatory)] [string]$server
)
$login_headers = $global:request_header
$kaUri = "https://$server/rest/key-agreement"
$body = '{"identifier":"string","nonce":"string","public_key":"string","supported_schemes": ["SCHEME-AES2"]}'
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
if (Test-Server -server $server) {
try {
$response = Invoke-RestMethod -Method Post -Uri $kaUri -Body $body -Headers $login_headers -SessionVariable websession -SkipCertificateCheck
} catch {
Write-Host "ERROR accessing $loginUri"
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
}
if ($response -ne $null) {
$response | Add-Member -MemberType NoteProperty -Name server_name -Value $server
$response | Add-Member -MemberType NoteProperty -Name session -Value $websession
$response | Add-Member -MemberType NoteProperty -Name user_name -Value "$domain\$username"
}
return $response
} else {
throw "Error: Unable to verify server $server on port 443."
}
}
function Connect-HorizonServer {
param (
[System.Management.Automation.PSCredential]$credential,
[Parameter(Mandatory)] [string]$Server
)
$loginUri = "https://$server/rest/login"
if ($credential) {
$username = $credential.UserName.split("\")[1]
$domain = $credential.UserName.split("\")[0]
$password = $credential.GetNetworkCredential().Password
}
$login_headers = $global:request_header
$body = '{"domain":"' + $domain + '","password":"' + $password + '","username":"' + $username + '"}'
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
if (Test-Server -server $server) {
try {
$response = Invoke-RestMethod -Method Post -Uri $loginUri -Body $body -Headers $login_headers -SessionVariable websession -SkipCertificateCheck
} catch {
Write-Host "ERROR accessing $loginUri"
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
}
$response | Add-Member -MemberType NoteProperty -Name server_name -Value $server
$response | Add-Member -MemberType NoteProperty -Name session -Value $websession
$response | Add-Member -MemberType NoteProperty -Name user_name -Value "$domain\$username"
return $response
} else {
throw "Error: Unable to verify server $server on port 443."
}
}
function Refresh-HorizonConnection {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$body = @{"refresh_token"="$token"}
$refreshUri = "https://$server/rest/refresh"
Write-Host $body
try {
$result = Invoke-RestMethod -Method Post -Uri $refreshUri -Headers $request_header -Body $body -SkipCertificateCheck
} catch {
Write-Error -Message "ERROR accessing $refreshUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
return
}
return $result
}
function Get-HorizonMachine {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$machineUri = "https://$server/rest/inventory/v5/machines?sort_by=name&order_by=ASC"
try {
$machines = Invoke-RestMethod -Method Get -Uri $machineUri -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $machineUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $machines
}
function Remove-HorizonMachine {
param (
[string]$id = "",
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$ids = $id.split(",")
if ($ids.length -gt 1) {
$body = "{""machine_ids"":["
foreach ($thisid in $ids) {
$thisid = $thisid.Trim('"')
if ($body -ne "{""machine_ids"":[") { $body += "," }
$body += '"' + $thisid + '"'
}
$body += ']}'
} else {
$body = ']"{""machine_ids"":[""' + $id + '""]}'
}
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$machineUri = "https://$server/rest/inventory/v1/machines"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$result = Invoke-RestMethod -Method DELETE -Uri $machineUri -Body $body -Headers $request_header -SkipCertificateCheck -ErrorAction:SilentlyContinue
} catch {
$_.Exception.Response.StatusCode.value
$_.Exception.Response.StatusDescription
throw "ERROR accessing $machineUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $result
}
function Get-HorizonSession {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$sessionUri = "https://$server/rest/inventory/v2/sessions?sort_by=session_type&order_by=ASC"
$userURI = "https://$server/rest/external/v2/ad-users-or-groups/**ID**"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$sessions = Invoke-RestMethod -Method Get -Uri $sessionUri -Headers $request_header -SkipCertificateCheck
$sessions | Add-Member -MemberType NoteProperty -Name user_name -Value ""
$sessions | Add-Member -MemberType NoteProperty -Name user_principal_name -Value ""
$sessions | Add-Member -MemberType NoteProperty -Name session_length -Value 0
$sessions | Add-Member -MemberType NoteProperty -Name connected_length -Value 0
} catch {
throw "ERROR accessing $sessionUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
foreach($session in $sessions) {
$user = $Horizon | Get-HorizonADUser -id $session.user_id
$session.user_name = $user.login_name
$session.user_principal_name = $user.user_principal_name
$session.session_length = Get-HorizonSessionLength -session $session
$session.connected_length = Get-HorizonSessionConnectedLength -session $session
}
return $sessions
}
function Get-HorizonVcenter {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$vcuri = "https://$server/rest/config/v1/virtual-centers"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$vcs = Invoke-RestMethod -Method Get -Uri $vcUri -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $vcUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $vcs
}
function Get-HorizonBaseVMs {
param (
[Parameter(Mandatory)] [string]$vcid,
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$bvuri = "https://$server/rest/external/v1/base-vms?vcenter_id=$vcid&filter_incompatible_vms=true"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$bvs = Invoke-RestMethod -Method Get -Uri $bvUri -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $bvUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $bvs
}
function Get-HorizonBaseSnapshots {
param (
[Parameter(Mandatory)] [string]$vcid,
[Parameter(Mandatory)] [string]$vmid,
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$bsuri = "https://$server/rest/external/v1/base-vms?vcenter_id=$vcid&base_vm_id=$vmid"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$bss = Invoke-RestMethod -Method Get -Uri $bsUri -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $bsUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $bss
}
function Get-HorizonEntitlements {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$enturi = "https://$server/rest/entitlements/v1/desktop-pools"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$entitlements = Invoke-RestMethod -Method Get -Uri $entUri -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $entUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $entitlements
}
Function Get-HorizonEntitlementDetail {
param (
[pscustomobject] $entitlements,
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$domains = $Horizon | Get-HorizonAdDomains
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
$result = @()
foreach ($entitlement in $entitlements) {
$entsid = $entitlement
$user = $Horizon | Get-HorizonADUser -id $entitlement
$object = $null
if ($user) {
if ($user.group) {
$type = "Group"
} else {
$type = "User"
}
$object = [PSCustomObject]@{
Type = $type
Domain = $user.domain
SID = $entitlement
SamAccountName = $user.login_name
GivenName = $user.first_name
Surname = $user.last_name
Name = $user.name
UserPrincipalName = $user.user_principal_name
}
}
$result += $object
}
return $result
}
function Get-HorizonDesktopPool {
param (
[string]$poolname = "",
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$pooluri = "https://$server/rest/inventory/v8/desktop-pools?sort_by=name&order_by=ASC"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
if (Test-Server -server $server) {
try {
$pools = Invoke-RestMethod -Method Get -Uri $pooluri -Headers $request_header -SkipCertificateCheck
if ($poolname) {
$pools = $pools | Where-Object {$_.Name -eq $poolname}
}
} catch {
Write-Host "ERROR accessing $poolUri"
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
return $null
}
if ($pools) {
$pools | Add-Member -MemberType NoteProperty -Name machines -Value @()
$pools | Add-Member -MemberType NoteProperty -Name sessions -Value @()
$pools | Add-Member -MemberType NoteProperty -Name vCenter -Value ""
$pools | Add-Member -MemberType NoteProperty -Name cluster -Value ""
$pools | Add-Member -MemberType NoteProperty -Name GpuEnabled -Value 0
$pools | Add-Member -MemberType NoteProperty -Name Entitlements -Value @()
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name base_vm_name -Value ""
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name snapshot_path -Value ""
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name datacenter_name -Value ""
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name resource_pool_name -Value ""
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name base_folder_path -Value ""
$pools.storage_settings.datastores | Add-Member -MemberType NoteProperty -Name name -Value ""
$machines = $Horizon | Get-HorizonMachine
$sessions = $Horizon | Get-HorizonSession
$counter = 1
foreach ($pool in $pools) {
Write-Progress -Activity "Processing Pools" -Status "Processing $counter of $($pools.length) - $($pool.name)" -PercentComplete (($counter / $pools.length) *100) -Id 1
$mymachines = $machines | where {$_.desktop_pool_id -eq $pool.id}
$pool.machines += $mymachines
$mysessions = $sessions | where {$_.desktop_pool_id -eq $pool.id}
$pool.sessions += $mysessions
foreach($session in $pool.sessions) {
$machineDNSName = ($mymachines | where {$_.id -eq $session.machine_id}).dns_name
$machineName = ($mymachines | where {$_.id -eq $session.machine_id}).name
$session | Add-Member -MemberType NoteProperty -Name machine_name -Value $machineName
$session | Add-Member -MemberType NoteProperty -Name machine_dns_name -Value $machineDNSName
}
$pool.vCenter = ($hzconn | Get-HorizonVcenter | where-object {$_.id -eq $pool.vcenter_id}).server_name
if (-not ($global:DefaultVIServer.Name -contains $pool.vCenter)) {
throw "Not Connected to vCenter $($pool.vCenter)"
}
$pool.cluster = (Get-Cluster | where {$_.id -eq "ClusterComputeResource-$($pool.provisioning_settings.host_or_cluster_id)"}).name
$pool.provisioning_settings.base_vm_name = (Get-View -Id "VirtualMachine-$($pool.provisioning_settings.parent_vm_id)").Name
$snappath = ""
$goldVM = Get-VM $pool.provisioning_settings.base_vm_name
$thissnap = $goldVM | Get-Snapshot | Where-Object {$_.Id -eq "VirtualMachineSnapshot-$($pool.provisioning_settings.base_snapshot_id)"}
while ($thissnap.parentsnapshotid.length -gt 0) {
if ($snappath -eq "") {
$snappath = $thissnap.Name
} else {
$snappath = $thissnap.Name + "\" + $snappath
}
$thissnap = Get-VM $pool.provisioning_settings.base_vm_name | Get-Snapshot | Where-Object {$_.Id -eq $thissnap.ParentSnapshotId}
}
if ($snappath -eq "") {
$snappath = $thissnap.Name
} else {
$snappath = $thissnap.Name + "\" + $snappath
}
$pool.provisioning_settings.snapshot_path = $snappath
$pool.provisioning_settings.resource_pool_name = $goldVM.ResourcePool
$pool.provisioning_settings.datacenter_name = (Get-View "Datacenter-$($pool.provisioning_settings.datacenter_id)").Name
$basefolderpath = ""
$thisfolder = Get-View "Folder-$($pool.provisioning_settings.vm_folder_id)"
while ($thisfolder.parentsnapshotid.length -gt 0) {
if ($basefolderpath -eq "") {
$basefolderpath = $thisfolder.Name
} else {
$basefolderpath = $thisfolder.Name + "\" + $basefolderpath
}
$thisfolder = Get-View "Folder-$($thisfolder.Parent)"
}
if ($basefolderpath -eq "") {
$basefolderpath = $thisfolder.Name
} else {
$basefolderpath = $thisfolder.Name + "\" + $basefolderpath
}
$pool.provisioning_settings.base_folder_path = $basefolderpath
$datastores = @()
foreach ($datastore in $pool.storage_settings.datastores) {
$thisds = Get-View -Id "Datastore-$($datastore.datastore_id)"
$datastore.name = $thisds.Name
$datastores += $thisds.Name
}
$pool.GpuEnabled = ($pool.display_protocol_settings.vgpu_grid_profile.Length -gt 0);
$entsids = ($Horizon | Get-HorizonEntitlements | Where-Object {$_.id -eq $pool.id}).ad_user_or_group_ids
if ($entsids.length -gt 0) {
$entitlements = $Horizon | Get-HorizonEntitlementDetail -entitlements $entsids
$pool.entitlements = $entitlements
}
++$counter
}
Write-Progress -Activity "Processing Pools" -Completed -Id 1
return $pools
}
} else {
return $false
}
}
function Get-HorizonAdDomains {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$domainURI = "https://$server/rest/external/v2/ad-domains"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$domains = Invoke-RestMethod -Method Get -Uri $domainUri -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $domainUri`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $domains
}
Function Get-HorizonADUser {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon,
[string]$Domain = "",
[string]$Id = "",
[string]$Name = "",
[int]$Page = 0,
[int]$PageSize = 0
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$userlookupURI = "https://$server/rest/external/v1/ad-users-or-groups"
$extension = "";
if ($domain -ne "") {
$extension = "?group_only=false&filter={`"type`":`"Equals`",`"name`":`"domain`",`"value`":`"$domain`"}"
if ($Page -ne 0) {
$extension += "&page=$Page"
if ($PageSize -ne 0) {
$extension += "&size=$PageSize"
}
}
}
if ($Id -ne "") {
$extension = "/$($Id)?group_only=false"
}
if ($Name -ne "") {
$extension = "?group_only=false&filter={`"type`":`"Equals`",`"name`":`"user_principal_name`",`"value`":`"$Name`"}"
}
$userlookupURI += $extension
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$users = Invoke-RestMethod -Method Get -Uri $userlookupURI -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $userlookupURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $users
}
<# Function Get-HorizonADGroup {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon,
[string]$Domain = "",
[string]$Id = "",
[string]$Name = "",
[int]$Page = 0,
[int]$PageSize = 0
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$grouplookupURI = "https://$server/rest/external/v1/ad-users-or-groups"
$extension = "";
if ($domain -ne "") {
$extension = "?group_only=true&filter={`"type`":`"Equals`",`"name`":`"domain`",`"value`":`"$domain`"}"
if ($Page -ne 0) {
$extension += "&page=$Page"
if ($PageSize -ne 0) {
$extension += "&size=$PageSize"
}
}
}
if ($Id -ne "") {
$extension = "/$($Id)?group_only=true"
}
if ($Name -ne "") {
$extension = "?group_only=true&filter={`"type`":`"Equals`",`"name`":`"name`",`"value`":`"$Name`"}"
}
$grouplookupURI += $extension
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$users = Invoke-RestMethod -Method Get -Uri $grouplookupURI -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $grouplookupURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $users
} #>
function Invoke-SSHCommand {
param (
[Parameter(Mandatory)]
$ComputerName,
[Parameter(Mandatory)]
$Command,
[Parameter(Mandatory)]
$Credential
)
$username = $($Credential.UserName)
$password = $($Credential.GetNetworkCredential().password)
$done = $false
$count = 0;
while (!$done) {
$result = invoke-command {sshpass -p $password ssh $username@$ComputerName "$command"}
if ($result) {
$done = $true
} else {
++$count
if ($count >=5) { $done = $true }
}
}
return $result
}
function Get-HorizonSessionLength {
param (
[Parameter(Mandatory)] $session
)
$now = [int](get-date -UFormat %s -Millisecond 0)
$start = $session.start_time
$retval = $now - ($start/ 1000)
return $retval;
}
function Get-HorizonSessionConnectedLength {
param (
[Parameter(Mandatory)] $session
)
$retval = ($session.session_state -eq "CONNECTED") ? [math]::round($session.last_session_duration_ms/1000) : 0
return $retval;
}
Export-ModuleMember -Function Connect-HorizonServer
Export-ModuleMember -Function Get-HorizonVcenter
Export-ModuleMember -Function Get-HorizonDesktopPool
Export-ModuleMember -Function Get-HorizonMachine
Export-ModuleMember -Function Remove-HorizonMachine
Export-ModuleMember -Function Get-HorizonSession
Export-ModuleMember -Function Get-HorizonAdDomains
Export-ModuleMember -Function Get-HorizonAdUser
Export-ModuleMember -Function Invoke-SSHCommand
Export-ModuleMember -Function Get-HorizonSessionLength
Export-ModuleMember -Function Get-HorizonKeyAgreement
Export-ModuleMember -Function Get-HorizonEntitlements
Export-ModuleMember -Function Get-HorizonEntitlementDetail
+210
View File
@@ -0,0 +1,210 @@
param (
[string]$vCenter = "",
[string]$Horizon = "",
[string]$Pool = "",
[switch]$Help = $false,
[switch]$SaveCred = $false
)
# Variables
$fileVersion = "1.1.0"
$fileDate = "2026-06-15"
$scriptName = Split-Path -Path $PSCommandPath -Leaf
$mypath = split-path -parent $MyInvocation.MyCommand.Definition
$now = (Get-Date).ToString("yyyy-MM-dd_HH:mm")
$LogFile = "$mypath\KISVDIDesktopResolver.log"
# Display Script Banner
Write-Host "--------------------------------------------------" -ForegroundColor White
Write-Host "Resolve Failed Horizon Desktops" -ForegroundColor Green
Write-Host "Version $($FileVersion) ($($FileDate))" -ForegroundColor Green
Write-Host "Written by Charlie Silverman" -ForegroundColor Green
Write-Host "--------------------------------------------------" -ForegroundColor White
function CheckModuleInstalled($moduleName, $command) {
Write-Host "Checking that package $moduleName is installed..." -NoNewLine
if (-not(get-command $command -erroraction silentlycontinue)) {
Write-Host " Missing" -ForegroundColor Red
if (-not (Get-Module -ListAvailable -Name $moduleName)) {
$response = ""
while (($response -ne "yes") -and ($response -ne "no")) {
$response = (Read-Host "ImportExcel module not instaled. Install now? (yes/no)").ToLower()
}
if ($response -eq "no") {
Write-Host "Operation Cancelled." -ForegroundColor Yellow
exit
}
Write-Host "Installing PowerShell module $moduleName..." -NoNewLine
Install-Module -Name $moduleName -Scope CurrentUser -Force
Write-Host " Success" -ForegroundColor Green
}
Write-Host "Loading PowerShell module $moduleName..." -NoNewLine
Import-Module $moduleName
Write-Host " Success" -ForegroundColor Green
} else {
Write-Host " Found" -ForegroundColor Green
}
}
# Check for ImportExcel and install if missing
CheckModuleInstalled -moduleName "VCF.PowerCLI" -command "connect-viserver"
$void = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
import-module $mypath/KISHorizon.psm1 -DisableNameChecking
if ($Help) {
Write-Host
Write-Host "Purpose: Resolve Horizon VMs with errors."
Write-Host "Command Syntax:"
Write-Host " $scriptName (-vCenter [vcenter host name]) (-Horizon ""[cluster]"") (-Pool ""[filename]"")" -foregroundcolor green
Write-Host "Command Line Options:"
Write-Host
Write-Host -NoNewLine " -vCenter " -foregroundcolor Green
Write-Host "- The FQDN or IP address of a vCenter server."
Write-Host -NoNewLine " -Horizon " -foregroundcolor Green
Write-Host "- The FQDN or IP address of a Horizon connection broker."
Write-Host -NoNewLine " -Pool " -foregroundcolor Green
Write-Host "- A comma-delimited list of pools to check. If not specified, all pools will be checked."
Write-Host
Write-Host "If any of the information is not provided, you will be prompted with the exception of the pool list. If a credential has been"
Write-Host "previously stored, it will be used again unless the -savecred option is specified."
Write-Host
exit
}
if ($vCenter -eq "") {
$vCenter = Read-Host "Enter FQDN or IP address of vCenter Server"
}
if ($SaveCred) {
$vccred = Get-Credential -Message "Enter credentials for vCenter server $vCenter"
$vccred| Export-Clixml -path .\kistools-$vcenter.cred
Write-Host "Saving credentials."
}
if (Test-Path -path .\kistools-$vcenter.cred -PathType Leaf) {
$vccred = Import-Clixml -path .\kistools-$vcenter.cred
Write-Host "- Using stored vCenter credentials"
} else {
$vccred = Get-Credential -Message "Enter credentials for vCenter server $vCenter"
if (!$vccred) {
Write-Host "ERROR: No credentials were entered" -foregroundcolor yellow
exit
}
}
#Establish connection to vCenter
try {
Write-Host -NoNewLine "Connecting to vCenter $vcenter... "
$vcconn = Connect-VIServer -server $vcenter -Credential $vccred -ErrorAction Stop
Write-Host "Success" -ForegroundColor green
} catch {
Write-Host "Failure" -ForegroundColor Yellow -BackgroundColor Red -NoNewLine
Write-Host
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
exit
}
if ($Horizon -eq "") {
$Horizon = Read-Host "Enter FQDN or IP address of Horizon Connection Broker"
}
if ($SaveCred) {
$hzcred = Get-Credential -Message "Enter credentials for Horizon Broker $Horizon"
$hzcred | Export-Clixml -path .\kistools-$Horizon.cred
Write-Host "Saving credentials."
}
if (Test-Path -path .\kistools-$Horizon.cred -PathType Leaf) {
$hzcred = Import-Clixml -path .\kistools-$Horizon.cred
Write-Host "- Using stored Horizon credentials"
} else {
$hzcred = Get-Credential -Message "Enter credentials for Horizon server $Horizon"
if (!$hzcreds) {
Write-Host "ERROR: No credentials were entered" -foregroundcolor yellow
exit
}
}
#Establish connection to Horizon
try {
Write-Host -NoNewLine "Connecting to Horizon Connection Broker $Horizon... "
$hzconn = Connect-HorizonServer -server $Horizon -credential $hzcred
Write-Host "Success" -ForegroundColor green
} catch {
Write-Host "Failure" -ForegroundColor Yellow -BackgroundColor Red -NoNewLine
Write-Host
exit
}
if ($Pool -ne "") {
$Pools = $Pool.split(",")
$poolsvalid = @()
foreach($thisPool in $Pools) {
Write-Host -NoNewLine "Verifying existence of pool $thisPool... "
$thispool = $hzconn | Get-HorizonDesktopPool -poolname $thisPool
if ($thispool) {
Write-Host "Success" -ForegroundColor green
$poolsvalid += $thisPool
} else {
Write-Host "Failure - Skipping" -ForegroundColor Yellow -BackgroundColor Red -NoNewLine
Write-Host
}
}
} else {
$poolsvalid = $hzconn | Get-HorizonDesktopPool
}
$totalfixed = 0
$totalerror = 0
$poolreport = ""
foreach ($thisPool in $poolsvalid) {
$unreachable = $thispool.Machines | Where-Object {$_.state -eq "AGENT_UNREACHABLE"}
if ($unreachable.length -gt 0) {
$label = "desktops"
if ($unreachable.length -eq 1) { $label = "desktop" }
Write-Host "Pool $($thispool.Name) has $($unreachable.length) $label in an Unreachable state."
}
$poolfixed = 0
foreach ($machine in $unreachable) {
$thisvm = Get-VM $machine.Name
Write-Host "- Resetting NIC for VM $($thisvm.Name) in pool $($thisPool.Name)... " -NoNewLine
$void = $thisvm | Get-NetworkAdapter | Set-NetworkAdapter -connected:$false -confirm:$false
Start-Sleep -Seconds 2
$void = $thisvm | Get-NetworkAdapter | Set-NetworkAdapter -connected:$true -confirm:$false
Write-Host "Success" -ForegroundColor Green
$poolfixed += 1
}
if ($poolfixed -gt 0) {
$totalfixed += $poolfixed
$label = "VMs"
if ($poolfixed -eq 1) { $label = "VM" }
$poolreport += "`r`n - Pool $($thispool.Name): $poolfixed $label resolved"
}
}
try {
Write-Host -NoNewLine "Disconnecting from vCenter $vcenter... "
$void = Disconnect-VIServer -server $vcenter -confirm:$false
Write-Host "Success" -ForegroundColor green
} catch {
Write-Host "Failure" -ForegroundColor Yellow -BackgroundColor Red -NoNewLine
Write-Host
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
exit
}
Write-Host "`r`nTotal Desktops fixed in this run: $totalfixed"
Write-Host $poolreport
$label = "desktops"
if ($totalfixed -eq 1) { $label = "desktop" }
Add-Content $LogFile "$Now - Total $label fixed in this run: $totalfixed"
$reportlines = $poolreport.split("`r`n")
foreach ($line in $reportlines) {
if ($line -ne "") {
Add-Content $LogFile "$Now - $line"
}
}