vOperator.psm1

####################################################################################

############## Obsolete functions, will be replaced with new functions later on
Function Convert-hvAPIFilter {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, HelpMessage = "Your accesstoken to Horizon Connetion Server")]
        [ValidateNotNullOrEmpty()]
        [securestring]$accessToken,
        [Parameter(Mandatory = $true, HelpMessage = "Horizon Connection Server FQDN")]
        [ValidateScript({ $_ -notlike "https://*" })]
        [string]$hvURI,
        [Parameter(Mandatory = $true, HelpMessage = "Enter the restmethod you want to use for example /external/v1/audit-events")]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ $_ -like "/*" })]
        [String]$restMethod,
        [Parameter(Mandatory = $false, HelpMessage = "Used for URI query for example some rest method are /inventory/v1/desktop-pools/{id} then you will enter the /ID here")]
        #[ValidateScript({ $_ -like "/*" })]
        [String]$ID,
        [Parameter(Mandatory = $false, HelpMessage = "Used when some restmethods adds extra url at the end for example of the URI string /inventory/v2/desktop-pools/{id}/action/schedule-push-image")]
        [ValidateScript({ $_ -like "/*" })]
        [String]$urlExtra,
        [Parameter(Mandatory = $false, HelpMessage = "Pass your filter or filters here")]
        [Array]$Filter,
        [Parameter(Mandatory = $false, HelpMessage = "Enter filter type, for example And. This is case sensitiv")]
        [ValidateSet("Equals", "NotEquals", "Contains", "StartsWith", "Between", "Not", "And", "Or")]
        [String]$FilterType,
        [Parameter(Mandatory = $false, HelpMessage = "How you want to sort something either ASC or DESC (Case sensitiv), SortBy must be used combined with this")]
        [ValidateSet("ASC", "DESC")]
        [String]$OrderBy = "ASC",
        [Parameter(Mandatory = $false, HelpMessage = "Sort by property in return data from the REST API call")]
        [String]$SortBy,
        [Parameter(Mandatory = $false, HelpMessage = "How many result you want to show on each page, default 500")]
        [int]$PageSize = 500,
        [Parameter(Mandatory = $false, HelpMessage = "Use this if you want to use pagination")]
        [Switch]$Pagination = $false,
        [Parameter(Mandatory = $false, HelpMessage = "What type of filter you want to use")]
        [ValidateSet("ID", "IDFilter", "Filter", "uriFilter")]
        [String]$Type,
        [Parameter(Mandatory = $false, HelpMessage = "If you want to use filter in the URI example https://FQDN/rest/inventory/v1/global-sessions?user_id")]
        [ValidateScript({ $_ -notlike "*=*" })]
        [String]$uriFilterName,
        [Parameter(Mandatory = $false, HelpMessage = "If you want to use filter in the URI, enter value here example https://FQDN/rest/inventory/v1/global-sessions?user_id=YOURVALUE")]
        [String]$uriFilterValue
    )

    try {
        # Importing translation of the returncodes from the rest API
        #$ReturnCodeTranslation = (Import-tSetting -Category "Horizon" -Setting "API_ReturnCode" -All).ReturnValue

        #Building first state or URI for rest
        $firstURI = "https://$hvURI/rest" + $restMethod

        # If SortBy are populated it will add it to the URI you also need to use order_by. This should be last in the URI
        if (-Not([string]::IsNullOrEmpty($SortBy))) {
            $SortOrder = "&sort_by=$SortBy&order_by=$OrderBy"
        }

        # If filter type is used then it will format URI to work with filter
        if (-Not([string]::IsNullOrEmpty($FilterType))) {
            # Adding filter to API Call
            $FilterHashtable = [ordered]@{
                'type'    = $FilterType
                'filters' = [array]$Filter
            }
        
            # Making sure that the depth of the filter is correct
            $FilterJson = $FilterHashtable | ConvertTo-Json -Compress -Depth 5 -WarningVariable FilterJsonWarning
        
            # Making sure that the JSON depth are right, if not it will increase the depth to be correct
            if ($FilterJsonWarning) {
                $Depth = 6
                while ($FilterJsonWarning) {
                    $Depth++
                    $FilterJson = $FilterHashtable | ConvertTo-Json -Depth $Depth -Compress -WarningVariable FilterJsonWarning
                }
            }
            
            # Building filter string to URI
            $uriFilter = "filter=" + $FilterJson
        }
        # If ID is used it will format URI to work with ID
        elseif ($Type -eq "ID") {
            # If somethings is in $ID it will convert the secondURI in the correct way
            $secondURI = $firstURI + $ID + "?"
        }
        elseif ($Type -eq "uriFilter") {
            $secondURI = $firstURI + "?" + $uriFilterName + "=" + $uriFilterValue
        }
        else {
            # If ID is empty this will be the secondURI convertation
            $secondURI = $firstURI + "?"
        }

        if ($Pagination -eq $true) {
            Switch ($Type) {
                uriFilter {
                    $finalURI = $firstURI + "?" + $uriFilterName + "=" + $uriFilterValue + "&" + "page="
                }
                Filter {
                    $finalURI = $firstURI + "?" + $uriFilter + "&" + "page="
                }
                IDFilter {
                    $finalURI = $firstURI + "?" + $uriFilterName + "=" + $uriFilterValue + "&" + "page="
                }
                ID {
                    $finalURI = $secondURI + "page="
                }
                default {
                    $finalURI = $secondURI + "page="
                }
            }

            # Looking how many pages it should be and making sure that it search trough each page
            $Page = 0
            do {
                $Page++
                if ($Type -eq "IDFilter") {
                    $pageURI = $finalURI + $Page + "&size=$PageSize" + "&" + $uriFilter
                }
                else {
                    $pageURI = $finalURI + $Page + "&size=$PageSize" + $SortOrder
                }
                $APICall = Invoke-RestMethod -Uri $pageURI -Method Get -ContentType "application/json" -Authentication Bearer -Token $accessToken -StatusCodeVariable "StatusCode" -ResponseHeadersVariable "responsHeader" -HttpVersion 3.0
            } while ($responsHeader.HAS_MORE_RECORDS -eq $true)
        }
        else {
            $finalURI = $secondURI + $urlExtra + $SortOrder
            $APICall = Invoke-RestMethod -Uri $finalURI -Method Get -ContentType "application/json" -Authentication Bearer -Token $accessToken -StatusCodeVariable "StatusCode" -ResponseHeadersVariable "responsHeader" -HttpVersion 3.0
        }

        # Retriving the results
        if ($StatusCode -eq 200 -and $null -ne $APICall) {
            return Get-ReturnMessageTemplate -ReturnType Success -Message "Success, did retrive all data from REST API Call against $finalURI" -ReturnValue $APICall
            Break
        }
        else {
            return Get-ReturnMessageTemplate -ReturnType Error -Message "$($ReturnCodeTranslation.$($StatusCode).translate), ErrorCode: $StatusCode" -ReturnValue $StatusCode
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)"
        break
    }
}
################################################################################

