Virtualization, technology, and random rantings with a focus on Citrix and VMware.

Tag: powershell Page 1 of 4

Update to Licensing Report Script

When upgrading licensing from the User Device (U/D) licenses to the Universal Hybrid Multi Cloud (UHMC), I noticed that there was a change on the report outcome. I would receive a divide by zero error. I was like I haven’t tried dividing by zero in a long time, so I guess it was my turn to do so. The link that was previously used to get license info below was no longer working.

https://api-us.cloud.com/licensing/license/enterprise/cloud/cvad/ud/current

This had to be updated because the new UHMC licenses are concurrent licenses and the link for the information on this is different.

https://api.cloud.com/licensing/license/enterprise/cloud/cvad/ccd/summary

This link has some different options to choose from.

I chose the monthPeak in order to get the high mark for the month. There are sub options there I use which are assignedLicenseCount and totalLicenseCount in the script.

In order to get it to work correctly, I had to add my CustomerID in the secureclient.csv and assign it as $CUSTOMER_ID = $creds.CustomerID in the script. You will also need your SDK installed as well as your secureclient.csv file for your credentials to run this. This assumes you want to run Monday through Friday. You can change the section regarding that if you want to run every single day.

When it runs, you get this output

asnp Citrix*

$Today = Get-Date
if(($Today.DayOfWeek) -eq 'Monday')
{$when = $Today.AddDays(-3)}
else{$when = $Today.AddDays(-1)}

$licenseIOwnCount = licensecount

$creds            = import-csv "c:\scripts\Citrix\secureclient.csv"
$CLIENT_ID        = $creds.ID
$CLIENT_SECRET    = $creds.Secret
$CUSTOMER_ID      = $creds.CustomerID
$tokenUrl         = 'https://api-us.cloud.com/cctrustoauth2/root/tokens/clients'

$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
 }

 # Get Licensing Info
$response            = Invoke-WebRequest "https://api.cloud.com/licensing/license/enterprise/cloud/cvad/ccd/summary" -Method Get -Headers $headers
$content             = $response.Content | ConvertFrom-Json
$response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 10
$licensingTotalCount = $content.monthPeak.totalLicenseCount
$licensingUsageCount = $content.monthPeak.assignedLicenseCount
$licensingRemaining  = ($licenseIOwnCount - $licensingUsageCount)

$connections = Get-BrokerConnectionLog -BearerToken $headers.Authorization -Filter {BrokeringTime -gt $when} -MaxRecordCount 100000 | Select-Object BrokeringUserName
$billedUnits = import-csv "c:\scripts\Citrix\billedunits.csv"

$CitrixVDIConnected     = (Get-BrokerSession -BearerToken $headers.Authorization  -MaxRecordCount 100000 | Where-Object SessionSupport -eq "SingleSession" | Where-Object SessionState -eq "Active").count
$CitrixVDIDisconnected  = (Get-BrokerSession -BearerToken $headers.Authorization  -MaxRecordCount 100000 | Where-Object SessionSupport -eq "SingleSession" | Where-Object SessionState -eq "Disconnected").count


$ctxUsers = [PSCustomObject] @{

  UniqueCitrixUsers      = ($connections.BrokeringUserName | Select-Object -Unique).count
  CurrentSessions        = (Get-Brokersession -BearerToken $headers.Authorization -MaxRecordCount 100000 | Select-Object BrokeringUserName).count
  CitrixVDISessions      = $CitrixVDIConnected + $CitrixVDIDisconnected
  CitrixLicensesUsed     = $licensingUsageCount
  CitrixTotalLicenses    = $licensingTotalCount
  CtxLicenseFreePercent  = ((($licensingRemaining) / $licensingTotalCount ) * 100).ToString("#.##")
  CtxRemainingLicenses   = $licensingRemaining
  CX10BilledUnits        = $billedUnits.CitrixApp
  VD10BilledUnits        = $billedUnits.CitrixVDI

}

# HTML Formatting
$style = "<style>BODY{font-family: Arial; font-size: 10pt;}"
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"
$style = $style + "TD{border: 1px solid black; padding: 5px; }"
$style = $style + "</style>"

# HTML Email Body
$body = $ctxUsers | ConvertTo-Html -Head $style


# Generates email with attachment
$style = "<style>BODY{font-family: Arial; font-size: 10pt;}"
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"
$style = $style + "TD{border: 1px solid black; padding: 5px; }"
$style = $style + "</style>"
$body  = $report | ConvertTo-Html -Head $style
 
$dateEmail        = Get-Date -Format "MM-dd-yyyy"
$emailFrom        = "EmailFrom@company.com"
$emailTo          = "Someone@company.com"
$subject          = "Daily Citrix User Check | $dateEmail"
$email            = New-object System.Net.Mail.MailMessage 
$email.to.Add($emailTo)
$email.From       = New-Object system.net.Mail.MailAddress $emailFrom
$email.Subject    = $subject
$email.IsBodyHtml = $true
$email.body       = $body
 
