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

Category: PowerShell Page 2 of 5

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

Want To Update That HTML5 Workspace App? How About A Script! : Scripted Update HTML5 Workspace App

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

Check That .NET And Install It!

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


Script To Monitor Group Membership Changes And Report

Sometimes you do adds to a group for reasons. Maybe you are doing a staged migration of things and you are using a group to do that. Maybe someone wants a report of new users added in the last week. This can help do that for you. To complete this, you just need to run a command or two and generate your starting file to compare to. Then you setup a scheduled task to run on a weekly basis and there you have it! If you have had no changes, then it will not email you. If you do have changes, it will email with attachment and count of number of objects added. This also does this based on the named dates of the files so if you need to run it earlier, you will need to do a copy of the file and change the name to reflect a week earlier, or change the logic to not use the date names.

To get your starting file, you will need to create your folder to store the reports and run the command below in PowerShell with a machine that has the AD module loaded as part of the RSAT tools. You will also need the ScheduledTasks module loaded to create the scheduled tasks via PowerShell.

$outputLocation         = "C:\scripts\logs\GroupChanges"
$domain                 = "DomainOfGroup"
$groupName              = "GroupToMonitor"
$date                   = Get-Date
$dateReport             = ($date).ToString("MMddyyyy")
$outputFileName         = "$dateReport-group-members.csv"


$getCurrentGroupMembership = (Get-ADGroup -Server $domain $groupName | Get-ADGroupMember -Recursive | Select-Object -Unique | Sort | Select-Object SamAccountName)

$getCurrentGroupMembership | Export-CSV -Path $outputLocation\$outputFileName -Append -NoTypeInformation

Second verse, somewhat similar to the first.

# Script to do a weekly compare of Active Directory group memberships. Requires AD module to be loaded and an account / service account with read permissions to the objects.

$outputLocation         = "C:\scripts\logs\GroupChanges"
$domain                 = "DomainOfGroup"
$groupName              = "GroupToMonitor"
$date                   = Get-Date
$dateLastWeek           = ($date).AddDays(-7).ToString("MMddyyyy")
$dateReport             = ($date).ToString("MMddyyyy")
$outputFileName         = "$dateReport-group-members.csv"
$importFileNameLastWeek = "$dateLastWeek-group-members.csv"
$deltaFileName          = "$dateReport-delta.csv"
$report                 = @()

$getCurrentGroupMembership = (Get-ADGroup -Server $domain $groupName | Get-ADGroupMember -Recursive | Select-Object -Unique | Sort | Select-Object SamAccountName)

$getCurrentGroupMembership | Export-CSV -Path $outputLocation\$outputFileName -Append -NoTypeInformation

$inputFileNameLastWeek     = Import-Csv -Path "$outputLocation\$importFileNameLastWeek"

$objectCompare     = @{

  ReferenceObject  = ($getCurrentGroupMembership)
  DifferenceObject = ($inputFileNameLastWeek)

}

$deltaNames = (Compare-Object @objectCompare).InputObject

if($deltaNames -ne $null){

  $deltaNames | Export-Csv -Path $outputLocation\$deltaFileName -Append -NoTypeInformation
  $numberNewUsers = "New Users: " + ($deltaNames).Count

  $report += $deltaNames
  $report += $numberNewUsers
}

if($deltaNames -eq $null){

  Exit

}


$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          = "Weekly Group 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
$attachment       = "$outputLocation\$deltaFileName"
$email.Attachments.Add($attachment)

$smtpserver       = "mail.company.com" 
$smtp             = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($email)


And after you save that file, you will need to create the scheduled task. Make sure this is a machine that will be on and available during the time you want to run the script. I’m using “weeklychanges.ps1” as the example saved file name, running at 10am on Saturdays. Below will need to be ran from an elevated prompt / ISE in order to create the task without getting an “Access denied” error.

$taskName    = 'WeeklyGroupCheck'
$time        = "10am"
$user        = "domain\user"
$credentials = Get-Credential -Credential $user
$password    = $credentials.GetNetworkCredential().Password
$scriptPath  = "c:\scripts\weeklycheck.ps1"
$trigger     = New-ScheduledTaskTrigger -Weekly -At $time -DaysOfWeek Saturday
$action      = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -noprofile -file $scriptPath" 

Register-ScheduledTask -TaskName $taskName -Trigger $trigger -User $user -Password $password -Action $action -RunLevel Highest -Force

Getting Network Names Of VDI Machines

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

Sample Output

How Many Users Are In There? : Getting Group Membership Counts

So you want to know how many people are in the groups. As luck would have it, you can get that. There was an interesting thing that I encountered with Get-ADGroupMember when trying to return a count. If there were 0 members, it returned correctly. If there were 2 or more, it returned correctly. If there was 1 member, it returned nothing. It wasn’t null as I checked that. It just returned nothing. Found the answer on this site as to why it was doing it:Why it returned nothing. TLDR: From Martin9700, PowerShell, when only 1 object is returned it is returned AS that object. Count is property of an array (and you can have an array of pretty much any variable/object type)