Function Get-ReturnMessageTemplate {
    <#
        .SYNOPSIS
        Return messages and value in the correct format

        .DESCRIPTION
        This function will return value and messages in the correct format for vOperator to use.

        .PARAMETER ReturnType

        .PARAMETER Message

        .PARAMETER ReturnValue

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, HelpMessage = "What kind of message do you want template for")]
        [ValidateSet("Error", "Success", "Warning", "Information")]
        [string]$ReturnType,
        [Parameter(Mandatory = $false, HelpMessage = "Message that you want to return")]
        $Message = "N/A",
        [Parameter(Mandatory = $false, HelpMessage = "Return value that you want to present for the user, not return message.")]
        $ReturnValue = "N/A"
    )

    Switch ($ReturnType) {
        Error {
            return [PSCustomObject]@{
                ReturnCode  = 1
                Severity    = "Error"
                Icon        = "xmark"
                IconColor   = "red"
                Color       = 'red'
                Duration    = "4000"
                Message     = $Message
                ReturnValue = $ReturnValue
            }
            Break
        }
        Success {
            return [PSCustomObject]@{
                ReturnCode  = 0
                Severity    = "Success"
                Icon        = "Check"
                IconColor   = "green"
                Color       = 'green'
                Duration    = "4000"
                Message     = $Message
                ReturnValue = $ReturnValue
            }
            Break
        }
        Warning {
            return [PSCustomObject]@{
                ReturnCode  = 2
                Severity    = "Warning"
                Icon        = "TriangleExclamation"
                IconColor   = "yellow"
                Color       = 'yellow'
                Duration    = "4000"
                Message     = $Message
                ReturnValue = $ReturnValue
            }
            Break
        }
        Information {
            return [PSCustomObject]@{
                ReturnCode  = 3
                Severity    = "Info"
                Icon        = "CircleInfo"
                IconColor   = "blue"
                Color       = 'blue'
                Duration    = "4000"
                Message     = $Message
                ReturnValue = $ReturnValue
            }
            Break
        }
    }
}
Function Disconnect-hvSrv {
    <#
        .SYNOPSIS
        Disconnect Horizon connection against the API on the Connection Server

        .DESCRIPTION

        .PARAMETER hvURI

        .PARAMETER accessToken

        .PARAMETER refreshToken

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>

    
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Connection Server FQDN")]
        [ValidateScript({ $_ -notlike "https://*" })]
        [string]$hvURI,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "AccessToken to Horizon")]
        [ValidateNotNullOrEmpty()]
        [securestring]$accessToken,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Refresh token from Horizon")]
        [ValidateNotNullOrEmpty()]
        $refreshToken
    )

    try {
        $Body = [ordered]@{
            'refresh_token' = $refreshToken
        }

        $Connection = Invoke-RestMethod -Uri "https://$hvURI/rest/logout" -Method Post -Body ($Body | ConvertTo-Json) -ContentType "application/json" -StatusCodeVariable "StatusCode" -ResponseHeadersVariable "ResponseHeaders" -HttpVersion 3.0
        return Get-ReturnMessageTemplate -ReturnType Success -Message "Disconnected from VMWare Horizon $hvURI"
        Break
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)" -ReturnValue "$($PSItem.Exception.Message)"
        Break
    }
}
Function Get-hvVM {
    <#
        .SYNOPSIS
        This function will return either all VMs that exists in the Horizon POD or a singel machine.

        .DESCRIPTION
        If you don't use -VM "MACHINENAME" you will get all machines

        .PARAMETER hvURI

        .PARAMETER accessToken

        .PARAMETER FilterName

        .PARAMETER FilterValue

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Connection Server FQDN")]
        [ValidateScript({ $_ -notlike "https://*" })]
        [string]$hvURI,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "AccessToken to Horizon")]
        [ValidateNotNullOrEmpty()]
        [SecureString]$accessToken,
        [Parameter(Mandatory = $false, HelpMessage = "Filter on")]
        [string]$FilterName = "name",
        [Parameter(Mandatory = $false, HelpMessage = "Value for filter")]
        [string]$FilterValue
    )

    try {
        if ([string]::IsNullOrEmpty($FilterValue)) {
            $APICall = Convert-hvAPIFilter -hvURI $hvURI -accessToken $accessToken -SortBy "name" -restMethod "/inventory/v5/machines" -Pagination
            $LogText = "all VMs"
        }
        else {
            [array]$filtersAPI = [ordered]@{
                'type'  = 'Equals'
                'name'  = $FilterName
                'value' = $FilterValue
            }

            $APICall = Convert-hvAPIFilter -Filter $filtersAPI -FilterType And -hvURI $hvURI -accessToken $accessToken -SortBy "name" -restMethod "/inventory/v5/machines" -Pagination -Type Filter
            $LogText = "VM $FilterName $FilterValue"
        }
        if ($APICall.ReturnCode -eq 0 -and $APICall.ReturnValue.Count -gt 0) {
            return Get-ReturnMessageTemplate -ReturnType Success -Message "Information about $LogText have been collected." -ReturnValue $($APICall.ReturnValue)
            Break
        }
        else {
            return Get-ReturnMessageTemplate -ReturnType Error -Message "Information about $LogText could not be collected." -ReturnValue $APICall
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)" -ReturnValue "$($PSItem.Exception)"
        break
    }
}
Function Set-hvVMUserAssignment {
    <#
        .SYNOPSIS
        Assign or Unassign user to persistent VM in Horizon

        .DESCRIPTION

        .PARAMETER accessToken
        Horizon Access token

        .PARAMETER UserName
        Enter Username of the user that you want to change assignment for, if you running this script on a non Windows machine please enter SID instead of username.

        .PARAMETER hvURI
        Enter URI for the Horizon Connection Server, the hole FQDN. Don't use https://

        .PARAMETER VM
        Plain name of the VM you want to assign or unassign user on.

        .PARAMETER Action
        Enter what kind of action you want to execute. Valide inputs are "unassign", "assign"

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName, HelpMessage = "AccessToken to Horizon")]
        [ValidateNotNullOrEmpty()]
        [SecureString]$accessToken,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName, HelpMessage = "Connection Server FQDN, don't enter https://")]
        [ValidateScript({ $_ -notlike "https://*" })]
        [String]$hvURI,
        [Parameter(Mandatory = $true, HelpMessage = "Name of the VM you want to get assign or unassign user against")]
        [ValidateNotNullOrEmpty()]
        [String]$VM,
        [Parameter(Mandatory = $true, HelpMessage = "Enter what you want to do here, valid input is unassign or assign")]
        [ValidateSet("unassign", "assign")]
        [String]$Action,
        [Parameter(Mandatory = $true, HelpMessage = "Enter username of the user or users you want to make changes for, if your running this script on no Windows machine please enter SID instead of username.")]
        [ValidateNotNullOrEmpty()]
        [String]$UserName
    )
    
    try {
        Switch ($Action) {
            assign {
                [string]$urlAction = "assign-users"
            }
            unassign {
                [string]$urlAction = "unassign-users"
            }
        }

        # Get VM information from Horizon
        $GetVMInfo = Get-hvVM -FilterName name -FilterValue $VM -accessToken $accessToken -hvURI $hvURI
        if ($GetVMInfo.ReturnCode -eq 0) {
            $VMInfo = $GetVMInfo.ReturnValue
            [String]$VMID = "$($VMInfo.Id)"
        }

        if ($PSVersionTable.Platform -like "Win32NT") {
            # Convert UserName to SID
            $UserSID = $(Get-ADUser -Filter "sAMAccountName -eq '$UserName'" | select-object -ExpandProperty SID).value
        }
        else {
            $UserSID = $UserName 
        }
        
        # Creating Body
        [string]$RAWBody = $UserSID
        $Body = "[$($RAWBody | ConvertTo-Json)]"
    
        $APICall = Invoke-RestMethod -Uri "https://$hvURI/rest/inventory/v1/machines/$VMID/action/$urlAction" -Method POST -Body $Body -Authentication Bearer -Token $accessToken -ContentType "application/json" -StatusCodeVariable "StatusCode" -HttpVersion 3.0
            
        if ($StatusCode -eq 200 -and $APICall.Status_Code -eq 200) {
            return Get-ReturnMessageTemplate -ReturnType Success -Message "$UserName are now $Action to $VM" -ReturnValue $APICall
            Break
        }
        else {
            return Get-ReturnMessageTemplate -ReturnType Error -Message "Could not $Action on VM $VM for user $UserName, API Status Code: $StatusCode Call Status Code: $($APICall.Status_Code)" -ReturnValue "API Status Code: $StatusCode Call Status Code: $($APICall.Status_Code)"
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)" -ReturnValue "$($PSItem.Exception.Message)"
        Break
    }
}
Function Get-hvDesktopPool {
    <#
        .SYNOPSIS
        This function let you get a specific Desktop Pool

        .DESCRIPTION

        .PARAMETER hvURI

        .PARAMETER accessToken

        .PARAMETER FilterValue

        .PARAMETER FilterName

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Connection Server FQDN")]
        [ValidateScript({ $_ -notlike "https://*" })]
        [string]$hvURI,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "AccessToken to Horizon")]
        [ValidateNotNullOrEmpty()]
        [SecureString]$accessToken,
        [Parameter(Mandatory = $false, HelpMessage = "Name of the pool")]
        [ValidateNotNullOrEmpty()]
        [string]$FilterValue,
        [Parameter(Mandatory = $false, HelpMessage = "What you want to search for")]
        [ValidateSet("name", "display_name", "id", "global_desktop_entitlement_id")]
        [string]$FilterName = "name"
    )

    try {
        if ($ID -eq $false) {
            #/inventory/v7/desktop-pools/{id}
        }
        elseif ([string]::IsNullOrEmpty($FilterValue)) {
            $APICall = Convert-hvAPIFilter -hvURI $hvURI -accessToken $accessToken -SortBy "name" -restMethod "/inventory/v7/desktop-pools" -Pagination
        }
        else {
            [array]$filtersAPI = [ordered]@{
                'type'  = 'Equals'
                'name'  = $FilterName
                'value' = $FilterValue
            }
            $APICall = Convert-hvAPIFilter -Filter $filtersAPI -FilterType And -hvURI $hvURI -accessToken $accessToken -SortBy "name" -restMethod "/inventory/v7/desktop-pools" -Pagination -Type Filter
        }

        if ($APICall.ReturnCode -eq 0 -and $APICall.ReturnValue.Count -gt 0) {
            return Get-ReturnMessageTemplate -ReturnType Success -Message "All information about Desktop Pool $FilterValue has been collected" -ReturnValue $APICall.ReturnValue
            Break
        }
        else {
            return Get-ReturnMessageTemplate -ReturnType Error -Message "Could not find any Desktop Pool with the $FilterName $FilterValue" -ReturnValue $APICall.ReturnValue
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "Get-NewhvDesktopPool $($PSItem.Exception.Message)"
        break
    }
}
Function Set-hvVMPool {
    <#
        .SYNOPSIS

        .DESCRIPTION

        .PARAMETER hvURI

        .PARAMETER accessToken

        .PARAMETER VM

        .PARAMETER DesktopPool

        .PARAMETER Action

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Enter FQDN to one Connection Server")]
        [ValidateNotNullOrEmpty()]
        [string]$hvURI,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Access token for authentication")]
        [ValidateNotNullOrEmpty()]
        [SecureString]$accessToken,
        [Parameter(Mandatory = $true, HelpMessage = "Name of the VM")]
        [ValidateNotNullOrEmpty()]
        [string]$VM,
        [Parameter(Mandatory = $true, HelpMessage = "Name of Horizon Pool")]
        [ValidateNotNullOrEmpty()]
        [string]$DesktopPool,
        [Parameter(Mandatory = $true, HelpMessage = "Select what you want to do, add or remove machine from pool")]
        [ValidateSet("Add", "Remove")]
        [string]$Action,
        [Parameter(Mandatory = $false, HelpMessage = "if you use this you need to enter VM ID in VM parameter and Pool ID in DesktopPool parameter")]
        [Switch]$UseID = $false
    )

    try {
        if ($UseID -eq $false) {
            # Check so the Desktop Pool exists and extract the ID
            $CheckDesktopPool = Get-hvDesktopPool -FilterValue $DesktopPool -hvURI $hvURI -accessToken $accessToken
            if ($CheckDesktopPool.ReturnCode -eq 0) {
                if ($Null -ne $CheckDesktopPool) {
                    [String]$DesktopPoolID = $($CheckDesktopPool.ReturnValue).Id
                }
                else {
                    return Get-ReturnMessageTemplate -ReturnType Error -Message "Could not find any Desktop Pool with the name $DesktopPool"
                    Break
                }
            }
            else {
                return $CheckDesktopPool
                Break
            }

            # Collecting virutal center ID
            $GetVirtualCenter = Get-hvVirtualCenter -hvURI $hvURI -accessToken $accessToken
            if ($GetVirtualCenter.ReturnCode -eq 0) {
                [String]$vCenterID = $($GetVirtualCenter.ReturnValue.id -as [string])
            }
            else {
                return $GetVirtualCenter
                Break
            }

            # Collecting all VMs from virtual center and grabbing the correct one.
            $GetVirtualCenterVMs = Get-hvVirtualCenterVM -VM $VM -VirtualCenterID $vCenterID -hvURI $hvURI -accessToken $accessToken
            if ($GetVirtualCenterVMs.ReturnCode -eq 0) {
                [String]$VMID = $($GetVirtualCenterVMs.ReturnValue).Id
            }
            else {
                return $GetVirtualCenterVMs
                Break
            }
        }
        else {
            $DesktopPoolID = $DesktopPool
            $VMID = $VM
        }


        # Adding VM to pool
        $Body = "[$($VMID | ConvertTo-Json)]"
        if ($Action -like "Add") {
            $APICall = Invoke-RestMethod -Uri "https://$hvURI/rest/inventory/v1/desktop-pools/$DesktopPoolID/action/add-machines" -Method Post -Body $Body -ContentType "application/json" -Authentication Bearer -Token $accessToken -HttpVersion 3.0 -StatusCodeVariable "StatusCode"
        }
        else {
            $APICall = Invoke-RestMethod -Uri "https://$hvURI/rest/inventory/v1/desktop-pools/$DesktopPoolID/action/remove-machines" -Method Post -Body $Body -ContentType "application/json" -Authentication Bearer -Token $accessToken -HttpVersion 3.0 -StatusCodeVariable "StatusCode"
        }

        if ($APICall.status_code -notlike "4*") {
            return Get-ReturnMessageTemplate -ReturnType Success -Message "VM $VM is now $($Action) to/from VMWare Horizon pool $DesktopPool" -ReturnValue $APICall
            Break
        }
        else {
            return Get-ReturnMessageTemplate -ReturnType Error -Message "Could not $($Action) VM $VM to/from VMWare Horizon pool $DesktopPool, StatusCode: $($APICall.status_code)" -ReturnValue $APICall
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)" -ReturnValue "$($PSItem.Exception.Message)"
        Break
    }
}
Function Convert-hvAPI {
    <#
    This function will format API url for Horizon to the proper format.
    It's for calls with two filters in the url string

    ### Type IDurlfilter ###
    With one urlfilter parameter used -ID , -urlfilter , urlvalue
    https://FQDN/rest/external/v2/base-vms/YOURID?vcenter_id=YOURVALUE

    With two urlfilter, parameter used -ID , -urlfilter , urlvalue -urlfilter2 -urlvalue2
    https://FQDN/rest/external/v2/base-vms/YOURID?vcenter_id=YOURVALUE&datacenter_id=YOURVALUE

    ### Type urlfilter ###
    With one urlfilter
    https://FQDN/rest/external/v2/base-snapshots?vcenter_id=YOURVALUE&page=1&size=10&filter=FILTER&sort_by=id&order_by=ASC

    With two urlfilter
    https://FQDN/rest/external/v2/base-snapshots?vcenter_id=YOURVALUE&base_vm_id=YOURVALUE&page=1&size=10&filter=FILTER&sort_by=id&order_by=ASC

    And if it's not with a filter
    Example command:
    Convert-hvAPI -hvURI FQDN -accessToken ACCESSTOKEN -restMethod "/external/v1/virtual-machines" -uriFilterName "vcenter_id" -uriFilterValue $VirtualCenterID -Type urlfilter -Pagination
    Example url:
    https://FQDN/rest/external/v1/virtual-machines?vcenter_id=VIRTUALCENTERID&page=1&size=10&sort_by=id&order_by=ASC
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, HelpMessage = "Your accesstoken to Horizon Connetion Server")]
        [ValidateNotNullOrEmpty()]
        [securestring]$accessToken,
        [Parameter(Mandatory = $true, HelpMessage = "Horizon Connection Server FQDN")]
        [ValidateScript({ $_ -notlike "https://*" })]
        [string]$hvURI,
        [Parameter(Mandatory = $true, HelpMessage = "Enter the restmethod you want to use for example /external/v1/audit-events")]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ $_ -like "/*" })]
        [String]$restMethod,
        [Parameter(Mandatory = $false, HelpMessage = "What type of url you want to convert it to")]
        [ValidateSet("UrlFilter", "IDUrlFilter", "ID")]
        [String]$Type,
        [Parameter(Mandatory = $false, HelpMessage = "Pass your filter or filters here")]
        [Array]$Filter,
        [Parameter(Mandatory = $false, HelpMessage = "Enter filter type, for example And. This is case sensitiv")]
        [ValidateSet("Equals", "NotEquals", "Contains", "StartsWith", "Between", "Not", "And", "Or")]
        [String]$FilterType,
        [Parameter(Mandatory = $false, HelpMessage = "How you want to sort something either ASC or DESC (Case sensitiv), SortBy must be used combined with this")]
        [ValidateSet("ASC", "DESC")]
        [String]$OrderBy = "ASC",
        [Parameter(Mandatory = $false, HelpMessage = "Sort by property in return data from the REST API call")]
        [String]$SortBy,
        [Parameter(Mandatory = $false, HelpMessage = "IDUrlFilter, only used")]
        [String]$ID,
        [Parameter(Mandatory = $false, HelpMessage = "How many result you want to show on each page, default 500")]
        [int]$PageSize = 100,
        [Parameter(Mandatory = $false, HelpMessage = "Use this if you want to use pagination")]
        [Switch]$Pagination = $false,
        [Parameter(Mandatory = $false, HelpMessage = "If you want to use filter in the URI example https://FQDN/rest/inventory/v1/global-sessions?user_id it's the user_id")]
        [ValidateScript({ $_ -notlike "*=*" })]
        [String]$uriFilterName,
        [Parameter(Mandatory = $false, HelpMessage = "If you want to use filter in the URI, enter value here example https://FQDN/rest/inventory/v1/global-sessions?user_id=YOURVALUE")]
        [String]$uriFilterValue,
        [Parameter(Mandatory = $false, HelpMessage = "Second URL filter, If you want to use filter in the URI example https://FQDN/rest/inventory/v1/global-sessions?user_id it's the user_id")]
        [ValidateScript({ $_ -notlike "*=*" })]
        [String]$uriFilterName2,
        [Parameter(Mandatory = $false, HelpMessage = "Second URL filter, If you want to use filter in the URI, enter value here example https://FQDN/rest/inventory/v1/global-sessions?user_id=YOURVALUE")]
        [String]$uriFilterValue2
    )

    try {
        # Importing translation of the returncodes from the rest API
        #$ReturnCodeTranslation = (Import-tSetting -Category "Horizon" -Setting "Return_Code" -All).ReturnValue

        #Building first state or URI for rest
        $firstURI = "https://$hvURI/rest" + $restMethod

        # If SortBy are populated it will add it to the URI you also need to use order_by. This should be last in the URI
        if (-Not([string]::IsNullOrEmpty($SortBy))) {
            $SortOrder = "&sort_by=$SortBy&order_by=$OrderBy"
        }

        # If filter type is used then it will format URI to work with filter
        if (-Not([string]::IsNullOrEmpty($FilterType))) {
            # Adding filter to API Call
            $FilterHashtable = [ordered]@{
                'type'    = $FilterType
                'filters' = [array]$Filter
            }
        
            # Making sure that the depth of the filter is correct
            $FilterJson = $FilterHashtable | ConvertTo-Json -Compress -Depth 5 -WarningVariable FilterJsonWarning
        
            # Making sure that the JSON depth are right, if not it will increase the depth to be correct
            if ($FilterJsonWarning) {
                $Depth = 6
                while ($FilterJsonWarning) {
                    $Depth++
                    $FilterJson = $FilterHashtable | ConvertTo-Json -Depth $Depth -Compress -WarningVariable FilterJsonWarning
                }
            }
            
            # Building filter string to URI
            $uriFilter = "filter=" + $FilterJson
        }
        else {
            Switch ($Type) {
                urlfilter {
                    if (-Not([string]::IsNullOrEmpty($uriFilterName2))) {
                        $secondURI = $firstURI + "?" + $uriFilterName + "=" + $uriFilterValue + "&" + $uriFilterName2 + "=" + $uriFilterValue2
                    }
                    else {
                        $secondURI = $firstURI + "?" + $uriFilterName + "=" + $uriFilterValue
                    }
                }
                IDUrlFilter {
                    if (-Not([string]::IsNullOrEmpty($uriFilterName2))) {
                        $secondURI = $firstURI + "/$($ID)" + "?" + $uriFilterName + "=" + $uriFilterValue + "&" + $uriFilterName2 + "=" + $uriFilterValue2
                    }
                    else {
                        $secondURI = $firstURI + "/$($ID)" + "?" + $uriFilterName + "=" + $uriFilterValue
                    }
                }
            }
        }

        if ($Pagination -eq $true) {
            Switch ($Type) {
                urlfilter {
                    if (-Not([string]::IsNullOrEmpty($uriFilterName2))) {
                        $finalURI = $firstURI + "?" + $uriFilterName + "=" + $uriFilterValue + "&" + $uriFilterName2 + "=" + $uriFilterValue2 + "&" + "page="
                    }
                    else {
                        $finalURI = $firstURI + "?" + $uriFilterName + "=" + $uriFilterValue + "&" + "page="
                    }
                }
            }

            # Looking how many pages it should be and making sure that it search trough each page
            $Page = 0
            do {
                $Page++
                if (-Not([string]::IsNullOrEmpty($FilterType))) {
                    $pageURI = $finalURI + $Page + "&size=$PageSize" + "&" + $uriFilter + $SortOrder
                }
                else {
                    $pageURI = $finalURI + $Page + "&size=$PageSize" + $SortOrder
                }

                $APICall = Invoke-RestMethod -Uri $pageURI -Method Get -ContentType "application/json" -Authentication Bearer -Token $accessToken -StatusCodeVariable "StatusCode" -ResponseHeadersVariable "responsHeader" -HttpVersion 3.0
            } while ($responsHeader.HAS_MORE_RECORDS -eq $true)
        }
        else {
            $finalURI = $secondURI + $SortOrder
            $APICall = Invoke-RestMethod -Uri $finalURI -Method Get -ContentType "application/json" -Authentication Bearer -Token $accessToken -StatusCodeVariable "StatusCode" -ResponseHeadersVariable "responsHeader" -HttpVersion 3.0
        }

        # Retriving the results
        if ($StatusCode -eq 200 -and $null -ne $APICall) {
            return Get-ReturnMessageTemplate -ReturnType Success -Message "Success, did retrive all data from REST API Call against $finalURI" -ReturnValue $APICall
            Break
        }
        else {
            return Get-ReturnMessageTemplate -ReturnType Error -Message "$($ReturnCodeTranslation.$($StatusCode).translate), ErrorCode: $StatusCode" -ReturnValue $StatusCode
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)"
        break
    }
}
Function Connect-hvSrv {
    <#
        .SYNOPSIS
        Connect to Horizon Connection Server

        .DESCRIPTION
        With this function you can connect to Horizon Connection Servers and it will return the accessToken and refresh token.
        If the connection server that this function trys to connect to is down, maintaince or similar it will try to connect to an other Connection server at the same POD.
        You can either connect to one specific POD or set All parameter to true then it will randomly connect to some connection server at any of your PODs.

        .PARAMETER POD
        Specify what POD you want to connect to.

        .PARAMETER All
        You can set this to either $true or $false, if you set this to $true it will try to connect to a random connection server at a random POD.

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>


    [CmdletBinding()]
    Param(
        <#[Parameter(Mandatory = $false, HelpMessage = "What POD you want to get connection servers from, don't combine this with All bool")]
        [String]$POD,
        [Parameter(Mandatory = $false, HelpMessage = "Use this if you want to get try to connect to any one of your connection servers in all of your PODs")]
        [bool]$All = $false,#>

        [Parameter(Mandatory = $true, HelpMessage = "This should only be used if it's a new setup with the JSONFile hvEnvironment script")]
        [String]$hvFQDN,
        [Parameter(Mandatory = $true, HelpMessage = "Credentials to connect to Horizon Connection Server")]
        [pscredential]$Cred,
        [Parameter(Mandatory = $true, HelpMessage = "Domain name, not doman.xx it should only be domain")]
        [String]$Domain
    )

    try {
        # Importing settings
        #$SystemSettings = (Import-tSetting -Category "System" -Setting "Settings" -All).ReturnValue
       
        <#
        # Get a horizon connection server that's working
        if ([String]::IsNullOrEmpty($hvFQDN)) {
            $hvServer = Get-hvServer -POD $POD -All $All
        }
        #>


        if ($hvServer.ReturnCode -eq 0 -and [String]::IsNullOrEmpty($hvFQDN) -or (-Not[String]::IsNullOrEmpty($hvFQDN))) {
            <#if (-Not[String]::IsNullOrEmpty($hvFQDN)) {
            $hvInfo = "N/A"
            $hvFQDN = $hvFQDN
            }
            else {
                $hvInfo = $hvServer.ReturnValue
                [String]$hvFQDN = "$($hvInfo.fqdn)"
            }#>


            $Body = [ordered]@{
                'domain'   = $($Domain)
                'username' = $($Cred.UserName)
                'password' = $($Cred.GetNetworkCredential().Password)
            }

            $APICall = Invoke-RestMethod -Uri "https://$hvFQDN/rest/login" -Method Post -Body ($Body | ConvertTo-Json) -ContentType "application/json" -StatusCodeVariable "StatusCode" -ResponseHeadersVariable "ResponseHeader" -HttpVersion 3.0

            if ($StatusCode -eq 200) {
                #$Pod_Name = $(Convert-WhiteSpaceToDot -String $hvInfo.pod_name)
                #$hvName = $(Convert-WhiteSpaceToDot -String $hvInfo.name)
                return [PSCustomObject]@{
                    ReturnCode   = 0
                    accessToken  = ConvertTo-SecureString $APICall.access_token -AsPlainText -Force
                    refreshToken = $APICall.refresh_token
                    #hvName = $hvName
                    hvURI        = $hvFQDN
                    #POD_Name = $Pod_Name
                    connectTime  = Get-Date
                    Message      = "Connection established to Horizon Connection Server $hvName with FQDN $hvFQDN in the POD $Pod_Name"
                }
                Break
            }
            else {
                return Get-ReturnMessageTemplate -ReturnType Error -Message "Could not connect to the Horizon Connection Server, ErrorCode: $StatusCode" -ReturnValue "$($StatusCode)"
                Break
            }
        }
        else {
            return $hvServer
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)" -ReturnValue "$($PSItem.Exception.Message)"
        Break
    }
}
Function Get-hvVirtualCenter {
    <#
    This function will collect information about all Virtual Centers from the POD
    If you fill out $VirtualCenterID you will only get information about that specific Virtual Center
    if you don't fill it out you will get information about all Virtual Centers
    #>

    <#
        .SYNOPSIS

        .DESCRIPTION

        .PARAMETER hvURI

        .PARAMETER accessToken

        .PARAMETER VirtualCenterID

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Enter FQDN to one Connection Server")]
        [ValidateNotNullOrEmpty()]
        [string]$hvURI,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Access token for authentication")]
        [ValidateNotNullOrEmpty()]
        [SecureString]$accessToken,
        [Parameter(Mandatory = $false, HelpMessage = "What Virtual Center you want to get information about")]
        [string]$VirtualCenterID
    )

    try {
        if ([string]::IsNullOrEmpty($VirtualCenterID)) {
            $APICall = Invoke-RestMethod -Uri "https://$hvURI/rest/config/v4/virtual-centers" -Method Get -Body $Body -Authentication Bearer -Token $accessToken -ContentType "application/json" -StatusCodeVariable "StatusCode" -HttpVersion 3.0
            $LogText = "all Virtual Centers"
        }
        else {
            $APICall = Invoke-RestMethod -Uri "https://$hvURI/rest/config/v4/virtual-centers/$VirtualCenterID" -Method Get -Body $Body -Authentication Bearer -Token $accessToken -ContentType "application/json" -StatusCodeVariable "StatusCode" -HttpVersion 3.0
            $LogText = "Virtual Center with ID $VirtualCenterID"
        }
        if ($StatusCode -eq 200) {
            return Get-ReturnMessageTemplate -ReturnType Success -Message "Information about$LogText has been collected." -ReturnValue $APICall
            Break
        }
        else {
            return Get-ReturnMessageTemplate -ReturnType Error -Message "Could not collect information about $LogText from VMWare Horizon Connection Server $hvURI, StatusCode: $StatusCode" -ReturnValue "StatusCode: $StatusCode"
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)"
        break
    }
}
Function Get-hvVirtualCenterVM {
    <#
        .SYNOPSIS
        This function will get all virtual centers for the POD

        .DESCRIPTION

        .PARAMETER hvURI

        .PARAMETER accessToken

        .PARAMETER VM

        .PARAMETER VirtualCenterID

        .EXAMPLE

        .LINK

        .NOTES
        Author: Robin Stolpe
        Private: robin@stolpe.io
        Twitter / X: https://twitter.com/rstolpes
        Website: https://stolpe.io
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Enter FQDN to one Connection Server")]
        [ValidateNotNullOrEmpty()]
        [string]$hvURI,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, HelpMessage = "Access token for authentication")]
        [ValidateNotNullOrEmpty()]
        [SecureString]$accessToken,
        [Parameter(Mandatory = $false, HelpMessage = "Name of the VM")]
        [String]$VM,
        [Parameter(Mandatory = $true, HelpMessage = "ID for the virtual center")]
        [String]$VirtualCenterID
    )

    try {

        $APICall = Convert-hvAPI -hvURI $hvURI -accessToken $accessToken -restMethod "/external/v1/virtual-machines" -uriFilterName "vcenter_id" -uriFilterValue $VirtualCenterID -Type urlfilter -Pagination

        if ($APICall.ReturnCode -eq 0 -and $null -ne $APICall.ReturnValue) {
            $VerifyVM = $($APICall.ReturnValue | Where-Object { $_.name -like "$($VM)" })
            if ($Null -ne $VerifyVM) {
                return Get-ReturnMessageTemplate -ReturnType Success -Message "Information about VM $VM have been collected from Virtual Center ID $VirtualCenterID" -ReturnValue $VerifyVM
                Break
            }
            else {
                return Get-ReturnMessageTemplate -ReturnType Error -Message "Could not find any VM with the name $VM in Virtual Center ID $VirtualCenterID" -ReturnValue $APICall.ReturnValue
                Break
            }
        }
        else {
            return Get-ReturnMessageTemplate -ReturnType Error -Message "Could not collect VMs from Vritual Center ID $VirtualCenterID, StatusCode: $StatusCode" -ReturnValue "StatusCode: $StatusCode"
            Break
        }
    }
    catch {
        return Get-ReturnMessageTemplate -ReturnType Error -Message "$($PSItem.Exception.Message)" -ReturnValue "$($PSItem.Exception)"
        Break
    }
}