*Update. Changes made due to API only allowing 100 licenses to be released at one time.*
Building on getting the licenses on the last post, how about a way to release them? Obviously you will need to follow the guidelines laid out by Citrix EULA on when you can release licenses: Citrix License Usage.
You do have the option to use the console in Citrix Cloud, but if you prefer to do things via script, you can do that as well!
Important note to make…. The format of the consumerList is what presented the biggest issue. With a bit of assistance from a teammate, it was possible to get the proper way to format the data.
This is the format to have the data in:
You’ll notice the consumerList = @() in there. It is expecting to have an array of consumerList be passed to it. If you do not format this way, you will get invalid character errors.
Example of format that will fail all so miserably and give you that wonderful invalid character error:
This is an example of the output that will work and prevent frustration:
Example with five of users:
Example of result of successful release of 5 users:
# Script to release licenses from Citrix Cloud. Tested with PowerShell ISE. Also requires secure-client.csv with ID, Secret, and CustomerID in CSV to pass to Citrix Cloud.
# This must be used in accordance with Citrix EULA for users not having accessed in 30+ day period. Example below is targeting where users that have not logged in over 60 days.
# This will report on what users are in that group over 60 days. You can do a quick check using $releaseUsers.count before doing the release to see how many licenses will be released.
# ALWAYS check the exported report of users before releasing to make sure you are releasing the licenses you are expecting to.
$date = (Get-Date).AddDays(-60)
$creds = Import-Csv "c:\scripts\secure-client.csv"
$CLIENT_ID = $creds.ID
$CLIENT_SECRET = $creds.Secret
$CUSTOMER_ID = $creds.CustomerID
$tokenUrl = 'https://api-us.cloud.com/cctrustoauth2/root/tokens/clients'
$reportName = "user-license-toRelease.csv"
$getDate = Get-Date -Format MMddyyyy
$reportLocation = "C:\scripts\logs"
$response = Invoke-WebRequest $tokenUrl -Method POST -Body @{
grant_type = "client_credentials"
client_id = $CLIENT_ID
client_secret = $CLIENT_SECRET
}
$token = $response.Content | ConvertFrom-Json
$headers = @{
Accept = "application/json"
Authorization = "CwsAuth Bearer=$($token.access_token)"
'Citrix-CustomerId' = $CUSTOMER_ID
}
$resourceLocUrl = "https://api-us.cloud.com/catalogservice/$CUSTOMER_ID/sites"
$response = Invoke-WebRequest $resourceLocUrl -Headers $headers
$content = $response.Content | ConvertFrom-Json
$siteID = $content.sites.id
$headers = @{
Accept = "application/json"
Authorization = "CwsAuth Bearer=$($token.access_token)"
'Citrix-CustomerId' = $CUSTOMER_ID
'Citrix-InstanceId' = $siteID
}
$consumerList = Invoke-RestMethod "https://api-us.cloud.com/licensing/license/enterprise/cloud/cvad/ud/users" -Method GET -Headers $headers | Select-Object consumerList -ExpandProperty consumerList -ExcludeProperty consumerList
$releaseUsers = $consumerList | Where-Object {[DateTime]$_.latestLogonTime -lt $date}
$releaseUsers | Export-Csv "$reportLocation\$getDate-$reportName" -Append -NoTypeInformation
if($releaseUsers.count -gt 100){
$smallerCounter = $releaseUsers.Count
$toSkip = 0
Do{
$smallerList = $releaseUsers | Select-Object -Skip $toSkip -first 100
$body = @{
productEdition = "XAXDFull"
licenseType = "user"
consumerList = @(
$smallerList.consumerId
)
} | ConvertTo-Json
Invoke-RestMethod "https://api-us.cloud.com/licensing/license/enterprise/cloud/cvad/ud/licenserelease" -Method POST -Body $body -Headers $headers -ContentType 'application/json'
$toSkip += 100
$smallerCounter -= 100
} Until ($smallerCounter -lt 100)
$smallerList = $releaseUsers | Select-Object -Skip $toSkip -first $smallerCounter
$body = @{
productEdition = "XAXDFull"
licenseType = "user"
consumerList = @(
$smallerList.consumerId
)
} | ConvertTo-Json
Invoke-RestMethod "https://api-us.cloud.com/licensing/license/enterprise/cloud/cvad/ud/licenserelease" -Method POST -Body $body -Headers $headers -ContentType 'application/json'
}
I have been working on updating the VDA install / upgrade script and combining the app install script and getting it all in one script. This will now check for .NET version (for prerequisite of 2203LTSR), upgrade if necessary, and then install / upgrade VDA. This will also check if RDS role is installed on server for a new build, and if not, it will install it and kick off application installs. Install switches past 2203CU2 are different, so please refer to CTX article on proper install switches for newer versions. Just make the changes to the install batch files with the appropriate switches. This will also check against OS types and copy the appropriate VDA / install scripts based on if it is server OS or desktop OS detected. Link below for the older version and for configuring Citrix Cloud connection profile.
# Script to check for .NET 4.8 and install if not installed as well as checking for OS Type to determine which version to install for VDA Upgrade / Install. This will also check if RDS is installed on server OS and install role if not installed.
# This will also run a baseinstall script that will install applications outlined in the batch file. This requires PowerShell, the DaaS SDK, connection to vCenter, and a profile defined as "default" for the Citrix Cloud connection.
# This will also snapshot the server / desktop if running vCenter. This also requires admin access to the target machines as well as firewall access / remote PowerShell enabled. You will need to run ISE as admin.
asnp Citrix*
$VDIList = Get-Content "C:\scripts\logs\vdaupgrade.txt"
$source = "fileshare.fqdn\vdaupgrade"
$dest = "c$\software\vdaupgrade"
$serverInstallFile = "install_server.bat"
$serverRemoveFile = "remove_server.bat"
$desktopInstallFile = "install_desktop.bat"
$desktopRemoveFile = "remove_desktop.bat"
$VDAFileServer = "VDAServerSetup_2203_2000.exe"
$VDAFileDesktop = "VDAWorkstationSetup_2203_2000.exe"
$vcenter = "vCenter.fqdn"
$dotNetSource = "fileshare.fqdn\DotNET-48"
$dotNetInstall = "ndp48-x86-x64-allos-enu.exe"
$date = Get-Date -Format MMddyyyy
$totalItems = $VDIList.Count
$currentItem = 0
$percentComplete = 0
$report = @()
if($global:defaultviserver -eq $null){
Connect-VIServer $vcenter
}
if($GLOBAL:XDSDKProxy -eq $null){
Get-XDAuthentication -ProfileName "default"
}
foreach ($VDI in $VDIList) {
Write-Progress -Activity "Starting on $VDI" -Status "$percentComplete% Complete:" -PercentComplete $percentComplete
$line = "" | Select-Object Name, PreviousVersion, SnapShot
$VDI1 = ($VDI.Split('.')[0])
$line.Name = "$VDI"
$line.PreviousVersion = (Get-BrokerMachine -HostedMachineName $VDI1 | Select-Object AgentVersion).AgentVersion
$snapshot = (Get-VM $VDI1 | New-Snapshot -name $date-$VDI1-preupgrade)
$line.SnapShot = (Get-VM $VDI1 | Get-Snapshot).Name
$dotnetTest = Invoke-Command -ComputerName $VDI -ScriptBlock {Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client' | Get-ItemProperty -name Version}
$dotnetVersion = $dotnetTest.Version
[System.Version]$testResult = $dotnetVersion
if($testResult.Major -eq 4 -and $testResult.Minor -lt 8){
Write-Host "Copying and installing .NET 4.8 on $VDI"
if (!(Test-Path -Path "\\$VDI\c$\software\vdaupgrade")) {
New-Item -ItemType Directory -Path "\\$VDI\c$\software" -Name "vdaupgrade"
Copy-Item "\\$dotNetSource\$dotNetInstall" -Destination "\\$VDI\$dest" -Force
}
else {
Copy-Item "\\$dotNetSource\$dotNetInstall" -Destination "\\$VDI\$dest" -Force
}
$session = New-PsSession -ComputerName $VDI
$remoteSession = Invoke-Command -ScriptBlock {Start-Process -FilePath "c:\software\vdaupgrade\ndp48-x86-x64-allos-enu.exe" -ArgumentList @("/q") -wait -Verb RunAs} -Session $session
Remove-PSSession -Id $session.id
Start-Sleep -s 30
Do {Start-Sleep -s 15}
Until ((Test-NetConnection -ComputerName $VDI -Port 445).TcpTestSucceeded -eq $true)
Do {Start-Sleep -s 5}
Until ((Get-Service -ComputerName $VDI -Name 'TermService').Status -eq "Running")
Write-Host "$VDI back up"
}
$wmiOSTypeCheck = Get-WmiObject -ComputerName $VDI -Class Win32_OperatingSystem | Where {$_.Primary -eq $true}
if($wmiOSTypeCheck.ProductType -eq 3){
$installFile = "$serverInstallFile"
$removeFile = "$serverRemoveFile"
$VDAFile = "$VDAFileServer"
$rdsCheck = (Invoke-Command -ComputerName $VDIList -ScriptBlock {Get-WindowsFeature | Where-Object Name -like "rds-rd-server" | Select-Object InstallState })
if($rdsCheck.InstallState.value -eq "Available") {
Write-Host "RDS not installed. Installing RDS role on $VDI"
Copy-Item "\\$source\baseinstall.bat" -Destination "\\$VDI\$dest" -Force
if (!(Test-Path -Path "\\$VDI\c$\software\vdaupgrade")) {
New-Item -ItemType Directory -Path "\\$VDI\c$\software" -Name "vdaupgrade"
Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
}
else {
Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
}
Invoke-Command -ComputerName $VDI -Scriptblock {
$action = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\install_server.bat'
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U
$taskName = "VDAInstall"
$taskDescription = "Citrix VDA Install"
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName $taskName -Description $taskDescription
}
Invoke-Command -ComputerName $VDI -Scriptblock {
$time = (Get-Date).AddMinutes(7)
$action = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\baseinstall.bat'
$trigger = New-ScheduledTaskTrigger -Once -At $time
$principal = New-ScheduledTaskPrincipal -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U
$taskName = "BaseInstall"
$taskDescription = "Base Software Install"
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName $taskName -Description $taskDescription
}
Invoke-Command -ComputerName $VDI -ScriptBlock {
Add-WindowsFeature rds-rd-server
Restart-computer
}
}
Write-Host "Copying VDA files and installing on server $VDI"
if (!(Test-Path -Path "\\$VDI\c$\software\vdaupgrade")) {
New-Item -ItemType Directory -Path "\\$VDI\c$\software" -Name "vdaupgrade"
Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
}
else {
Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
}
Invoke-Command -ComputerName $VDI -Scriptblock {
$time = (Get-Date).AddMinutes(3)
$action = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\remove_server.bat'
$trigger = New-ScheduledTaskTrigger -Once -At $time
$principal = New-ScheduledTaskPrincipal -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "VDAUninstall" -Description "Citrix VDA Uninstall"
}
Invoke-Command -ComputerName $VDI -Scriptblock {
$action = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\install_server.bat'
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "VDAInstall" -Description "Citrix VDA Install"
}
}
if($wmiOSTypeCheck.ProductType -eq 1){
$installFile = "$desktopInstallFile"
$removeFile = "$desktopRemoveFile"
$VDAFile = "$VDAFileDesktop"
Write-Host "Copying VDA files and installing on desktop $VDI"
if (!(Test-Path -Path "\\$VDI\c$\software\vdaupgrade")) {
New-Item -ItemType Directory -Path "\\$VDI\c$\software" -Name "vdaupgrade"
Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
}
else {
Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
}
Invoke-Command -ComputerName $VDI -Scriptblock {
$time = (Get-Date).AddMinutes(3)
$action = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\remove_desktop.bat'
$trigger = New-ScheduledTaskTrigger -Once -At $time
$principal = New-ScheduledTaskPrincipal -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "VDAUninstall" -Description "Citrix VDA Uninstall"
}
Invoke-Command -ComputerName $VDI -Scriptblock {
$action = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\install_desktop.bat'
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "VDAInstall" -Description "Citrix VDA Install"
}
}
$currentItem++
$percentComplete = [int](($currentItem / $totalItems) * 100)
$report += $line
Start-Sleep -Milliseconds 2500
}
$report | Export-Csv c:\scripts\logs\$date-vda-upgrades.csv -Append -NoTypeInformation
VDAUpgrade.txt
machine1.fqdn
machine2.fqdn
machine3.fqdn
BaseInstall.bat
baseinstall.bat
@ECHO ON
change user /install
REM pause
timeout 5
net localgroup "Remote Desktop Users" /add "domain1\domain users" "domain2\domain users"
REM pause
timeout 5
REG IMPORT C:\software\mode.reg
REM pause
timeout 5
C:\software\AcrobatRdrDC\setup.exe /sAll /ini Setup.ini
REM pause
timeout 10
cd C:\software\MS-Edge
powershell -File ".\Install-Edge.ps1" -MSIName "MicrosoftEdgeEnterpriseX64.msi" -ChannelID "{56eb18f8-b008-4cbd-b6d2-8c97fe7e9062}" -DoAutoUpdate "True"
REM pause
timeout 5
msiexec.exe /i "C:\software\Google-Chrome\64B\GoogleChromeStandaloneEnterprise64.msi" /qn
REM pause
timeout 5
C:\software\Office\setup.exe /config .\ProPlus.WW\config.xml /adminfile CITRIX.MSP
REM pause
timeout 10
change user /execute
REM pause
timeout 5
C:\Windows\system32\schtasks.exe /delete /tn BaseInstall /f
C:\Windows\System32\timeout.exe /t 5
C:\Windows\System32\shutdown.exe /r /t 20 /f
del c:\software\vdaupgrade\baseinstall.bat /F
Install-Edge.ps1
Install-Edge.ps1
param
(
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[a-zA-Z0-9]+.[m|M][s|S][i|I]$')]
[string]$MSIName,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^{[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}}$')]
[string]$ChannelID,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$DoAutoUpdate
)
# See if autoupdate is false
if($DoAutoUpdate -eq $false)
{
# Registry value name is in the format "Update<{ChannelID}> where ChannelID is the GUID
Set-Variable -Name "AutoUpdateValueName" -Value "Update$ChannelID" -Option Constant
Set-Variable -Name "RegistryPath" -Value "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" -Option Constant
# Test if the registry key exists. If it doesn't, create it
$EdgeUpdateRegKeyExists = Test-Path -Path $RegistryPath
if (!$EdgeUpdateRegKeyExists)
{
New-Item -Path $RegistryPath
}
# See if the autoupdate value exists
if (!(Get-ItemProperty -Path $RegistryPath -Name $AutoUpdateValueName -ErrorAction SilentlyContinue))
{
New-ItemProperty -Path $RegistryPath -Name $AutoUpdateValueName -Value 0 -PropertyType DWord
}
$AutoupdateValue = (Get-ItemProperty -Path $RegistryPath -Name $AutoUpdateValueName).$AutoUpdateValueName
# If the value is not set to 0, auto update is not turned off, this is a failure
if ($AutoupdateValue -ne 0)
{
Write-Host "Autoupdate value set incorrectly"
return -1
}
}
# Install the Edge MSI
return (Start-Process msiexec.exe -Wait -PassThru -ArgumentList "/i $MSIName /q").ExitCode
Install_Server.bat
REM change port number in below command.
REM Use citrix vda command line helper tool from citrix. https://support.citrix.com/article/CTX234824 if needed
REM Install new VDA agent, delete files and scheduled tasks. Finally reboot.
C:\software\vdaupgrade\VDAServerSetup_2203_2000.exe /components VDA /controllers "CloudConnector1 CloudConnector2" /noreboot /quiet /disableexperiencemetrics /enable_remote_assistance /enable_real_time_transport /enable_hdx_ports /enable_hdx_udp_ports /remove_pvd_ack /virtualmachine /masterpvsimage /includeadditional "Citrix Personalization for App-V - VDA","Citrix Profile Management","Citrix Profile Management WMI Plug-in","Citrix Telemetry Service","Citrix Supportability Tools" /exclude "Citrix Backup and Restore","Citrix MCS IODriver","Citrix Rendezvous V2","Citrix VDA Upgrade Agent","Machine Identity Service","User personalization layer","AppDisks VDA Plug-in","Citrix Files for Outlook","Citrix Files for Windows","Personal vDisk"
C:\Windows\system32\schtasks.exe /delete /tn VDAInstall /f
C:\Windows\system32\schtasks.exe /delete /tn VDAUninstall /f
del c:\software\vdaupgrade\remove.bat /F
del c:\software\vdaupgrade\VDAServerSetup_2203_2000.exe /F
C:\Windows\System32\timeout.exe /t 5
C:\Windows\System32\shutdown.exe /r /t 20 /f
del c:\software\vdaupgrade\install.bat /F
Install_Workstation.bat
REM change port number in below command.
REM Use citrix vda command line helper tool from citrix. https://support.citrix.com/article/CTX234824 if needed
REM Install new VDA agent, delete files and scheduled tasks. Finally reboot.
C:\software\vdaupgrade\VDAWorkstationSetup_2203_2000.exe /components VDA /controllers "CloudConnector1 CloudConnector2" /noreboot /quiet /disableexperiencemetrics /enable_remote_assistance /enable_real_time_transport /enable_hdx_ports /enable_hdx_udp_ports /remove_pvd_ack /virtualmachine /masterpvsimage /includeadditional "Citrix Personalization for App-V - VDA","Citrix Profile Management","Citrix Profile Management WMI Plug-in","Citrix Telemetry Service","Citrix Supportability Tools" /exclude "Citrix Backup and Restore","Citrix MCS IODriver","Citrix Rendezvous V2","Citrix VDA Upgrade Agent","Machine Identity Service","User personalization layer","AppDisks VDA Plug-in","Citrix Files for Outlook","Citrix Files for Windows","Personal vDisk"
C:\Windows\system32\schtasks.exe /delete /tn VDAInstall /f
C:\Windows\system32\schtasks.exe /delete /tn VDAUninstall /f
del c:\software\vdaupgrade\remove.bat /F
del c:\software\vdaupgrade\VDAWorkstationSetup_2203_2000.exe /F
C:\Windows\System32\timeout.exe /t 5
C:\Windows\System32\shutdown.exe /r /t 20 /f
del c:\software\vdaupgrade\install.bat /F
mode.reg
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM\Licensing Core]
"LicensingMode"=dword:00000004
So you want to find out what licenses are in use? Maybe you want to know licenses that are checked out that might be over 30 days since that user logged in. Maybe you want to know for longer periods of time. You can get this from the Citrix Cloud console. You can also get it another way. And you can filter down to based on if it has been a period of time since the user accessed. This example gets all users then filters down to the latestLogonTime of older than 30 days.
# Script to get license use from Citrix Cloud. Tested with Powershell ISE. Also requires secure-client.csv with ID, Secret, and CustomerID in CSV to pass to Citrix Cloud.
# This creates a csv with the consumerID, deviceCount, consumerDisplayName, latestLogonTime, and firstLogonTime.
<# All fields available:
consumerId : user@company.com
consumerDisplayName : user
deviceCount : 0
userId : user@company.com
upn : user@company.com
userName : user
domain : domain
latestLogonTime : UTC Time
firstLogonTime : UTC Time
#>
$date = Get-Date
$olderThan = $date.AddDays(-30)
$creds = Import-Csv "c:\scripts\secure-client.csv"
$CLIENT_ID = $creds.ID
$CLIENT_SECRET = $creds.Secret
$CUSTOMER_ID = $creds.CustomerID
$tokenUrl = 'https://api-us.cloud.com/cctrustoauth2/root/tokens/clients'
$reportName = "user-license-use.csv"
$getDate = Get-Date -Format MMddyyyy
$reportLocation = "C:\scripts\logs"
$output = @()
$searchOutput = @()
$response = Invoke-WebRequest $tokenUrl -Method POST -Body @{
grant_type = "client_credentials"
client_id = $CLIENT_ID
client_secret = $CLIENT_SECRET
}
$token = $response.Content | ConvertFrom-Json
$headers = @{
Accept = "application/json"
Authorization = "CwsAuth Bearer=$($token.access_token)"
'Citrix-CustomerId' = $CUSTOMER_ID
}
$resourceLocUrl = "https://api-us.cloud.com/catalogservice/$CUSTOMER_ID/sites"
$response = Invoke-WebRequest $resourceLocUrl -Headers $headers
$content = $response.Content | ConvertFrom-Json
$siteID = $content.sites.id
$headers = @{
Accept = "application/json"
Authorization = "CwsAuth Bearer=$($token.access_token)"
'Citrix-CustomerId' = $CUSTOMER_ID
'Citrix-InstanceId' = $siteID
}
$response = Invoke-RestMethod "https://api-us.cloud.com/licensing/license/enterprise/cloud/cvad/ud/users" -Method 'GET' -Headers $headers
$output = $response.consumerList
foreach($out in $output){
$line = "" | Select-Object consumerId, deviceCount, consumerDisplayName, latestLogonTime, firstLogonTime
$line.consumerId = $out.consumerId
$line.deviceCount = $out.deviceCount
$line.consumerDisplayName = $out.consumerDisplayName
$line.latestLogonTime = ([DateTime]$out.latestLogonTime)
$line.firstLogonTime = ([DateTime]$out.firstLogonTime)
$searchOutput += $line
}
$searchOutput | Where-Object latestLogonTime -lt $olderThan| Export-Csv "$reportLocation\$getDate-$reportName" -Append -NoTypeInformation
So you see that the HTML5 Workspace App updates pretty regularly. That’s a great thing! How about you may have several StoreFront servers. You want to do that manually EVERY time? Of course not! Here is a script that will do that for you! You will also need to go get this function from github that makes this work. The piece of resistance one could say! It lets you get the file version details metadata.
While trying to figure out how to target the HTML5 Workspace App version, I did not see it in registry or in Add/Remove Programs. So I was checking the location of the files to see what I could find. I ended up checking C:\Program Files\Citrix\Receiver StoreFront\HTML5Client and found a file SRI.js that had citrixHTML5Launcher version. I figured I could just check that file and get the version number as it reflected the correct version I had installed previously. I did an upgrade to check if that showed the correct version after the upgrade, and it did. Also found out that once you install it, you cannot go to an earlier version, so be sure to snapshot if you want to do some testing with versions.
Here is the link to the function by Lukas Wohrl that allowed me to get the file version from install file. Get-FileMetaData
Below is the script to do the upgrade:
# Script to check version of CitrixHTML5Client on StoreFront servers, copy upgrade file to upgrade folder, install upgrade, based on version detected.
# You will need the function Get-FileMetaData from https://gist.github.com/woehrl01/5f50cb311f3ec711f6c776b2cb09c34e. This allows you to get the file version and convert to
# [System.Version] type to compare. You will also need to have access to the StoreFront servers as admin to be able run invoke commands and if you need to snapshot, access to your hypervisor. Format for txt file is server.fqdn.
$creds = Get-Credential
$storeFronts = Get-Content "C:\scripts\logs\storefronts.txt"
$date = Get-Date -Format MMddyyyy
$sourceWorkspaceAppLocation = "fileserver\HTML5Client\upgrade"
$targetFileName = "CitrixHTML5Client-x64.exe"
$fileInfo = Get-FileMetaData "\\$sourceWorkspaceAppLocation\$targetFileName"
$pathToCheckStoreFront = "C$\Program Files\Citrix\Receiver StoreFront\HTML5Client\"
[System.Version]$targetVersion = $fileInfo.'File version'
$storeFrontDestinationLocation = "C$\software\html5"
$totalItems = $storeFronts.Count
$vCenterAddress = "vCenter.fqdn"
$storeFrontUpgradeReport = [PSCustomObject]@{}
$currentItem = 0
$percentComplete = 0
if($global:defaultviserver -eq $null){
Connect-VIServer $vcenter
}
foreach($store in $storeFronts){
$storeFrontUpgradeReport | Add-Member -NotePropertyName "StoreFrontName" -NotePropertyValue "$store"
Write-Progress -Activity "Checking StoreFront Server $store and upgrading if applicable" -Status "$percentComplete% Complete:" -PercentComplete $percentComplete
if (!(Test-Path -Path "\\$store\$pathToCheckStoreFront")){
Write-Host "Path not found on $store. HTML5 Client does not appear to be installed." -BackgroundColor Yellow -ForegroundColor Black
}
$html5Version = Invoke-Command -ComputerName $store -ScriptBlock {Get-Content 'C:\Program Files\Citrix\Receiver StoreFront\HTML5Client\SRI.js' | Select-String -Pattern "citrixHTML5Launcher\.([0-9]+(\.[0-9]+)+)" | foreach {$_.Matches[0].Groups[1].Value} }
[System.Version]$testVersion = $html5Version
if($testVersion -lt $targetVersion){
$storeFrontUpgradeReport | Add-Member -NotePropertyName "VersionBeforeUpgrade" -NotePropertyValue "$html5Version"
Write-Host "Working on $store"
if (!(Test-Path -Path \\$store\$storeFrontDestinationLocation)) {
New-Item -ItemType Directory -Path \\$store\c$\software -Name html5
Copy-Item "\\$sourceWorkspaceAppLocation\$targetFileName" -Destination \\$store\$storeFrontDestinationLocation -Force
}
else {
Copy-Item "\\$sourceWorkspaceAppLocation\$targetFileName" -Destination \\$store\$storeFrontDestinationLocation -Force
}
$shortName = $store.Split('.')[0]
Get-VM $shortName | New-Snapshot -Name "$date-$shortName"
$session = New-PsSession -ComputerName $store -Credential $creds
$remoteSession = Invoke-Command -ScriptBlock {Start-Process -FilePath "C:\software\html5\CitrixHTML5Client-x64.exe" -Verb RunAs} -Session $session
Start-Sleep -Seconds 30
$remoteSession2 = Invoke-Command -ComputerName $store -ScriptBlock {Get-Content 'C:\Program Files\Citrix\Receiver StoreFront\HTML5Client\SRI.js' | Select-String -Pattern "citrixHTML5Launcher\.([0-9]+(\.[0-9]+)+)" |foreach {$_.Matches[0].Groups[1].Value} }
$storeFrontUpgradeReport | Add-Member -NotePropertyName "VersionAfterUpgrade" -NotePropertyValue "$remoteSession2"
}
$currentItem++
$percentComplete = [int](($currentItem / $totalItems) * 100)
Start-Sleep -Milliseconds 1000
}
Get-PSSession | Remove-PSSession
$storeFrontUpgradeReport | Export-Csv -Path "C:\scripts\logs\storefront-HTML5-upgrade.csv" -Append -NoTypeInformation
Maybe you need to check the .NET version installed and if it is below a certain version, go install it. When upgrading to 2203LTSR VDA, you have to have 4.8 installed as a prerequisite. This will create a PSSession and remove it when it is done. In your upgrade.txt file, use the FQDN of the target machines. .NET installs seem to take a bit to complete, so be patient in it upgrading. Using the [System.Version] lets you compare major, minor, build, and revision so you can get more granular with checking versions in your test cases.
# Script to check if version is below 4.8 .NET and install 4.8 .NET if so. This requires running from an account that has admin on the destination and also a reboot to complete update.
# Tested on Server 2016 and 2019.
$dotNetSource = "uncshare\DotNET-48"
$dotNetInstall = "ndp48-x86-x64-allos-enu.exe"
$machineList = Get-Content "C:\scripts\logs\upgrade.txt"
$dest = "c$\software\upgrade"
$creds = Get-Credential
foreach($machine in $machineList){
$dotnetTest = Invoke-Command -ComputerName $machine -ScriptBlock {Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client' | Get-ItemProperty -name Version}
$dotnetVersion = $dotnetTest.Version
[System.Version]$testResult = $dotnetVersion
if($testResult.Major -eq 4 -and $testResult.Minor -lt 8){
Write-Host "Working on $machine"
if (!(Test-Path -Path \\$machine\c$\software\upgrade)) {
New-Item -ItemType Directory -Path \\$machine\c$\software -Name upgrade
Copy-Item "\\$dotNetSource\$dotNetInstall" -Destination \\$machine\$dest -Force
}
else {
Copy-Item "\\$dotNetSource\$dotNetInstall" -Destination \\$machine\$dest -Force
}
$session = New-PsSession -ComputerName $machine -Credential $creds
$remoteSession = Invoke-Command -ScriptBlock {Start-Process -FilePath "c:\software\upgrade\ndp48-x86-x64-allos-enu.exe" -ArgumentList @("/q") -wait -Verb RunAs} -Session $session
Remove-PSSession -Id $session.id
}
}
Looks like there have been some updates to the Activity Manager on the Citrix Cloud Director. Buttons have been moved. Layout appears different. The contact card in the upper-left appears now on the Activity Manager and Details page. The overall feel of this is really nice and feels sleeker.
The Details pane appears to have remained the same.
Sometimes it is just good to have all the network names of your VDI and IP. This will get the VM name, network adapter, network name defined in vSphere, and IP.
# Script to get network names for VDI machines. This was tested with VMware 7.x, Citrix Cloud connection configured with the "default" profile, and VDI with single NIC.
asnp Citrix*
Get-XDAuthentication -ProfileName "default"
$date = Get-Date -Format MMddyyyy
$report = @()
$CurrentItem = 0
$PercentComplete = 0
$ctxVDI = (Get-BrokerMachine -MaxRecordCount 100000 | Where-Object SessionSupport -eq "SingleSession" | Where-Object HostedMachineName -ne $null | Select-Object HostedMachineName).HostedMachineName
$totalItems = ($ctxVDI).count
foreach($ctx in $ctxVDI){
Write-Progress -Activity "Getting network name for $ctx" -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
$line = "" | Select-Object VM, Name, NetworkName, IPAddress
$networkInfo = (Get-VM $ctx | Get-NetworkAdapter)
$ipAddress = (Get-VM $ctx | Select-Object @{N="IPAddress";E={@($_.guest.IPAddress[0])}})
if($networkInfo -ne $null){
$line.VM = $ctx
$line.Name = $networkInfo.Name
$line.NetworkName = $networkInfo.NetworkName
$line.IPAddress = ($ipAddress).IPAddress
}
$report += $line
$CurrentItem++
$PercentComplete = [int](($CurrentItem / $TotalItems) * 100)
}
$report | export-csv c:\scripts\logs\$date-vdinetworks.csv -NoTypeInformation -Append
So you got your SAML Authentication server all configured. You got your MFA rolling. You start your day. You open up another app that has an assigned enterprise application to it with conditional access set. Then you open up your Citrix tab. You go to the site. It redirects you. And BOOM. Just like that. ERROR!!!
You think think think and think about why you are getting the error. You know when you opened Citrix FIRST yesterday the world was all sunny and bright. But today, that is not the case. But you really read the error closely. And you notice something… Authentication method ‘Password.’ You know that when you opened Citrix yesterday with your password and MFA, then continued on, it all worked. But change the order, and it does not. So. You go and check your SAML authentication settings.
You’ll see that by default, the “Password” class type is selected when you create the SAML authentication server. If you click on it so it is no longer blue, then save it, you notice that everything seems to work. So anything that is set there is EXPECTED in the assertion, not what is ACCEPTED. This would happen more if you have conditional access to not prompt on prem for one app, and prompt always on the other enterprise app. If you clear that, it will allow you to use the SAML assertion you got from the other app, assuming it is with the same IDP. There is also another option that you see outlined. “Force Authentication.” This option, if set, will force the session you start to redo the authentication and not use anything that you have cached. This is also good for testing purposes to force it to go through the authentication process.
Ran into some fun with setting up FAS for MFA. I was testing a shorter list of ciphers on a test SSL profile on ADC on the test vServer. Come to find out, when accessing a machine that was using MFA from outside the network, I was getting an SSL error 4 on Windows machines and SSL error 47 on Stratodesk machines. I hadn’t seen that error since Receiver 4.x. It appears there are some additional ciphers needed in regards to the Citrix Workspace App. It appeared to work fine with the other cipher set using the HTML5 Workspace App. This article has the updated cipher set you need to have or it may cause you some issues (Changes To FAS Ciphers). These would be applied to your SSL profile assigned to the vServer on the ADC.
I also ran into an issue with EndGame.
When trying to connect from to VDI Windows 10 machines, you would encounter an incorrect user name or password error if EndGame was enabled, instead of it SSO logging you in.
Checking the event log on the machine, you encounter a Smart Card Logon Event 5.
There are 2 DLLs you have to add to a global exclusion, scardhook.dll and scardhook64.dll. These are located under C:\Program Files\Citrix\ICAService. Just excluding those DLLs got rid of the Event 5 Smart Card Logon error and allowed the Provider DLL to initialize.
After getting these exclusions applied, SSO works normally for accessing the VDI machines.
So you have a new / different vCenter you want to move your VDI machines to. For the power management part, you will need to have the other hypervisor connection configured. You can run the script below to change the hypervisor connection on your VDI machines. You will want to make sure you have the VMs powered down before beginning. The first steps are just information gathering. This is part of migrating machines to new hardware. I will be adding the other pieces at a later date.
First you will need to get your hypervisor names and Uids with this command: Get-BrokerHypervisorConnection | Select-Object Name, HypHypervisorType, Uid.
Then you can get a list of machines with the hypervisor connection you want to change from. This was just getting the first machine that had the hypervisor I wanted to change from. Machine was already moved to a new Uid but it would be 2 in this case. Get-BrokerMachine -MaxRecordCount 100000 | Where-Object SessionSupport -eq “SingleSession” | Where-Object HypervisorConnectionUid -eq “3” | Select-Object -first 1 | Select-Object HostedMachineName, HypervisorConnectionName, HypervisorConnectionUid
<# Script to change Hypervisor connection and power systems back up. This was done with PowerShell ISE 5.1, default profile configured on CitrixCloud SDK, and ESXi 7.0 with connection
to vCenter. This was built from Ben McGirt and slightly modified to get the machine names with a specific configured HypervisorConnection via Get-BrokerMachine command.
As it is best to change these settings and power the VM on, you will need to have the VMs powered down before beginning. The Uid in the example is "2" to get the machines using a different
HypvisorConnectionUid that you wish to change from and setting in this example to HypervisorConnectionUid "3."
#>
Get-XDAuthentication -ProfileName "default"
$getVDIMachines = Get-BrokerMachine -MaxRecordCount 100000 | Where-Object SessionSupport -eq "SingleSession" | Where-Object HypervisorConnectionUid -eq "2" | Select-Object HostedMachineName, HypervisorConnectionName, HypervisorConnectionUid
$citrixVMs = $getVDIMachines.HostedMachineName
Function PowerOnVM ([string] $Name) #, [string] $Hostname)
#From https://thecloudxpert.net/2016/04/25/howto-power-on-a-vmware-virtual-machine-with-powercli-powercli-101/
{
$VM = Get-VM -Name $Name
Switch ($VM.PowerState)
{
PoweredOn { Write-Host "$VM already Powered On.";break}
PoweredOff { Write-Host "Powering on $VM"; Start-VM -VM $VM;break}
Suspended { Write-Host "$VM suspended.";break}
Default {break}
}
}
ForEach ($VM in $CitrixVMs){
Set-BrokerMachine -MachineName ("*\" + $VM) -HypervisorConnectionUid 3
Start-Sleep -Seconds 2
PowerOnVM $VM
}