So with that in mind, I went the route below to do a Measure-Object, then do the count. That returned the results I expected. I also wanted to only select the unique users in the group just in case there were nested groups that a user might have been in more than one of.

# Script to get user group counts. This requires the AD Powershell module, access rights to AD, a central location of Citrix groups, a naming convention, and used in Powershell ISE 5.1.
$domain          = "domain"
$searchBase      = "OU=CitrixGroups,OU=Groups,DC=somecompany,DC=com"
$getCtxGroups    = (Get-ADGroup -server $domain -SearchBase $searchBase -Filter {SamAccountName -like "CitrixGroupNamePattern*"} | Select-Object SamAccountName)
$totalItems      = ($getCtxGroups).Count
$date            = Get-Date -Format MMddyyyy
$report          = @()
$currentItem     = 0
$percentComplete = 0


ForEach($ctxGroup in $getCtxGroups){
  Write-Progress -Activity "Processing user count on ($ctxGroup).SamAccountName" -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
  $userCount      = (Get-ADGroup -Server $domain $ctxGroup.SamAccountName | Get-ADGroupMember -Recursive | Select-Object -Unique | Measure-Object).Count
  $line           = "" | Select GroupName, UserCount
  $line.GroupName = $ctxGroup.SamAccountName
  
  if($userCount -ne 0){
   
    $line.UserCount = $userCount
    
  }
 
  if($userCount -eq 0){
    $line.UserCount = "Empty"
  }
    
  $currentItem++
  $percentComplete = [int](($currentItem / $totalItems) * 100)
  
  $report += $line
}


$report | Export-Csv c:\scripts\logs\$date-ADUserGroup-Counts.csv -Append -NoTypeInformation

You Will Unblock Those Files! : Script To Unblock Powershell Files

So you’ve downloaded a cool new module. Your buddy gave you a module to test out. You copy it over and start looking to load it. You get the oh no! message. Your computer thinks you are being shady and blocks the file.

You have heard tale of an option to right-click EVERY single file and unblock the files.

All you want to do is unblock the files and load the module (at your own risk of course!). Well, then. Do it! Copy the module to your module directory in your path. Set the path to where it is. Iterate through those files, then load that module!

# Script to unblock files

$path = 'C:\Program Files\WindowsPowerShell\Modules\somemodule'
$files = Get-ChildItem -Path $path -Recurse | ForEach{$_.FullName}
$module = $files | Select-String -Pattern "psm1"

ForEach($file in $files){

  Unblock-File $file -Confirm:$false

}

Import-Module $module -Force

Changing HypervisorConnectionUid for 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
    
    }
 

What Do You Want? Information! : Getting Application Information And Exporting To CSV

You know you have a LOT of apps. But you want to know about 1 app in particular. You could go to Studio and look. You could peruse the various pieces and parts to and get what you want. Or…. you could just grab it via script. So that is what we gonna do here. We are going to present a grid view of the applications you have published, select one of them, and give you all the information you have room for! And, for an unlimited time offer, export to csv! This requires having setup your Citrix Cloud authentication and using your secureclient.csv to access.

Selection menu
Output from script
CSV output
# Get Citrix Application Info
asnp Citrix*
Get-XDAuthentication -ProfileName "default"

$application             = Get-BrokerApplication -MaxRecordCount 100000 | Select-Object ApplicationName, Enabled, AssociatedApplicationGroupUids, AllAssociatedDesktopGroupUids | Out-GridView -Title "Applications" -PassThru
$date                    = Get-Date -Format MMddyyyy
$a                       = 1
$d                       = 1

$app                     = $application.ApplicationName
$appGroupUids            = @($application.AssociatedApplicationGroupUids)
$deliveryGroupUids       = @($application.AllAssociatedDesktopGroupUids)
$appGroupUidsCounts      = ($appGroupUids).Count
$deliveryGroupUidsCounts = ($deliveryGroupUids).count
$ctxAppInfo              = [PSCustomObject]@{}
$ctxAppInfo | Add-Member -NotePropertyName "ApplicationName" -NotePropertyValue ($application).ApplicationName -Force
$ctxAppInfo | Add-Member -NotePropertyName "Enabled" -NotePropertyValue ($application).Enabled -Force

if($appGroupUidsCounts -gt 0){

  foreach($appGroups in $appGroupUids){
    
    $applicationGroupInfo    = (Get-BrokerApplicationGroup -Uid $appGroups)
    $applicationGroupNames   = ($applicationGroupInfo).ApplicationGroupName
    $ctxAppInfo | Add-Member -NotePropertyName "ApplicationGroups-$a" -NotePropertyValue $applicationGroupNames -Force

    if($applicationGroupInfo.AssociatedUserNames -ne $null){
      
      $applicationGroupUsers = ($applicationGroupInfo).AssociatedUserNames -join ';'
      $ctxAppInfo | Add-Member -NotePropertyName "ApplicationGroupUsers-$a" -NotePropertyValue $applicationGroupUsers -Force
      
    }
    
    $a++
  } 
}