$smtpserver       = "mail.company.com"
$smtp             = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($email)



Get All VDI SingleSession Assignments

Not bad to have a backup of your machine assignments. And sometimes really nice to be able to get what Machine Catalog, Delivery Group, and if you may have reserved IPs.

asnp Citrix*

$date            = (Get-Date).ToString("yyyyMMdd")
$reportPath      = "c:\scripts\logs"
$reportName      = "$date-vdiassignments-citrix.csv"
$report          = @()

$vdiAssignments  = (Get-BrokerCatalog -MaxRecordCount 250000 | Where-Object SessionSupport -eq "SingleSession" | Select-Object CatalogName).CatalogName

foreach($vdi in $vdiAssignments){

    
    $gatherAssignments = Get-BrokerDesktop -Filter {CatalogName -eq $vdi} | Select-Object -Property MachineName, CatalogName, DesktopGroupName, AssociatedUserNames, IPAddress

    $report += $gatherAssignments    

}

$report | Export-Csv -Path "$reportPath\$reportName" -NoTypeInformation -Append

VDA Upgrade Script: Updated For 2402 LTSR

Another update for upgrading to 2402 LTSR from earlier versions!

The biggest change that I saw in this was the addition of the “/xenapp” switch in the Install_Server.bat and Install_Desktop.bat. For some reason, without that switch, it will not continue. According to the documentation at Install Switches, it says, “Command-line option: /xenapp to install Citrix Virtual Apps. Citrix Virtual Desktops is installed if this option is omitted.” What I found is that it wouldn’t install in either case correctly unless I added that switch. This will still do a WMI check and see if it is a server / desktop class OS and run the appropriate version of the files. This was tested against 2016, 2019, 2022, and multiple versions of Win 10.

