Created
August 2, 2022 22:34
-
-
Save instance-id/b6cf748b01dac369b2c71049a701ac5f to your computer and use it in GitHub Desktop.
Azure Horizon Cloud API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.NOTES | |
============================================================ | |
Version: v0.1.00 | |
Created: 08/02/2022 | |
Last Edit: 08/02/2022 | |
Platform: Windows | |
Filename: horizon_api.ps1 | |
PSVersion: 7.2.5 | |
============================================================ | |
.DESCRIPTION | |
Azure/VMWare Horizon Cloud API Information Gathering | |
#> | |
# --| Static Import / Type Definition --------------------- | |
# --| ----------------------------------------------------- | |
# -- https://stackoverflow.com/questions/41897114/unexpected-error-occurred-running-a-simple-unauthorized-rest-query?rq=1 | |
Add-Type -TypeDefinition @' | |
public class SSLHandler | |
{ | |
public static System.Net.Security.RemoteCertificateValidationCallback GetSSLHandler() | |
{ | |
return new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; }); | |
} | |
} | |
'@ | |
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [SSLHandler]::GetSSLHandler() | |
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 | |
# --| Path Variables -------------------------------------- | |
# --| ----------------------------------------------------- | |
$currentPath = $PSScriptRoot | |
$poolDataOutput = [system.io.path]::combine($currentPath, 'output', 'pooldata.csv') | |
$sessionsOutput = [system.io.path]::combine($currentPath, 'output', 'sessions.csv') | |
$desktopsOutput = [system.io.path]::combine($currentPath, 'output', 'desktops.csv') | |
$appsDataOutput = [system.io.path]::combine($currentPath, 'output', 'appsdata.csv') | |
$utilDataOutput = [system.io.path]::combine($currentPath, 'output', 'utildata.csv') | |
$logPath = [system.io.path]::combine($currentPath, 'logger.ps1') | |
$domainCredPath = [system.io.path]::combine($credPath, 'credentials', 'domain.crypt') | |
$vmwareCredPath = [system.io.path]::combine($credPath, 'credentials', 'vmware.crypt') | |
$loadCredPath = [system.io.path]::combine($credPath, 'credentials', 'load_credentials.ps1') | |
if (!(test-path $domainCredPath) -or !(test-path $vmwareCredPath)) { | |
Write-Host 'Credentials not found. Please run the setup script.' | |
exit 1 | |
} | |
. $logPath | |
. $loadCredPath | |
function Log($message, $severity = 'INFO', $logSuffix = 'horizon_api') { | |
LogMessage "${duaNumber}" "${message}" "$severity" "${logSuffix}" | |
} | |
$domainCredentials = GetCredentialsFromFile $domainCredPath | |
$vmwareCredentials = GetCredentialsFromFile $vmwareCredPath | |
# --| Helper Functions ------------------------------------ | |
# --| ----------------------------------------------------- | |
function BuildURI([string]$apiPath) { | |
return "${baseURL}${apiPath}" | |
} | |
# Convert Unix time to datetime | |
function ConvertTime($unixTime) { | |
return (([System.DateTimeOffset]::FromUnixTimeSeconds($($unixTime / 1000))).DateTime).ToString() | |
} | |
# --| Function Variables ---------------------------------- | |
# --| ----------------------------------------------------- | |
$poolDataList = [System.Collections.ArrayList]@() | |
$sessionsList = [System.Collections.ArrayList]@() | |
$appsDataList = [System.Collections.ArrayList]@() | |
$desktopsList = [System.Collections.ArrayList]@() | |
$utilDataList = [System.Collections.ArrayList]@() | |
# --| Pool Data --------------------------------- | |
$poolDataPropertyList = 'name', 'hydraNodeName', 'requestedSize', 'actualSize', 'dateCreated', 'poolOnline', ` | |
'deleteQuota', 'defaultPoolProtocol', 'OsDiskConfiguration', 'encrypted', 'highlyAvailable', 'internal', ` | |
'deleteProtected', 'lastUpdated', 'preferredClientType', 'poolSizeType', 'poolSessionType', 'supportedPoolProtocols' | |
$poolDataProperties = @{Property = $poolDataPropertyList } | |
# --| Session Data ------------------------------ | |
$sessionPropertyList = 'poolName', 'userName', 'clientBuild', 'connectionType', 'domain', 'duration', 'lastActiveTime', ` | |
'loginStatus', 'loginTime', 'userAgentOrHostOsName', 'userId', 'vmName' | |
$sessionProperties = @{Property = $sessionPropertyList } | |
# --| Application Data -------------------------- | |
$appExclusion = 'explorer', 'svchost', 'dllhost', 'smss', 'csrss', 'wininit', 'winlogon', 'WUDFHost', 'lsass', 'dwm' | |
$appPropertyList = 'poolName', 'name', 'cpuPercentage', 'createdTimestampInMS', 'diskIOBytes', 'memoryBytes', 'processId', ` | |
'userId', 'userName', 'vmName' | |
$appProperties = @{Property = $appPropertyList } | |
# --| Desktop Data ------------------------------ | |
$desktopPropertyList = 'poolName', 'name', 'dnsName', 'ipAddress', 'guestOS', 'desktopManagerName', 'numCPUs', 'poolId', ` | |
'macAddress', 'vmModel', 'diskSize', 'diskType', 'sessionAllocationState', 'powerOnDate', 'vmpowerState', 'state', 'vmId', ` | |
'daaSAgentVersion', 'vmlifeState', 'vmwareToolsState', 'agentErrorCode', 'memorySizeMB', 'hydraNodeId', 'viewAgentVersion' | |
$desktopProperties = @{Property = $desktopPropertyList } | |
# --| Utilization Data -------------------------- | |
$utilizationPropertyList = 'hydraNodeName', 'maxPossibleDesktopSessions', 'usedDesktopSessions', 'maxPossibleApplicationSessions', ` | |
'usedApplicationSessions', 'hydraNodeId' | |
$utilizationProperties = @{Property = $utilizationPropertyList } | |
# --| Authentication Variables ---------------------------- | |
# --| ----------------------------------------------------- | |
[string]$base = 'cloud' | |
[string]$baseURL = "https://$base.horizon.vmware.com" | |
[string]$loginURL = '/api/login/login' | |
$sessionVariable = $null | |
[hashtable]$authParams = @{ | |
username = $vmwareCredentials.username | |
password = $vmwareCredentials.GetNetworkCredential().password.ToCharArray() | |
} | |
[hashtable]$domainParams = @{ | |
username = $domainCredentials.username | |
password = $domainCredentials.GetNetworkCredential().password.ToCharArray() | |
} | |
[hashtable]$restParams = @{ | |
'ContentType' = 'application/json' | |
'Method' = 'Post' | |
} | |
$restParams.Body = ($authParams | ConvertTo-Json).ToString() | |
$restParams.Uri = BuildURI $loginURL | |
# --| VMWare Cloud Login ------------------------ | |
try { | |
$response = Invoke-RestMethod @restParams -SessionVariable sessionVariable | |
} catch { | |
$response = $null | |
$errorMessage = "Login failed: $($restParams.uri) as $($vmwareCredentials.username) : $_" | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
if ( ! $response -or ! $response.PSObject.Properties[ 'authSession' ] ) { | |
$errorMessage = "Did not receive auth session token from $($restParams.uri) as $($vmwareCredentials.username)" | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
$restParams.websession = $sessionVariable | |
[string]$domain = ($domainCredentials.username -split '\\')[0] | |
if ( $null -eq $response.PSObject.Properties[ 'credentialRequested' ] ) { | |
Log 'No credentialRequested returned from initial logon' 'ERROR' | |
} | |
if ( $domain -notin $response.domainNames ) { | |
Log "Domain name `"$domain`" in AD credential not in list of domains returned from Horizon - $($response.domainNames -join ',')" 'ERROR' | |
} | |
# --| Domain Login ------------------------------ | |
$authParams.domain = $domain | |
$authParams.username = ($domainParams.username -split '\\')[-1] | |
$authParams.password = $domainParams.password | |
$authParams.credentialRequested = $response.credentialRequested | |
$authParams.authSession = $response.authSession | |
$restParams.Body = ($authParams | ConvertTo-Json).ToString() | |
try { | |
$authentication = Invoke-RestMethod @restParams | |
} catch { | |
$authentication = $null | |
$errorMessage = "Domain login failed: $($restParams.uri) as $($domainCredential.UserName) : $_" | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
if ( ! $authentication -or $null -eq $authentication.PSObject.Properties['apiToken']) { | |
$errorMessage = 'No apiToken returned from domain login' | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
$restParams.Headers = @{ | |
'Authorization' = "Bearer $($authentication.apiToken)" | |
'Accept' = 'application/json' | |
'Content-Type' = 'application/json' | |
} | |
# --| Pool Data ------------------------------------------- | |
# --| ----------------------------------------------------- | |
function GetPoolData() { | |
$poolURI = '/dt-rest/v100/pool/manager/pools' | |
$restParams.Remove( 'Body' ) | |
$restParams.Method = 'GET' | |
$restParams.uri = BuildURI $poolURI | |
try { | |
$poolData = Invoke-RestMethod @restParams | |
} catch { | |
$poolData = $null | |
$errorMessage = "Failed to get pool data from $($restParams.uri) : $_" | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
if ( ($null -eq $poolData) -or ($poolData.Count -eq 0) ) { | |
$errorMessage = 'No pools returned from pool data' | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
foreach ($p in $poolData) { | |
$properties = $p | Select-Object @poolDataProperties | |
[void]$poolDataList.Add($properties) | |
} | |
return $poolData | |
} | |
# --| Session Data ---------------------------------------- | |
# --| ----------------------------------------------------- | |
function GetSessionData([string]$poolId, [string]$poolName) { | |
[hashtable]$poolIdData = @{ | |
'poolId' = $poolId | |
} | |
$sessionURI = '/dt-rest/v100/session/manager/session/active' | |
$restParams.Method = 'POST' | |
$restParams.uri = BuildURI $sessionURI | |
$restParams.Body = ($poolIdData | ConvertTo-Json).ToString() | |
try { | |
$sessionData = Invoke-RestMethod @restParams | |
} catch { | |
$sessionData = $null | |
$errorMessage = "Failed to get session data from $($restParams.uri) : $_" | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
foreach ($session in $sessionData) { | |
$session | Add-Member -MemberType NoteProperty -Name poolName -Value $poolName | |
$sessions = $session | Select-Object @sessionProperties | |
[void]$sessionsList.Add($sessions) | |
$sessionId = $session.id | |
$appURI = "/dt-rest/v100/session/active/${sessionId}/processes" | |
$restParams.Remove( 'Body' ) | |
$restParams.Method = 'GET' | |
$restParams.uri = BuildURI $appURI | |
try { | |
$appData = Invoke-RestMethod @restParams | |
} catch { | |
$appData = $null | |
$errorMessage = "Failed to get app data from $($restParams.uri) : $_" | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
if ( ($null -eq $appData) -or ($appData.Count -eq 0) ) { | |
$errorMessage = 'No pools returned from pool data' | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
foreach ($app in $appData) { | |
$appName = $app.name.Replace('.exe', '') | |
if ($appExclusion.Contains($appName)) { continue } | |
$app | Add-Member -MemberType NoteProperty -Name poolName -Value $poolName | |
$app = $app | Select-Object @appProperties | |
[void]$appsDataList.Add($app) | |
} | |
} | |
} | |
# --| Desktop Data ---------------------------------------- | |
# --| ----------------------------------------------------- | |
function GetDesktopData([string]$poolId, [string]$poolName) { | |
$desktopURI = "/dt-rest/v100/infrastructure/pool/desktop/${poolId}/vms" | |
$restParams.Remove('Body') | |
$restParams.Method = 'GET' | |
$restParams.uri = BuildURI $desktopURI | |
try { | |
$desktopData = Invoke-RestMethod @restParams | |
} catch { | |
$desktopData = $null | |
$errorMessage = "Failed to get desktop data from $($restParams.uri) : $_" | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
foreach ($desktop in $desktopData) { | |
$desktop | Add-Member -MemberType NoteProperty -Name pool -Value $poolName | |
$desktop = $desktop | Select-Object @desktopProperties | |
[void]$desktopsList.Add($desktop) | |
} | |
} | |
# --| Utilization Data ------------------------------------ | |
# --| ----------------------------------------------------- | |
function GetUtilization() { | |
$utilizationURI = '/dt-rest/v100/session/manager/session/utilization' | |
$restParams.Remove( 'Body' ) | |
$restParams.Method = 'GET' | |
$restParams.uri = BuildURI $utilizationURI | |
try { | |
$utilData = Invoke-RestMethod @restParams | |
} catch { | |
$utilData = $null | |
$errorMessage = "Failed to get utilization data from $($restParams.uri) : $_" | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
if ( ($null -eq $utilData) -or ($utilData.Count -eq 0) ) { | |
$errorMessage = 'No utilization reporting data returned' | |
Log $errorMessage 'ERROR' | |
throw $errorMessage | |
} | |
foreach ($util in $utilData) { | |
$util = $util | Select-Object @utilizationProperties | |
[void]$utilDataList.Add($util) | |
} | |
} | |
# --| Primary Execution ----------------------------------- | |
# --| ----------------------------------------------------- | |
$pools = GetPoolData | |
foreach ($pool in $pools) { | |
GetSessionData $pool.id $pool.name | |
GetDesktopData $pool.id $pool.name | |
} | |
GetUtilization | |
# --| Output ------------------------------------ | |
# --| Pool --------------------------------- | |
if ($poolDataList.Count -gt 0) { | |
'' > $poolDataOutput | |
foreach ($poolData in $poolDataList) { | |
$poolData.dateCreated = ConvertTime $poolData.dateCreated | |
$poolData.lastUpdated = ConvertTime $poolData.lastUpdated | |
$protocols = [system.String]::Join(",", $poolData.supportedPoolProtocols) | |
$poolData.supportedPoolProtocols = $protocols | |
} | |
$csvData = $poolDataList | ConvertTo-Csv | |
$csvData | Out-File $poolDataOutput -Force | |
} | |
# --| Session ------------------------------ | |
if ($sessionsList.Count -gt 0) { | |
'' > $sessionsOutput | |
foreach ($session in $sessionsList) { | |
$session.lastActiveTime = ConvertTime $session.lastActiveTime | |
$session.loginTime = ConvertTime $session.loginTime | |
} | |
$csvData = $sessionsList | ConvertTo-Csv | |
$csvData | Out-File $sessionsOutput -Force | |
} | |
# --| Desktop ------------------------------ | |
if ($desktopsList.Count -gt 0) { | |
'' > $desktopsOutput | |
foreach ($desktop in $desktopsList) { | |
$desktop.powerOnDate = ConvertTime $desktop.powerOnDate | |
} | |
$csvData = $desktopsList | ConvertTo-Csv | |
$csvData | Out-File $desktopsOutput -Force | |
} | |
# --| Applications ------------------------- | |
if ($appsDataList.Count -gt 0) { | |
'' > $appsDataOutput | |
foreach ($app in $appsDataList) { | |
$app.createdTimestampInMS = ConvertTime $app.createdTimestampInMS | |
} | |
$csvData = $appsDataList | ConvertTo-Csv | |
$csvData | Out-File $appsDataOutput -Force | |
} | |
# --| Utilization -------------------------- | |
if ($utilDataList.Count -gt 0) { | |
'' > $utilDataOutput | |
$csvData = $utilDataList | ConvertTo-Csv | |
$csvData | Out-File $utilDataOutput -Force | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment