Nexthink-Omnissa-Connector.psm1
<#PSScriptInfo
.VERSION 0.1.1 .GUID 6e063c9e-4db5-4d3b-9f1d-omnissa202406 .AUTHOR integration.madrid@nexthink.com .COMPANYNAME Nexthink .PROJECTURI https://github.com/nexthink/vdi-experience.connector-omnissa-horizon-server #> <# .DESCRIPTION This script gathers machine and desktop pool information from Omnissa Horizon REST API and logs the results. #> # # Constants definition New-Variable -Name 'SCRIPT_VERSION' -Value "0.1.1" -Option ReadOnly -Scope Script -Force New-Variable -Name 'START_TIME' -Value (Get-Date) -Option ReadOnly -Scope Script -Force # LOGGING New-Variable -Name 'LOG_FORMAT' -Value "[%{timestamp:+yyyy-MM-dd HH:mm:ss.fffffzzz}][%{level:-7}][%{lineno:3}] %{message}" -Option ReadOnly -Scope Script -Force New-Variable -Name 'LOG_RETENTION_DAYS' -Value 7 -Option ReadOnly -Scope Script -Force New-Variable -Name 'LOG_LEVEL' -Value 'INFO' -Option ReadOnly -Scope Script -Force # CONFIG New-Variable -Name 'OMNISSA_ENV_CONFIG_NOT_FOUND' -Value 'Configuration for Omnissa environment not found' -Option ReadOnly -Scope Script -Force New-Variable -Name 'MISSING_FIELDS' -Value 'Missing required fields' -Option ReadOnly -Scope Script -Force # ENRICHMENT API New-Variable -Name 'ENRICHMENT_API_HOST' -Value 'api.eu-west-3.dev.nexthink.cloud' -Option ReadOnly -Scope Script -Force New-Variable -Name 'TARGET_CREDENTIALS_NAME' -Value 'nxt-ctx-credentials' -Option ReadOnly -Scope Script -Force New-Variable -Name 'JWT_URL' -Value 'https://{0}/api/v1/token' -Option ReadOnly -Scope Script -Force New-Variable -Name 'ENRICHMENT_URL' -Value 'https://{0}/api/v1/enrichment/data/fields' -Option ReadOnly -Scope Script -Force New-Variable -Name 'REQUEST_BATCH_SIZE' -Value 1000 -Option ReadOnly -Scope Script -Force # EXIT CODES New-Variable -Name 'EXIT_CODE_OK' -Value 0 -Option ReadOnly -Scope Script -Force New-Variable -Name 'EXIT_CODE_ERROR' -Value 1 -Option ReadOnly -Scope Script -Force # TRACE ID New-Variable -Name 'TRACE_ID_HEADER' -Value 'x-enrichment-trace-id' -Option ReadOnly -Scope Script -Force New-Variable -Name 'TRACE_ID_VALUE' -Value ([Guid]::NewGuid()) -Option ReadOnly -Scope Script -Force # TIMESTAMP New-Variable -Name 'TIMESTAMP' -Value '1970-01-01T00:00:00Z' -Option ReadOnly -Scope Script -Force New-Variable -Name 'EXECUTION_START_DATE' -Value (Get-Date) -Option ReadOnly -Scope Script -Force # VIRTUALIZATION CONSTANTS # horizon_on_prem = 5 New-Variable -Name 'DESKTOP_BROKER' -Value 5 -Option ReadOnly -Scope Script -Force # CACHE New-Variable -Name 'BASE_IMAGES_CACHE' -Value (@{}) -Option ReadOnly -Scope Script -Force New-Variable -Name 'BASE_SNAPSHOTS_CACHE' -Value (@{}) -Option ReadOnly -Scope Script -Force # # Main function # function Invoke-Main { param( [Parameter(Mandatory = $true)] [string]$OmnissaEnvironment, [Parameter(Mandatory = $true)] [string]$ScriptRootPath ) $exitCode = $EXIT_CODE_OK try { Initialize-EnrichmentProcess -OmnissaEnvironment $OmnissaEnvironment -ScriptRootPath $ScriptRootPath Write-CustomLog -Message "Starting Omnissa connector $SCRIPT_VERSION for environment $OmnissaEnvironment" -Severity 'INFO' Start-EnrichmentProcess -OmnissaEnvironment $OmnissaEnvironment } catch { Write-CustomLog -Message "The execution stopped unexpectedly. Details: $($_.Exception.Message)" -Severity 'ERROR' $exitCode = $EXIT_CODE_ERROR } Write-CustomLog -Message "Stopping Omnissa connector $SCRIPT_VERSION with exit code $exitCode`n" -Severity 'INFO' Wait-Logging return $exitCode } function Initialize-EnrichmentProcess { param( [Parameter(Mandatory = $true)] [string]$OmnissaEnvironment, [Parameter(Mandatory = $true)] [string]$ScriptRootPath ) # Initialize path-dependent variables $logsFolder = Join-Path -Path (Join-Path -Path $ScriptRootPath -ChildPath "Logs") -ChildPath $OmnissaEnvironment $configFileFolder = Join-Path -Path (Join-Path -Path $ScriptRootPath -ChildPath "Config") -ChildPath "config.json" New-Variable -Name 'SCRIPT_FOLDER' -Value $ScriptRootPath -Option ReadOnly -Scope Script -Force New-Variable -Name 'LOGS_FOLDER' -Value $logsFolder -Option ReadOnly -Scope Script -Force New-Variable -Name 'LOGFILE_NAME' -Value (Join-Path -Path $logsFolder -ChildPath "OmnissaConnector-%{+yyyyMMdd}.log") -Option ReadOnly -Scope Script -Force New-Variable -Name 'ZIPFILE_NAME' -Value (Join-Path -Path $logsFolder -ChildPath "RotatedLogs.zip") -Option ReadOnly -Scope Script -Force New-Variable -Name 'CONFIG_FILE_NAME' -Value $configFileFolder -Option ReadOnly -Scope Script -Force Initialize-Folder -Path $logsFolder Initialize-Logger Get-ConfigData -OmnissaEnvironment $OmnissaEnvironment -ConfigFilePath $configFileFolder # Needed to update log level after reading it from the configuration Initialize-Logger New-Variable -Name 'TIMESTAMP' -Value $(Get-Date -Format o) -Option ReadOnly -Scope Script -Force } function Start-EnrichmentProcess { $pools = @(Get-DesktopPools) $machines = @(Get-Machines -Pools $pools) $rdsServers = @(Get-RdsServers -Pools $pools) $devices = $machines + $rdsServers $totalDevicesToSend = $devices.Count Write-CustomLog -Message "$totalDevicesToSend device(s) retrieved in total from Omnissa API." -Severity 'INFO' for ($devicesOffset = 0; $devicesOffset -lt $totalDevicesToSend; $devicesOffset += $REQUEST_BATCH_SIZE) { $deviceLimit = [Math]::Min($totalDevicesToSend - 1, $devicesOffset + $REQUEST_BATCH_SIZE - 1) $fullData = $devices[$devicesOffset..$deviceLimit] Send-EnrichmentRequest -FullData $fullData -OmnissaEnvironment $OmnissaEnvironment } } function Send-EnrichmentRequest ([Object[]]$FullData, [String]$OmnissaEnvironment) { # Time in milliseconds since the script started up to when the enrichment request is sent $executionDuration = [Int](((Get-Date) - $EXECUTION_START_DATE).TotalMilliseconds) $clientTelemetry = Get-ClientTelemetry -ExecutionDuration $executionDuration $jwt = Get-Jwt $enrichmentEndpoint = [String]::Format($ENRICHMENT_URL, $ENRICHMENT_API_HOST) $enrichmentBody = Get-JsonFromOmnissaData -OmnissaData $FullData -ClientTelemetry $clientTelemetry -OmnissaEnvironment $OmnissaEnvironment $response = Invoke-SendDataToEnrichmentAPI -EndpointURL $enrichmentEndpoint -JsonPayload $enrichmentBody -Jwt $jwt switch ( $response.statusCode ) { '200' { Write-CustomLog -Message "[$TRACE_ID_VALUE] Batch with $($FullData.Count) devices successfully processed by Enrichment API." -Severity 'INFO' } '207' { Write-CustomLog -Message "[$TRACE_ID_VALUE] Batch with $($FullData.Count) devices partially processed by Enrichment API." -Severity 'INFO' Write-CustomLog -Message "[$TRACE_ID_VALUE] Partial success response: $($response.content)" -Severity 'INFO' } default { $message = "[$TRACE_ID_VALUE] Error sending request to Enrichment API with status code: $($response.statusCode)" Write-CustomLog -Message $message -Severity 'ERROR' throw $message } } } function Get-Jwt () { $jwtUrl = [String]::Format($JWT_URL, $ENRICHMENT_API_HOST) if (-not (Test-ValidWebUrl($jwtUrl))) { throw "Invalid URL to retrieve the token: $jwtUrl" } try { $credentials = Get-ClientCredentials -Target $TARGET_CREDENTIALS_NAME $basicHeader = Get-StringAsBase64 -InputString "$($credentials.clientId):$($credentials.clientSecret)" $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $headers.Add('Authorization', "Basic $basicHeader") $response = Invoke-WebRequest -Uri $jwtUrl -Method 'POST' -Headers $headers -UseBasicParsing $parsedResponse = ConvertFrom-Json $([String]::new($response.Content)) return $parsedResponse.access_token } catch [Net.WebException], [IO.IOException] { Write-CustomLog "Error sending request to get the JWT token. Details: [$($_.Exception.response.StatusCode.value__)] $($_.ErrorDetails)" -Severity "ERROR" throw "Unable to access token endpoint. Details: $($_.Exception.Message)" } catch { throw "An error occurred that could not be resolved. Details: $($_.Exception.Message)" } } function Test-ValidWebUrl($UrlToValidate) { $uri = $UrlToValidate -as [System.Uri] $null -ne $uri.AbsoluteURI -and $uri.Scheme -match '[http|https]' } function Get-ClientCredentials ([String]$Target) { $storedCredentials = Get-StoredCredential -Target $Target if ($storedCredentials -and $null -ne $storedCredentials.UserName -and $null -ne $storedCredentials.Password ) { $userName = $storedCredentials.UserName $securePassword = $storedCredentials.Password $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword) $unsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) return @{ clientId = $userName; clientSecret = $unsecurePassword } } else { throw "Credentials not found or they are empty for Target: $Target" } } function Get-StringAsBase64 ([String]$InputString) { $Bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString) $EncodedText = [Convert]::ToBase64String($Bytes) return $EncodedText } # # Omnissa Horizon functions # function Get-DesktopPools { Write-CustomLog -Message "Fetching desktop pools..." -Severity 'DEBUG' $headers = Get-RequestHeaders try { $desktopPoolsResponse = Invoke-RestMethod -Uri "$OMNISSA_HOST/rest/inventory/v7/desktop-pools" -Headers $headers -Method Get $desktopPools = $desktopPoolsResponse | Where-Object { $null -ne $_.id } $desktopPoolsCount = $desktopPools.Count Write-CustomLog -Message "$desktopPoolsCount desktop pool IDs found" -Severity 'DEBUG' return $desktopPools } catch { throw "Failed to get desktop pools. Details: $($_.Exception.Message)" } } function Get-Machines { param ( [Object[]]$Pools ) try { $machinesOutput = @() $headers = Get-RequestHeaders $machinesResponse = Invoke-RestMethod -Uri "$OMNISSA_HOST/rest/inventory/v5/machines" -Headers $headers -Method Get $machineCount = $machinesResponse.Count Write-CustomLog -Message "$machineCount machines found" -Severity 'DEBUG' if ($machineCount -eq 0) { # Early return if no machines were found return $machinesOutput } $poolLookup = Get-ObjectLookup -KeySelector { param($p) $p.id } -Objects $Pools foreach ($machine in $machinesResponse) { $pool = $poolLookup[$machine.desktop_pool_id] $vCenterId = $machine.managed_machine_data.virtual_center_id $baseVmId = $machine.managed_machine_data.base_vm_id $snapshotId = $machine.managed_machine_data.base_vm_snapshot_id $baseImage = Get-BaseImage -VCenterId $vCenterId -BaseVmId $baseVmId -SnapshotId $snapshotId $machinesOutput += [PSCustomObject]@{ MachineName = $machine.name DesktopPool = $pool.name PoolType = $pool.type Hostname = $machine.managed_machine_data.host_name UserAssignment = $pool.user_assignment DiskImage = $baseImage } } return $machinesOutput } catch { throw "Failed to get machines. Details: $($_.Exception.Message)" } } function Get-RdsServers { param ( [Object[]]$Pools ) try { $rdsServersOutput = @() $headers = Get-RequestHeaders $rdsServersResponse = Invoke-RestMethod -Uri "$OMNISSA_HOST/rest/inventory/v2/rds-servers" -Headers $headers -Method Get $rdsServersCount = $rdsServersResponse.Count Write-CustomLog -Message "$rdsServersCount RDS servers found" -Severity 'DEBUG' if ($rdsServersCount -eq 0) { # Early return if there are no RDS servers return $rdsServersOutput } $farmsResponse = @(Get-Farms) $farmLookup = Get-ObjectLookup -Objects $farmsResponse -KeySelector { param($p) $p.id } $poolLookup = Get-ObjectLookup -KeySelector { param($p) $p.farm_id } -Objects $Pools foreach ($rdsServer in $rdsServersResponse) { $pool = $poolLookup[$rdsServer.farm_id] $farm = $farmLookup[$rdsServer.farm_id] $vCenterId = $farm.automated_farm_settings.vcenter_id $baseVmId = $farm.automated_farm_settings.provisioning_settings.parent_vm_id $snapshotId = $farm.automated_farm_settings.provisioning_settings.base_snapshot_id $baseImage = Get-BaseImage -VCenterId $vCenterId -BaseVmId $baseVmId -SnapshotId $snapshotId $rdsServersOutput += [PSCustomObject]@{ MachineName = $rdsServer.name DesktopPool = $pool.name PoolType = $pool.type Hostname = $rdsServer.dns_name UserAssignment = $pool.user_assignment DiskImage = $baseImage } } return $rdsServersOutput } catch { throw "Failed to get RDS servers. Details: $($_.Exception.Message)" } } function Get-BaseImage { param ( [string]$VCenterId, [string]$BaseVmId, [string]$SnapshotId ) if ([string]::IsNullOrEmpty($VCenterId) -or [string]::IsNullOrEmpty($BaseVmId) -or [string]::IsNullOrEmpty($SnapshotId)) { Write-CustomLog -Message "Required parameters missing. Skipping base image retrieval." -Severity 'DEBUG' return $null } $baseImagesCacheKey = "$VCenterId" $baseVMLookup = Get-FromCacheOrInvoke -Cache $BASE_IMAGES_CACHE -Key $baseImagesCacheKey -Invoke { Get-BaseVmLookup -VCenterId $VCenterId } $baseVmPath = $baseVMLookup[$BaseVmId].path $baseSnapshotsCacheKey = "$VCenterId" + "_" + "$BaseVmId" $snapshotResponse = Get-FromCacheOrInvoke -Cache $BASE_SNAPSHOTS_CACHE -Key $baseSnapshotsCacheKey -Invoke { Get-BaseSnapshotLookup -VCenterId $VCenterId -BaseVmId $BaseVmId } $snaphotPath = $snapshotResponse[$SnapshotId].path return $baseVmPath + $snaphotPath } function Get-BaseVmLookup { param ( [Parameter(Mandatory = $true)][string]$VCenterId ) Write-CustomLog -Message "Fetching base VMs for VCenterId='$VCenterId'..." -Severity 'DEBUG' try { $headers = Get-RequestHeaders $baseVMsResponse = Invoke-RestMethod -Uri "$OMNISSA_HOST/rest/external/v2/base-vms?vcenter_id=$VCenterId" -Headers $headers -Method Get $baseVMsCount = $baseVMsResponse.Count Write-CustomLog -Message "$baseVMsCount base VMs found" -Severity 'DEBUG' return Get-ObjectLookup -Objects $baseVMsResponse -KeySelector { param($p) $p.id } } catch { throw "Failed to get base VMs. Details: $($_.toString())" } } function Get-BaseSnapshotLookup { param ( [Parameter(Mandatory = $true)][string]$VCenterId, [Parameter(Mandatory = $true)][string]$BaseVmId ) Write-CustomLog -Message "Fetching base snapshots for VCenterId='$VCenterId' and BaseVmId='$BaseVmId'..." -Severity 'DEBUG' try { $headers = Get-RequestHeaders $baseSnapshotsResponse = Invoke-RestMethod -Uri "$OMNISSA_HOST/rest/external/v2/base-snapshots?vcenter_id=$VCenterId&base_vm_id=$BaseVmId" -Headers $headers -Method Get $baseSnapshotsCount = $baseSnapshotsResponse.Count Write-CustomLog -Message "$baseSnapshotsCount base snapshots found" -Severity 'DEBUG' return Get-ObjectLookup -Objects $baseSnapshotsResponse -KeySelector { param($p) $p.id } } catch { throw "Failed to get base snapshots. Details: $($_.toString())" } } function Get-Farms { Write-CustomLog -Message "Fetching farms..." -Severity 'DEBUG' try { $headers = Get-RequestHeaders $farms = Invoke-RestMethod -Uri "$OMNISSA_HOST/rest/inventory/v6/farms" -Headers $headers -Method Get $farmsCount = $farms.Count Write-CustomLog -Message "$farmsCount farms found" -Severity 'DEBUG' return $farms } catch { throw "Failed to get farms. Details: $($_.toString())" } } function Get-RequestHeaders { $credentials = Get-ClientCredentials -Target $OMNISSA_CREDENTIALS_NAME $authBody = @{ domain = "NEXTHINK-OMNISSA-CONNECTOR" username = $credentials.clientId password = $credentials.clientSecret } | ConvertTo-Json try { $response = Invoke-WebRequest -Uri "$OMNISSA_HOST/rest/login" -Method POST -Body $authBody -ContentType "application/json" -UseBasicParsing $token = ($response.Content | ConvertFrom-Json).access_token if (-not $token) { throw "Authentication failed: No token returned." } } catch { throw "Authentication failed. Details: $($_.ToString())" } return @{ "Authorization" = "Bearer $token" "Accept" = "application/json" "Content-Type" = "application/json" } } # Function to determine virtualization type based on input string function Get-VirtualizationType { param( [string]$UserAssignment, [string]$PoolType ) # Default to UNSPECIFIED (0) if no match is found $result = 0 # Determine virtualization type based on UserAssignment and PoolType if ($UserAssignment -eq "DEDICATED") { # PERSONAL = 1 $result = 1 } elseif ($UserAssignment -eq "FLOATING") { # POOLED = 2 $result = 2 } elseif ($PoolType -eq "RDS") { # SHARED = 3 $result = 3 } return $result } # # Logging # function Write-CustomLog ([String]$Message, [String]$Severity = 'INFO') { Write-Log -Message $Message -Level $Severity } function Initialize-Folder ([String]$Path) { try { if (-not (Test-Path -Path $Path)) { [Void](New-Item -Path $Path -ItemType 'Directory' -Force -ErrorAction Stop) } } catch { throw "Error creating folder at $Path." } } function Initialize-Logger { Add-LoggingTarget -Name File -Configuration @{ Path = $LOGFILE_NAME Encoding = 'unicode' Level = $LOG_LEVEL Format = $LOG_FORMAT RotateAfterAmount = $LOG_RETENTION_DAYS RotateAmount = 1 CompressionPath = $ZIPFILE_NAME } Set-LoggingCallerScope 2 } # # Config functions # function Get-ConfigData { param( [Parameter(Mandatory = $true)] [string]$OmnissaEnvironment, [Parameter(Mandatory = $true)] [string]$ConfigFilePath ) $configData = Read-ConfigFile -ConfigFilePath $ConfigFilePath $omnissaEnvironmentConfigData = Get-OmnissaEnvironmentConfigData -ConfigData $configData -OmnissaEnvName $OmnissaEnvironment if ($null -eq $omnissaEnvironmentConfigData) { throw "$OMNISSA_ENV_CONFIG_NOT_FOUND" } if (-not (Test-ConfigData -ConfigData $configData -OmnissaEnvConfigData $omnissaEnvironmentConfigData)) { throw "$MISSING_FIELDS" } New-Variable -Name 'LOG_RETENTION_DAYS' -Value $configData.Logging.LogRetentionDays -Option ReadOnly -Scope Script -Force New-Variable -Name 'LOG_LEVEL' -Value $configData.Logging.LogLevel -Option ReadOnly -Scope Script -Force New-Variable -Name 'OMNISSA_HOST' -Value $omnissaEnvironmentConfigData.Host -Option ReadOnly -Scope Script -Force New-Variable -Name 'OMNISSA_CREDENTIALS_NAME' -Value $omnissaEnvironmentConfigData.WindowsCredentialEntry -Option ReadOnly -Scope Script -Force New-Variable -Name 'ENRICHMENT_API_HOST' -Value $configData.NexthinkAPI.HostFQDN -Option ReadOnly -Scope Script -Force New-Variable -Name 'TARGET_CREDENTIALS_NAME' -Value $configData.NexthinkAPI.WindowsCredentialEntry -Option ReadOnly -Scope Script -Force New-Variable -Name 'REQUEST_BATCH_SIZE' -Value $configData.NexthinkAPI.RequestBatchSize -Option ReadOnly -Scope Script -Force } function Read-ConfigFile { param( [Parameter(Mandatory = $true)] [string]$ConfigFilePath ) try { return (Get-Content "$ConfigFilePath" -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop) } catch { throw "Error loading config file. Details: $($_.toString())" } } function Get-OmnissaEnvironmentConfigData($ConfigData, $OmnissaEnvName) { return $ConfigData.OmnissaEnvironments | Where-Object { $_.Name -eq $OmnissaEnvName } } function Test-ConfigData($ConfigData, $OmnissaEnvConfigData) { return ($ConfigData.Logging.LogRetentionDays -and $ConfigData.Logging.LogLevel -and $ConfigData.NexthinkAPI.HostFQDN -and ` $ConfigData.NexthinkAPI.WindowsCredentialEntry -and $ConfigData.NexthinkAPI.RequestBatchSize -and ` $OmnissaEnvConfigData.Name -and $OmnissaEnvConfigData.Host -and ` $OmnissaEnvConfigData.WindowsCredentialEntry) } # # Utilities # function Get-ObjectLookup { param( [Object[]]$Objects, [Parameter(Mandatory = $true)] [ScriptBlock]$KeySelector ) $lookup = @{} foreach ($obj in $Objects) { $key = & $KeySelector $obj if ([string]::IsNullOrEmpty($key)) { continue } if (-not $lookup.ContainsKey($key)) { $lookup.Add($key, $obj) } } return $lookup } function Get-FromCacheOrInvoke { param ( [hashtable]$Cache, [string]$Key, [ScriptBlock]$Invoke ) if ($Cache.ContainsKey($Key)) { return $Cache[$Key] } $result = & $Invoke $Cache[$Key] = $result return $result } # # Enrichment API # function Get-JsonFromOmnissaData ([Object[]]$OmnissaData, [PSCustomObject]$ClientTelemetry, [String]$OmnissaEnvironment) { $jsonResult = '{"enrichments": [' foreach ($device in $OmnissaData) { try { if (Test-MachineIsValid($device)) { $virtualizationType = Get-VirtualizationType -UserAssignment $device.UserAssignment -PoolType $device.PoolType $currentRow = '{"identification":[{"name":"device/device/name","value":"' + $device.MachineName + '"}],' $currentRow = $currentRow + '"fields":[{"name":"device/device/virtualization/desktop_pool","value":"' + $device.DesktopPool + '"}' $currentRow = $currentRow + ',{"name":"device/device/virtualization/type","value": ' + $virtualizationType + '}' $currentRow = $currentRow + ',{"name":"device/device/virtualization/hostname","value":"' + $device.Hostname + '"}' $currentRow = $currentRow + ',{"name":"device/device/virtualization/environment_name","value":"' + $OmnissaEnvironment + '"}' $currentRow = $currentRow + ',{"name":"device/device/virtualization/desktop_broker","value": ' + $DESKTOP_BROKER + '}' if ($null -ne $device.DiskImage) { $currentRow = $currentRow + ',{"name":"device/device/virtualization/disk_image","value":"' + $device.DiskImage + '"}' } $currentRow = $currentRow + ',{"name":"device/device/virtualization/last_update","value":"' + $TIMESTAMP + '"}' $currentRow = $currentRow + ']},' $jsonResult = $jsonResult + $currentRow } else { Write-CustomLog -Message "Invalid device: '$($device | ConvertTo-Json -Compress)'" -Severity 'DEBUG' } } catch { Write-CustomLog -Message "Error processing device '$($device.MachineName)'. Details: $($_.Exception.Message)" -Severity 'ERROR' } } if ($jsonResult.EndsWith(',')) { $jsonResult = $jsonResult.Substring(0, $jsonResult.Length - 1) } $clientTelemetryJson = $clientTelemetry | ConvertTo-Json -Compress return $jsonResult + '], "domain":"omnissa", "clientTelemetry":' + $clientTelemetryJson + '}' } function Get-ClientTelemetry([Int]$ExecutionDuration) { return [PSCustomObject]@{ version = $SCRIPT_VERSION executionDurationInMs = $ExecutionDuration fullScan = "true" } } function Test-MachineIsValid([PSCustomObject]$Device) { if ($null -ne $Device.MachineName -and $null -ne $Device.DesktopPool -and $null -ne $Device.Hostname ) { return $true } return $false } function Invoke-SendDataToEnrichmentAPI ([String]$EndpointUrl, [String]$JsonPayload, [String]$Jwt) { try { $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $headers.Add('Content-Type', 'application/json') $headers.Add('Authorization', "Bearer $Jwt") $headers.Add($TRACE_ID_HEADER, $TRACE_ID_VALUE) $response = Invoke-WebRequest -Uri $EndpointUrl -Method 'POST' -Headers $headers -Body $JsonPayload -UseBasicParsing $statusCode = $response.StatusCode $content = $response.Content } catch { Write-CustomLog -Message "[$TRACE_ID_VALUE] Error sending request to Enrichment API. Details: $($_.Exception.Message)" -Severity 'ERROR' Write-CustomLog -Message "[$TRACE_ID_VALUE] Error message: $_" -Severity 'ERROR' $statusCode = $_.Exception.response.StatusCode.value__ } return @{ statusCode = $statusCode; content = $content } } # # Module exports - Only export when running as a module # try { Export-ModuleMember -Function * } catch [System.InvalidOperationException] { # Expected when dot-sourced as script during testing } catch { # Silently ignore any other export errors } # SIG # Begin signature block # MIIpiQYJKoZIhvcNAQcCoIIpejCCKXYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDKIrQ5yrOgB/ar # C+udqO0+OQhvQzgeJXYjAnH++6A1MaCCDnswggawMIIEmKADAgECAhAIrUCyYNKc # TJ9ezam9k67ZMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV # BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0z # NjA0MjgyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg # SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcg # UlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQDVtC9C0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0 # JAfhS0/TeEP0F9ce2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJr # Q5qZ8sU7H/Lvy0daE6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhF # LqGfLOEYwhrMxe6TSXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+F # LEikVoQ11vkunKoAFdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh # 3K3kGKDYwSNHR7OhD26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJ # wZPt4bRc4G/rJvmM1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQay # g9Rc9hUZTO1i4F4z8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbI # YViY9XwCFjyDKK05huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchAp # QfDVxW0mdmgRQRNYmtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRro # OBl8ZhzNeDhFMJlP/2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IB # WTCCAVUwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+ # YXsIiGX0TkIwHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0P # AQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAk # BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAC # hjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v # dEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAED # MAgGBmeBDAEEATANBgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql # +Eg08yy25nRm95RysQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFF # UP2cvbaF4HZ+N3HLIvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1h # mYFW9snjdufE5BtfQ/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3Ryw # YFzzDaju4ImhvTnhOE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5Ubdld # AhQfQDN8A+KVssIhdXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw # 8MzK7/0pNVwfiThV9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnP # LqR0kq3bPKSchh/jwVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatE # QOON8BUozu3xGFYHKi8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bn # KD+sEq6lLyJsQfmCXBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQji # WQ1tygVQK+pKHJ6l/aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbq # yK+p/pQd52MbOoZWeE4wggfDMIIFq6ADAgECAhAHJlJYMMxBAtpVem3mdhUEMA0G # CSqGSIb3DQEBCwUAMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg # SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcg # UlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwHhcNMjUwMzA2MDAwMDAwWhcNMjgwMzA3 # MjM1OTU5WjCByzETMBEGCysGAQQBgjc8AgEDEwJDSDEVMBMGCysGAQQBgjc8AgEC # EwRWYXVkMR0wGwYDVQQPDBRQcml2YXRlIE9yZ2FuaXphdGlvbjEYMBYGA1UEBRMP # Q0hFLTExMi4wMDAuNTc5MQswCQYDVQQGEwJDSDEPMA0GA1UEBxMGUHJpbGx5MRYw # FAYDVQQKEw1ORVhUaGluayBTLkEuMRYwFAYDVQQLEw1ORVhUaGluayBTLkEuMRYw # FAYDVQQDEw1ORVhUaGluayBTLkEuMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAyQBSj4N+dRT4Ft/77JxH1TCVXklR2+qgPiRzd1DHcjEu5mlpGbfFfGht # NiZ1yDKC05ieExBSO6qIfkw6Lcod5yDTd1ojwU13b2SroZUUG6tLcwHLVnnSDiBH # wi03dG7VmS9pqmgCLBRz7N3eF00AGY/cFJc8tT96HwXWSlVMAJ3DLkS6Db+1GLTi # /ne9f9uWyksWSR3fW/sskS4/X6jIR26jJjPSfA4PhrnjRTSUJiMostKTRlHIIZtH # T5m0Nf4adyU/4QCHHCSwx2cq+1z3H7C98yV7sZVb5hr0VUyVUYA+YP9xYQsKy1BQ # XXLBd8WxT1QT+/UlUl5reK/oo6mjfcOt5Sz3ZGj+24SYGZ2zgrTWEboe3rZHKoKB # 7fHqtKv4wrKXVkZt7J+kLuXg3fOusZ65V9NMyZx1gvL+t0RXd49dHGuBhg3ddVw7 # T4NZJ5M9XaWfwWUA04+aBouM4hqZLNKA9xpqv2JLFdg/UBof6JPETZ2cSP2tNKM1 # Ai+F00mm1Md196RC/+hltVGnmHqrw3rTScjiFT+HwHy0QWY5pdu275J9zCZcYZXz # qUEkFmJN5aU9Nz6TZlgK5sNy1TJensLcnFw4hMK/y0GLkjGoXht4VQUJFGP51Lyh # GSYo6EZJscVPx+kBg4c7I5P/4nEnFKguV3IjISiJbdDqNgbLHOsCAwEAAaOCAgIw # ggH+MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBT0 # DqAKYCVBQLXZvFpqeHPu5oUVtjA9BgNVHSAENjA0MDIGBWeBDAEDMCkwJwYIKwYB # BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMC # B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25p # bmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI # QTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT # QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA # A4ICAQArEaG0O/idYThPmYwLNwjvWSTevsonyUhvSGjSnkshdGZ1ClZdCmeMisXc # jHystVNVtLZgy7q4d5a0m5lNKguI2p2/yPvWb9mGw9pateQ6v9AZGYrkL93FzvQK # b/cVp/pcfAmAtjAbhP4FaFQlEfPHyLJSswJMkZHa3lt+scmSjG0DwLbw3J4MK8MJ # sQubqt/Po0hMEm5IXfIAhTUMVrgtD2iVQOIv4YchJQePkOhfoa6T3QAbQGisioT1 # GXpaw+cMQuakS03Dj59KVzwZKgKWf2WttIcByN+iSI8oIBbzj7uFDbaH1BpocbYb # sEG1+4zXkX92e45aetxuKGMe3DdH5dP+f2Mf820NHc7awetz2h4ejcX2Iw4VXLiu # Gd2qz2+My8ayqXbpEdXDkRcnOOUPk00Z4SekIvmmdu/R75Ri8ApQbYcLq7OJWn4F # 3KHTlztKTdcaBeYK3AsYNcap50uKnceS+eW7CXmEGFj93z6PUAiMkKabj/LHmd47 # hi9qdvumQ1lPzjilxHUrfbI5ijSq58mbgyG/LU5+wGh00mxSyLQ6dEYE7OdwEW3x # 4KUjXWj4/9lmdCqAv25+qOR6HikM3dDKBgJQN+tk9soHD+MEyoyldDHBRjf8Gt6g # MDnJ0Bvk1tfxFBx/KE7xwib8KS594kK7Q89nFgH3Uo1reXKkejGCGmQwghpgAgEB # MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD # VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI # QTM4NCAyMDIxIENBMQIQByZSWDDMQQLaVXpt5nYVBDANBglghkgBZQMEAgEFAKB8 # MBAGCisGAQQBgjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG # CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAZfoMC # N7RS+YvP9sWR1j1VP4L/ZZtghNMRRgWMQJzgNzANBgkqhkiG9w0BAQEFAASCAgAL # PXGln9WC5VOgbYwGA//2xHtuurP7ZcZi5mrmi/YDJMwsIml/1JU5BV39aZ4iAyNd # 2BBelZEUu5Rn6ckUf7cWjHEDh0fbyCZbqBJVzw3ISO+4EopObydY7pIGXzVuPLGe # PfcxLD9u3/+ZkigAZBVW8pMZXzByv45QJk/lw5mru4P5dhBhoxVv5SEokpv+AIcL # GckU2eT3FINTsm2YH9Zc7FNiNy+wb/mQ6M+BpQhwgpdIW0ka0fmT3QzBpOJ5fM9S # tnu4fPeuT8cWjS1OvC/NwylVrJnp2YulJEpICyrzrHxChIOBqXyb3HriMETKmalX # CdyPIOvnYR+XTqpLLFnouoc2/62oh5FMgWMQKGxBiCFmyZSDNQq91SqLzO8nTXNV # y6TjvswhQSZhDR9rOSYlRraWKHikpz3s032sQLSi2Waifcc514Is2xc/FhV23LmT # pu0bBnRTbrhgIwXxIeU2khhXt2GCw9fvL3rfuWK/oGfsKBDahygbTOjJM+MqR1FZ # Bsrl4Y65wMRU7LyS/Mpyt8qesW8CYe3EU7dRKzvhf4HiXSKOVnkLWeWRtNfre7Y0 # ULJaRMiCxzco3OaTVcf99LdiI2c7Z0IkFXbir+7YtHfT0f7u8RE3mojfm0PXvgpX # VAz9AecuwaFmzsPl4LvIjR6NhJ5Qk8L5D2fBNQT1o6GCFzowghc2BgorBgEEAYI3 # AwMBMYIXJjCCFyIGCSqGSIb3DQEHAqCCFxMwghcPAgEDMQ8wDQYJYIZIAWUDBAIB # BQAweAYLKoZIhvcNAQkQAQSgaQRnMGUCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFl # AwQCAQUABCDJV8LZ8scJ9PwE8/YzwvHX5OoxKc9WH8YWSaomcLnOMwIRAPNc+gPT # bkjGgjoKqj1sJbMYDzIwMjUwNzA5MDgwNTU5WqCCEwMwgga8MIIEpKADAgECAhAL # rma8Wrp/lYfG+ekE4zMEMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcw # FQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3Rl # ZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjQwOTI2MDAw # MDAwWhcNMzUxMTI1MjM1OTU5WjBCMQswCQYDVQQGEwJVUzERMA8GA1UEChMIRGln # aUNlcnQxIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDI0MIICIjANBgkq # hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvmpzn/aVIauWMLpbbeZZo7Xo/ZEfGMSI # O2qZ46XB/QowIEMSvgjEdEZ3v4vrrTHleW1JWGErrjOL0J4L0HqVR1czSzvUQ5xF # 7z4IQmn7dHY7yijvoQ7ujm0u6yXF2v1CrzZopykD07/9fpAT4BxpT9vJoJqAsP8Y # uhRvflJ9YeHjes4fduksTHulntq9WelRWY++TFPxzZrbILRYynyEy7rS1lHQKFpX # vo2GePfsMRhNf1F41nyEg5h7iOXv+vjX0K8RhUisfqw3TTLHj1uhS66YX2LZPxS4 # oaf33rp9HlfqSBePejlYeEdU740GKQM7SaVSH3TbBL8R6HwX9QVpGnXPlKdE4fBI # n5BBFnV+KwPxRNUNK6lYk2y1WSKour4hJN0SMkoaNV8hyyADiX1xuTxKaXN12HgR # +8WulU2d6zhzXomJ2PleI9V2yfmfXSPGYanGgxzqI+ShoOGLomMd3mJt92nm7Mhe # ng/TBeSA2z4I78JpwGpTRHiT7yHqBiV2ngUIyCtd0pZ8zg3S7bk4QC4RrcnKJ3Fb # jyPAGogmoiZ33c1HG93Vp6lJ415ERcC7bFQMRbxqrMVANiav1k425zYyFMyLNyE1 # QulQSgDpW9rtvVcIH7WvG9sqYup9j8z9J1XqbBZPJ5XLln8mS8wWmdDLnBHXgYly # /p1DhoQo5fkCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8E # AjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQC # MAsGCWCGSAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAd # BgNVHQ4EFgQUn1csA3cOKBWQZqVjXu5Pkh92oFswWgYDVR0fBFMwUTBPoE2gS4ZJ # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5 # NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYI # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZM # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNB # NDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEA # Pa0eH3aZW+M4hBJH2UOR9hHbm04IHdEoT8/T3HuBSyZeq3jSi5GXeWP7xCKhVire # KCnCs+8GZl2uVYFvQe+pPTScVJeCZSsMo1JCoZN2mMew/L4tpqVNbSpWO9QGFwfM # Ey60HofN6V51sMLMXNTLfhVqs+e8haupWiArSozyAmGH/6oMQAh078qRh6wvJNU6 # gnh5OruCP1QUAvVSu4kqVOcJVozZR5RRb/zPd++PGE3qF1P3xWvYViUJLsxtvge/ # mzA75oBfFZSbdakHJe2BVDGIGVNVjOp8sNt70+kEoMF+T6tptMUNlehSR7vM+C13 # v9+9ZOUKzfRUAYSyyEmYtsnpltD/GWX8eM70ls1V6QG/ZOB6b6Yum1HvIiulqJ1E # lesj5TMHq8CWT/xrW7twipXTJ5/i5pkU5E16RSBAdOp12aw8IQhhA/vEbFkEiF2a # bhuFixUDobZaA0VhqAsMHOmaT3XThZDNi5U2zHKhUs5uHHdG6BoQau75KiNbh0c+ # hatSF+02kULkftARjsyEpHKsF7u5zKRbt5oK5YGwFvgc4pEVUNytmB3BpIiowOII # uDgP5M9WArHYSAR16gc0dP2XdkMEP5eBsX7bf/MGN4K3HP50v/01ZHo/Z5lGLvNw # Q7XHBx1yomzLP8lx4Q1zZKDyHcp4VQJLu2kWTsKsOqQwggauMIIElqADAgECAhAH # Nje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUw # EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x # ITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAw # MDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp # Q2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2 # IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGh # RBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISK # Ihjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdG # AHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9 # zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKl # SNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae # 5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnz # yqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/ # BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7T # A4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbs # q11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IB # XTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3Mpdpov # dYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0P # AQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAk # BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAC # hjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v # dEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEE # AjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m # 1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dt # h/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+K # LHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd # 6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ # 38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+ # k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3l # NHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGY # X/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFm # ut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADN # XcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kv # RBVK5xMOHds3OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAY # WjANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl # cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdp # Q2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5 # MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw # FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVz # dGVkIFJvb3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBz # aN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbr # VsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTR # EEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJ # z82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyO # j4DatpGYQJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6R # AXwhTNS8rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k # 98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJ # tppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUa # dmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZB # dd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVf # nSD8oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0T # AQH/BAUwAwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0j # BBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsG # AQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t # MEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNl # cnRBc3N1cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9j # cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYD # VR0gBAowCDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3Qb # PbYW1/e/Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5 # +KH38nLeJLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+n # BgMTdydE1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc # /RzY9HdaXFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVr # zyerbHbObyMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o # 4rmUMYIDdjCCA3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl # cnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBT # SEEyNTYgVGltZVN0YW1waW5nIENBAhALrma8Wrp/lYfG+ekE4zMEMA0GCWCGSAFl # AwQCAQUAoIHRMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0B # CQUxDxcNMjUwNzA5MDgwNTU5WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBTb04Xu # YtvSPnvk9nFIUIck1YZbRTAvBgkqhkiG9w0BCQQxIgQgmSsNZdWXGxPVbGpnD6EA # f2N3szuUVhUVbAo279pWO2YwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgdnafqPJj # Lx9DCzojMK7WVnX+13PbBdZluQWTmEOPmtswDQYJKoZIhvcNAQEBBQAEggIAq3z2 # iqGgtnUTx2cYbFYT0dBpj7JJ744yHiq584lZHSaGiHDPhD4iXMamVwxE2TyxMQHQ # CLY8wcM2Vl3anFod4Aw1469rSPqX64mD2/FY8/E8hx4Ml8uguKSIfWltggA6Iz9Q # JT62gh5n0FNp/NMHVb2xrixS9kPG85XkRBpoyuzgSdFwnnpDB3thvPr/dbtD88fG # QIJrFyE0oPy/ygGgYBRYz2RS8waist6pM73P1bjoFPFIYg5OQ3GG1Qq5v8j/kJZN # 3xs4S6rUTTs8V5epiGTu9A2H8caW6DOp4hs/kn+3DaRLX2I+4txNSQV+YmLZToJf # /f689aTo9wPBbPjHOVZHAvoGnXZpemhC0yf1qnyY5F2i1qzTdeQgTPKrz9eXBtZC # uXToFL3QItI+9fuRrmSPkrmJxCb0igGL+8fNUcuHWvI/rV2CDoDYQUomJGGofyqJ # SB38PuEaqJta9BOyT+h0ykvuX5y2OqrV1Rk1INlon7KbBpwUlQZmJOFg4ikAslXs # 3snZvqUSgttJ4g9AhgTWfU+hpi9H17dcqsV47VZ6Apf9tM+ogxBMeVN6TSi++EgB # 1XKqFQkC00k3GV7kpa3YXg8zadQgzEm9Hy6IqHJsSgvbOmrSMsL8zjIxtmSLHrYg # 5a0U6x9metWVrPP7J4ivQHyNUsB709GzkdSYfLg= # SIG # End signature block |