$VDIList            = Get-Content "C:\scripts\logs\VDAUpgrade.txt"
$source             = "location.fqdn\Citrix\XenApp\vdaupgrade-2402"
$dest               = "c$\software\vdaupgrade"
$serverInstallFile  = "Install_Server.bat"
$serverRemoveFile   = "Remove_Server.bat"
$desktopInstallFile = "Install_Desktop.bat"
$desktopRemoveFile  = "Remove_Desktop.bat"
$VDAFileServer      = "VDAServerSetup_2402.exe"
$VDAFileDesktop     = "VDAWorkstationSetup_2402.exe"
$vcenter            = "vCenter Address"
$TotalItems         = $VDIList.Count
$CurrentItem        = 0
$PercentComplete    = 0
$date               = Get-Date -Format MMddyyyy
$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
    
    
    $wmiOSTypeCheck  = Get-WmiObject -ComputerName $VDI -Class Win32_OperatingSystem | Where-Object {$_.Primary -eq $true}
    
      if($wmiOSTypeCheck.ProductType -eq 3){
        $installFile = "$serverInstallFile"
        $removeFile  = "$serverRemoveFile"
        $VDAFile     = "$VDAFileServer"
        
     
        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 

VDAUpgrade.txt

machine1.fqdn
machine2.fqdn
machine3.fqdn

Install_Server.bat

C:\software\vdaupgrade\VDAServerSetup_2402.exe /noreboot /quiet /xenapp /components VDA /controllers "CloudConnector1.fqdn CloudConnector2.fqdn" /disableexperiencemetrics /enable_remote_assistance /enable_real_time_transport /enable_hdx_ports /enable_hdx_udp_ports /masterpvsimage /includeadditional "Citrix Personalization for App-V - VDA","Citrix Profile Management","Citrix Profile Management WMI Plug-in"



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_2402.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_Desktop.bat

C:\software\vdaupgrade\VDAWorkstationSetup_2402.exe /noreboot /quiet /xenapp /components VDA /controllers "CloudConnector1.fqdn CloudConnector2.fqdn" /disableexperiencemetrics /enable_remote_assistance /enable_real_time_transport /enable_hdx_ports /enable_hdx_udp_ports /masterpvsimage /includeadditional "Citrix Personalization for App-V - VDA","Citrix Profile Management","Citrix Profile Management WMI Plug-in"

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_2402.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

Remove_Server.bat

“C:\Program Files\Citrix\XenDesktopVdaSetup\XenDesktopVdaSetup.exe” /REMOVEALL /QUIET /NOREBOOT
C:\Windows\System32\shutdown.exe /r /t 5 /f

Remove_Desktop.bat

"C:\Program Files\Citrix\XenDesktopVdaSetup\XenDesktopVdaSetup.exe" /REMOVEALL /QUIET /NOREBOOT
C:\Windows\System32\shutdown.exe /r /t 5 /f

Rolling Remove From Maintenance Script

I was seeing a longer time in upgrading from 2203CU2/CU3 to 2402 especially on VDI. For smaller vCPU amounts like 2 vs 4, the time was 15 minutes (4vCPU) versus nearly 30 minutes (2vCPU) to upgrade. To get machines back online faster, I modified another script I was using to identify what I needed to upgrade. The one issue you will have, is if you are running say 1912 and 2203 with different CUs, they would be below the targeted version in the script and not automatically removed from maintenance mode. You would have to manually remove those from maintenance mode.

Create a scheduled task, that has the appropriate rights and from a machine that can reach the devices, set it for a 5ish minute interval. In my case, I scheduled it from the same machine that was running the upgrade script. Another assumption is that you will need the PowerShell SDK for DaaS loaded on the machine as well. You will need to have a profile from that machine that has a connection profile named “default,” or change the ProfileName to match what yours happens to be.

You can get the $targetVersion information by upgrading one of your machines prior to the rest, which you should be doing and testing! Running the command Get-Brokermachine -HostedMachineName machinetobetested | Select-Object HostedMachineName, AgentVersion on the machine you updated will show the version.

asnp Citrix*

$report                        = @()
[System.Version]$targetVersion = "2402.0.100.629"
$getMachines                   = Get-BrokerMachine -MaxRecordCount 1000000

if($GLOBAL:XDSDKProxy -eq $null){

  Get-XDAuthentication -ProfileName "default"

}

foreach($machine in $getMachines){
  $line                   = "" | Select HostedMachineName, DNSName, AgentVersion, WillBeUpgraded, OperatingSystem, MaintenanceMode
  $testVersion            = $machine.AgentVersion
  
  $line.HostedMachineName = $machine.HostedMachineName
  $line.DNSName           = $machine.DNSName
  $line.AgentVersion      = $machine.AgentVersion
  $line.OperatingSystem   = $machine.OSType
  $line.MaintenanceMode   = $machine.InMaintenanceMode
  
  if([System.Version]$testVersion -ge [System.Version]($targetVersion)){

  $line.WillBeUpgraded    = "Current Version Or Newer"
  
  }
  
  if([System.Version]$testVersion -lt [System.Version]($targetVersion)){

  $line.WillBeUpgraded    = "Yes"
  
  }

  
  $report += $line


}


$removeFromMaintenanceMode = ($report | Where-Object {($_.WillBeUpgraded -notlike "Yes" -and $_.MaintenanceMode -eq "True")} | Select-Object HostedMachineName).HostedMachineName


foreach($remove in $removeFromMaintenanceMode){


Get-BrokerMachine -HostedMachineName "$remove" | Set-BrokerMachine -InMaintenanceMode $false


}

Getting Reliability Monitor Info Remotely

Sometimes you just want to pull a quick check of Reliability Monitor remotely. Easy to do!

Get-WmiObject Win32_ReliabilityRecords -ComputerName machineIP -Property Message | Select-Object -first 10 Message | Format-List *

Quick Function To Find User VDI

Sometimes you need to find a user’s VDI machine to work on it. This function will do that for you. I typically use the last name as the search to limit the scope of the machines found. It will find all machines that contain any part of the string you enter. It also shows the MachineName which includes the domain\machinename to help locate the user machine. You can add other parameters such as AgentVersion if desired. I limited the scope to not include floating pool (Random) assigned machines. For a list of all fields of Get-BrokerMachine that can be selected in the function with Select-Object, please see this link: Get-BrokerMachine Options

# Requires being connected to Citrix Cloud with DaaS SDK. 
Function Get-VDI {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$VDIUserName
    )
   
    Process {
       $userToFind = '*' + $VDIUserName + '*'
       $getVDIs = Get-BrokerMachine -MaxRecordCount 100000 | Where-Object SessionSupport -eq "SingleSession" | Where-Object AllocationType -eq "Static" | Where-Object AssociatedUserNames -like "$userToFind" | Select-Object AssociatedUserNames, MachineName, RegistrationState, InMaintenanceMode, SessionCount
    }
    End {
        $getVDIs
    }
}
Example with user that has 4 desktops assigned.

Also to make it is easy for the day to day, you can add this to your profile with notepad $profile and copy and paste it there and reload.

Get User Info From Email Address

If you sometimes have to find a user list and get group memberships, it can be a chore. It doesn’t have to be. You too can experience a scripted approach to getting that data. This will search the AD Forest’s sub domains that you have rights to at least read to get user memberships that follow a specific pattern. Very useful when you have one domain for groups and users from many domains. Universal groups are great for that. You just need to populate the referenced “useremail.txt” file with user email addresses and deleting any white space in the file. You can also get additional information in the report by adding in the “line = “” | Select-Object” and adding additional fields and then matching the name designated with the “line.Fields = syntax.”