if($deliveryGroupUidsCounts -gt 0){
  
  foreach($deliveryGroup in $deliveryGroupUids){
    
    $deliveryGroupInfo  = Get-BrokerDesktopGroup -Uid $deliveryGroup
    $deliveryGroupNames = $deliveryGroupInfo.PublishedName
    $deliveryGroupUsers = (Get-BrokerAccessPolicyRule -DesktopGroupName "$deliveryGroupNames")
    $dgUserCheck        = ($deliveryGroupUsers).AllowedUsers
    $ctxAppInfo | Add-Member -NotePropertyName "DeliveryGroupNames-$d" -NotePropertyValue $deliveryGroupNames -Force
    
    if($dgUserCheck -eq "Filtered"){
    
      $deliveryGroupUsers  = ($deliveryGroupUsers).IncludedUsers.Name -join ';'
      $ctxAppInfo | Add-Member -NotePropertyName "DeliveryGroupUsers-$d" -NotePropertyValue $deliveryGroupUsers -Force
    }
    if($dgUserCheck -eq "AnyAuthenticated"){
      $ctxAppInfo | Add-Member -NotePropertyName "DeliveryGroupUsers-$d" -NotePropertyValue "AnyAuthenticated" -Force
    }
    
    $deliveryGroupMachines        = Get-BrokerMachine -MaxRecordCount 100000 | Where-Object DesktopGroupName -eq "$deliveryGroupNames"
    $deliveryGroupMachineNames    = ($deliveryGroupMachines | Select-Object MachineName).MachineName -join ';'
    $ctxAppInfo | Add-Member -NotePropertyName "DeliveryGroupMachines-$d" -NotePropertyValue $deliveryGroupMachineNames -Force
    
    
    $d++
  }

 
}

$ctxAppInfo | Export-Csv C:\scripts\logs\$date-$app-App-Info.csv -Append -NoTypeInformation

Moving Control Plane To Cloud: Migrating Citrix Daily User Report

Third in the series of moving the control plane to Citrix Cloud…. So you had your daily user report kicking out everyday (Surely you created one from this other post: https://xenapplepie.com/2022/04/12/if-you-could-get-those-user-counts-today-that-would-be-great/). It was working its happy way through life. Then you just moved parts it talked to into the cloud. I have created this updated report script to allow for it to pull from Citrix Cloud. This requires that you have already setup your API access with the secureclient.csv, that you added the CustomerID to your secureclient.csv, and you have installed the Citrix Cloud SDK. If you don’t have those, you are gonna have a bad day. I left the comment for the #Get Licensing Info so you can see what all other fields you can get if needed from there. If you are using VS Code, when you run that section, you can create a new variable and assign it as “$content.” and it will show the other available pieces of information you can assign such as “deviceLicenseUsage.”

**Update: Removed line with Get-XDAuthentication as it is doing a double authentication. Changed SDK commands to use $headers.Authorization to pass same bearer token**

Example Of Autocomplete From VS Code For Licensing
Sample Output From Script Email
# Citrix Daily Report with updates for using Citrix Cloud. This was done in Powershell ISE 5.1 with Citrix Cloud SDK installed.

asnp Citrix*

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

$creds          = import-csv "c:\scripts\logs\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
 }

# Get Licensing Info
$response            = Invoke-WebRequest "https://api-us.cloud.com/licensing/license/enterprise/cloud/cvad/ud/current" -Method Get -Headers $headers
$content             = $response.Content | ConvertFrom-Json
$response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 10
$licensingTotalCount = $content.totalAvailableLicenseCount
$licensingUsageCount = $content.totalUsageCount
$licensingRemaining  = $content.remainingLicenseCount

$connections = Get-BrokerConnectionLog -BearerToken $headers.Authorization  -Filter {BrokeringTime -gt $when} -MaxRecordCount 100000 | Select-Object BrokeringUserName

$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  = ((($licensingUsageCount) / $licensingTotalCount ) * 100).ToString("#.##")

}

# 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>"

$body = $ctxUsers | ConvertTo-Html -Head $style 

$date             = Get-Date -Format "MM-dd-yyyy"
$emailFrom        = "someemail@company.com"
$emailto          = "someemail@company.com"
$emailtwo         = "someemail@company.com"
$emailCC          = "someemail@company.com"
$subject          = "Daily Citrix User Report | $date" 
$email            = New-object System.Net.Mail.MailMessage 
$email.to.Add($emailto)
$email.to.Add($emailtwo)
$email.CC.Add($emailCC)
$Email.From       = New-Object system.net.Mail.MailAddress $emailFrom
$email.Subject    = $subject
$email.IsBodyHtml = $true
$email.body       = $body
$smtpserver       = "smtp.company.com" 
$smtp             = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($email)

Links to other articles in the series:

Part 1 Of Cloud Migration Series: Part 1

Part 2 Of Cloud Migration Series: Part 2

Part 4 Of Cloud Migration Series: Part 4

Page 2 of 5

Powered by WordPress & Theme by Anders Norén