# Script to search domains in forest for users via user email address. You could also search via UPN by replacing EmailAddress with UserPrincipalName.
# This requires AD module, read rights to forest / domains, and user group pattern. Ran with ISE and tested on desktop / server platforms. This also uses
# the global catalog lookup as seen referenced by Get-ADUser -server domain:3268. Assistance from Notesofascripter.com in making this.
$debug      = $true
$domains    = (Get-ADForest).Domains
$date       = Get-Date -Format MMddyy
$userExists = ""
$reportName = "nameofreport.csv"
$report     = @()

$emailList  = Get-Content -Path "c:\scripts\logs\useremail.txt"

$emailSam   =  foreach($user in $emailList) {
    (Get-ADUser -server domain:3268 -f {EmailAddress -eq $user}).SamAccountName
  }

$users      = $emailSam

foreach ($domain in $domains){
    if ($debug){Write-Host $domain -ForegroundColor Cyan}
    foreach ($user in $users) {
        Try{
            $userExists = Get-ADUser $user -Server $domain -ErrorAction Stop
        }
        Catch {
            if ($debug){Write-Host "$user not found in $domain" -BackgroundColor yellow -ForegroundColor black}
            $userExists = $null
            Continue
        }
        if ($userExists -ne $null){
            if ($debug){ Write-Host "Found $user in $domain domain" -BackgroundColor Cyan -ForegroundColor Black}
            $group = $userExists | Get-ADPrincipalGroupMembership | Where-Object Name -like "GroupNamePattern*" -ErrorAction Stop
            $line          = "" | Select-Object Name, Domain, RealName, Group
            $line.Name     = $user
            $line.Domain   = $domain
            $line.RealName = $userExists.name
            $line.Group    = $group.name -join "; "
            if ($debug){ $line }
            $report       += $line
            $users         = $users -ne $user
            
        }
    }
}

$report | Export-Csv "c:\scripts\logs\$date-$reportName" -Append -NoTypeInformation

When Trouble Comes Along, You Must Skip It! : Using -Skip With Select-Object And Do Loop

While working on the script to release licenses, I ran into a dilemma I had encountered before. I needed to move through a set of records in an array, but not the whole array at one time. Since I was limited to 100 licenses released per Invoke-RestMethod, I needed a way to do that without having to call it multiple times. I had also encountered this at other times and usually resorted to just doing the call multiple times and limiting my results to the number I wanted to iterate through. I didn’t think that was efficient and wanted to find a better way. So in looking around, I found this site: ResultSize. Using that wonderful -Skip, I just added that to a Do .. Until loop to get where I wanted to go.

So applying what I wanted to do was 100 records at a time, I set my $toSkip counter to 0 to start and the $smallerCount set to the value of the $someArray.count size inside the If check just before the Do loop. After each loop, I would increment the $toSkip and decrement the $smallerCounter by the same amount and set the Until condition to be less that the increment / decrement amount. Then I copied the same code for after the Until condition is met to run one more time with the amount of objects remaining in the array. For example, if it had 435 items in it, it would run 4 times with 100 items each time and one last time with 35 items in it.

$someArray = "lots", "of", "stuff", "here"

if($someArray.count -gt 100){

$smallerCounter = $someArray.Count
$toSkip = 0

Do{

$smallerList = $someArray | Select-Object -Skip $toSkip -first 100

foreach($small in $smallerList){

Do Stuff

}

$toSkip += 100
$smallerCounter -= 100

} Until ($smallerCounter -lt 100)

$smallerList = $someArray | Select-Object -Skip $toSkip -first $smallerCounter

foreach($small in $smallerList){

Do Stuff

}

}

Updated VDA Install / Upgrade / RDS Install / Desktop / Server / App Install Script

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

Remove_Server.bat

"C:\Program Files\Citrix\XenDesktopVdaSetup\XenDesktopVdaSetup.exe" /REMOVEALL /QUIET /NOREBOOT
C:\Windows\System32\shutdown.exe /r /t 5 /f

Remove_Workstation.bat

"C:\Program Files\Citrix\XenDesktopVdaSetup\XenDesktopVdaSetup.exe" /REMOVEALL /QUIET /NOREBOOT
C:\Windows\System32\shutdown.exe /r /t 5 /f

Mode.reg

mode.reg
Windows Registry Editor Version 5.00
 
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM\Licensing Core]
"LicensingMode"=dword:00000004

Link to previous VDA / App Install article: VDA And App Install

Link to setup authentication profile for Citrix Cloud: Profile Setup

License Please! : Get Licensed Users From Citrix Cloud

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

Page 1 of 4

Powered by WordPress & Theme by Anders Norén