-
New Script: Get Static Desktop User
March 11, 2019
Citrix Virtual Apps and Desktops, PowerShell, XenApp/XenDesktop 7.0 - 7.7
In March 2019, I was honored to present at the first CUGC Texas XL event held at the beautiful University of Texas Club. My presentation was on PowerShell Scripting with Webster. This article shows what I talked about in the second half of my presentation.
The week before the CUGC event, I did a Citrix App Layering project for a customer in Dallas. While waiting for the first published image to build, the customer asked if I could help them with a PowerShell script to get the users for a static desktop delivery group.
The customer’s requirements were:
- Use a specific Delivery Group
- Use only Assigned desktops
- Use last connected user’s name
- Output to text file
To accomplish this, we used the following process:
- Get-BrokerMachine -DesktopGroupName
- -Filter {IsAssigned -eq $True}
- Select LastConnectionUser
- Out-File StaticUsers.txt
To determine the properties needed, we ran:
Get-BrokerMachine -DesktopGroupName "The desktop group name"
as shown in Figure 1.
That showed us the values for IsAssigned and LastConnectionUser for an assigned desktop. The data for an unassigned desktop is shown in Figure 2.
In Webster’s Lab (since I can’t show you live customer data), I built three machine catalogs and delivery groups. One for random desktops, one for static applications, and one for static desktops. For the static desktops, I logged into three of the five desktops, as shown in Figure 3.
Next, we created a one-liner PowerShell “script”.
Note: The one-liner is shown on separate lines so the PowerShell code could be read on the PowerPoint slide.
Note 2: Use Get-BrokerMachine instead of Get-BrokerDesktop as Citrix deprecated Get-BrokerDesktop.
Get-BrokerMachine -DesktopGroupName "Texas CUGC XL"` -Filter {IsAssigned -eq $True} | ` Select LastConnectedUser | ` Out-File c:\webster\StaticUsers.txt
The output text file and data from Studio are shown in Figure 4.
This proves the one-liner produces accurate data.
How can we turn this one-liner with hard-coded values and assumptions into a generic script that is usable by anyone?
What is wrong with our one-liner? After all, it works and produces accurate data.
The main fault is it violates a major rule of scripting and programming: Make no assumptions.
- What happens if I don’t run on a delivery controller?
- What happens if the Delivery Group name is fat-fingered?
- What if I want to process all static desktop Delivery Groups?
- What happens if the output folder doesn’t exist?
- What if I want to specify a specific output folder?
- What happens if the Citrix PSSnapins is not loaded?
- What if the desktops are not statically assigned?
- What happens if there is no data?
Let’s turn this one-liner into a generic script.
We can fix three of the assumptions by using script parameters.
- Not running on the delivery controller: -AdminAddress
- Specify a specific delivery group or all delivery groups: -DeliveryGroupName
- Specify an output folder: -Folder
Since I have many scripts available at https://www.carlwebster.com/Downloads, I have a lot of reusable code at my disposal.
The three parameters in the help text are shown below.
.PARAMETER AdminAddress Specifies the address of a XenDesktop controller where the PowerShell snapins will connect. This can be provided as a hostname or an IP address. This parameter defaults to Localhost. This parameter has an alias of AA. .PARAMETER DeliveryGroupName By default, the script processes all Delivery Groups that deliver Static desktops. This is where the properties DeliveryType equals DesktopsOnly and DesktopKind equals Private. Use this parameter to specify a single Delivery Group. This parameter defaults to all Static Desktop Delivery Groups. This parameter has an alias of DGN. .PARAMETER Folder Specifies the optional output folder to save the output report.
You may notice the line for the parameter DeliveryGroupName: “This is where the properties DeliveryType equals DesktopsOnly and DesktopKind
equals Private.”How did I know that? I cheated. I looked at all the properties for the three delivery groups created and found what I needed as shown in Figure 5.
Here is the parameter block.
[CmdletBinding(SupportsShouldProcess = $False, ConfirmImpact = "None", DefaultParameterSetName = "") ] Param( [parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [Alias("AA")] [string]$AdminAddress="Localhost", [parameter(Mandatory=$False)] [Alias("DGN")] [string]$DeliveryGroupName="", [parameter(Mandatory=$False)] [string]$Folder="" )
Copying from the other Citrix related scripts, to check for the required Citrix PowerShell snapins.
Here is the function.
Function Check-NeededPSSnapins { Param([parameter(Mandatory = $True)][alias("Snapin")][string[]]$Snapins) #Function specifics $MissingSnapins = @() [bool]$FoundMissingSnapin = $False $LoadedSnapins = @() $RegisteredSnapins = @() #Creates arrays of strings, rather than objects, we're passing strings so this will be more robust. $loadedSnapins += Get-PSSnapin | ForEach-Object {$_.name} $registeredSnapins += Get-PSSnapin -Registered | ForEach-Object {$_.name} ForEach($Snapin in $Snapins) { #check if the snapin is loaded If(!($LoadedSnapins -like $snapin)) { #Check if the snapin is missing If(!($RegisteredSnapins -like $Snapin)) { #set the flag if it's not already If(!($FoundMissingSnapin)) { $FoundMissingSnapin = $True } #add the entry to the list $MissingSnapins += $Snapin } Else { #Snapin is registered, but not loaded, loading it now: Add-PSSnapin -Name $snapin -EA 0 *>$Null } } } If($FoundMissingSnapin) { Write-Warning "Missing Windows PowerShell snap-ins Detected:" $missingSnapins | ForEach-Object {Write-Warning "($_)"} Return $False } Else { Return $True } }
And the call to the function.
#check for required Citrix snapin If(!(Check-NeededPSSnapins "Citrix.Broker.Admin.V2")) { #We're missing Citrix Snapins that we need $ErrorActionPreference = $SaveEAPreference Write-Error "`nMissing Citrix PowerShell Snap-ins Detected, check the console above for more information. `nAre you sure you are running this script against a XenDesktop 7.0 or later Delivery Controller? `n`nIf you are running the script remotely, did you install Studio or the PowerShell snapins on $($env:computername)? `n`nScript will now close." Exit }
Fortunately, only one snapin is required for this script.
This would be much easier if Citrix would stop using the deprecated snapins and change their PowerShell to use modules. When will this happen? The Cleveland Browns, followed by the Detroit Lions, followed by the Houston Texans, and finally followed by the Jacksonville Jaguars will each have a perfect NFL season and win the Super Bowl before Citrix ever fixes their PowerShell.
The -AdminAddress parameter is used for all the Citrix Broker related PowerShell cmdlets. You will see this in the following Figures.
The -Folder parameter is used in a majority of my scripts, so this is again a copy and paste.
If($Folder -ne "") { Write-Verbose "$(Get-Date): Testing folder path" #does it exist If(Test-Path $Folder -EA 0) { #it exists, now check to see if it is a folder and not a file If(Test-Path $Folder -pathType Container -EA 0) { #it exists and it is a folder Write-Verbose "$(Get-Date): Folder path $Folder exists and is a folder" } Else { #it exists but it is a file not a folder Write-Error "Folder $Folder is a file, not a folder. Script cannot continue" Exit } } Else { #does not exist Write-Error "Folder $Folder does not exist. Script cannot continue" Exit } } ElseIf($Folder -eq "") { $pwdpath = $pwd.Path } Else { $pwdpath = $Folder } If($pwdpath.EndsWith("\")) { #remove the trailing \ $pwdpath = $pwdpath.SubString(0, ($pwdpath.Length - 1)) }
Did the user enter a Folder?
Test to see if the folder exists.
If it exists, is it a folder and not a file?
If a folder, the script can continue.
If a file, the script exits.
If it does not exist, the script exits.
If the user did not enter a Folder, set the variable $pwdpath to the value contained in $pwd.path.
Note: $pwd is an alias for Get-Location as shown in Figure 6.
If the user did enter a Folder, set the variable $pwdpath to the value of Folder.
If $pwdpath ends with a trailing backslash, “\”, remove it.
For -DeliveryGroupName, check if a value was entered. If a value was not entered, process all Delivery Groups where the properties DeliveryType and DesktopKind meet the criteria for Static Desktops.
If a value was entered, check if the specified Delivery Group exists and also contains Static Desktops. Do not assume the Delivery Group name entered exists or contains Static Desktops.
If($DeliveryGroupName -eq "") { $DeliveryGroups = Get-BrokerDesktopGroup -AdminAddress $AdminAddress ` -filter {DeliveryType -eq "DesktopsOnly" -and DesktopKind -eq "Private"} -EA 0 If(!$?) { Write-Error "Unable to retrieve Delivery Groups that deliver Static Desktops. `n`nScript will now close." Exit } ElseIf($? -and $Null -eq $DeliveryGroups) { Write-Error "No Delivery Groups were found that deliver Static Desktops. `n`nScript will now close." Exit } } Else { $DeliveryGroups = Get-BrokerDesktopGroup -AdminAddress $AdminAddress ` -filter {DeliveryType -eq "DesktopsOnly" -and DesktopKind -eq "Private"} ` -Name $DeliveryGroupName -EA 0 If(!$?) { Write-Error "Unable to retrieve Delivery Group $DeliveryGroupName. `n`nScript will now close." Exit } ElseIf($? -and $Null -eq $DeliveryGroups) { Write-Error "Delivery Group $DeliveryGroupName has no Static Desktops. `n`nScript will now close." Exit } }
Once the Delivery Group data is retrieved, process each Delivery Group searching for assigned desktops. Do not use Get-BrokerDesktop, as that cmdlet is deprecated. Citrix recommends using Get-BrokerMachine.
Once all assigned desktops are retrieved for a Delivery Group, check if the LastConnectionUser property is not Null.
I tried adding that test to the -Filter condition for Get-BrokerMachine, but that always resulted in no desktops returned.
Once all desktops, for the Delivery Group, are processed, sort the $UserNames array, remove all duplicates and output the array data to a text file.
Using -Encoding ascii removes the property name and the row of dashes (seen in Figure 4) from the output file as shown in Figure 8.
ForEach($DG in $DeliveryGroups) { Write-Verbose "$(Get-Date): `tRetrieving static desktops for Delivery Group $($DG.name)" $Machines = Get-BrokerMachine -Filter {IsAssigned -eq $True} -DesktopGroupName $DG.Name ` -EA 0 | Sort-Object DNSName If(!$?) { Write-Warning "`t`tNo desktops were found for Delivery Group $($DG.Name)" } Else { $UserNames = New-Object System.Collections.ArrayList ForEach($Machine in $Machines) { Write-Verbose "$(Get-Date): `t`tProcesing desktop $($Machine.DNSName)" If($null -ne $Machine.LastConnectionUser) { $UserNames.Add($Machine.LastConnectionUser) > $Null } } #sort users and remove duplicates $UserNames = $UserNames | Sort-Object -Unique $OutputFile = "$($pwdpath)$($DG.Name) Static Users.txt" Write-Verbose "$(Get-Date): Output text file $OutputFile" Out-File -FilePath $OutputFile -Encoding ascii -InputObject $UserNames 4>$Null } }
An example of the script running is shown in Figure 7.
An example of the output file is shown in Figure 8.
I want to thank Matt Lovett for the request for a script that I was able to turn into a generic script. Matt’s request is a good example of taking a one-liner for a specific request and then turn that one-liner into a full generic script for the community to use.
I wrote this script for the CUGC Texas XL event and the attendees allowed me to take a panoramic picture, Figure 9, to use for this article.
Thanks to the Austin, Dallas, and Houston CUGC leaders for inviting me to speak. Thanks to the attendees for being there and listening to my presentation and for allowing me to take a wide group photo.
The script download went live the morning of the event on March 5th.
You can always find the most current script by going to https://www.carlwebster.com/where-to-get-copies-of-the-documentation-scripts/
If you would like me to present at your CUGC or CUGC XL event, contact the CUGC team and maybe I will create a script for your event and get rid of some of the backlog I have for script ideas.
Thanks
Webster
2 Responses to “New Script: Get Static Desktop User”
Leave a Reply
July 6, 2021 at 10:02 am
Hi Carl,
As always, sincerely appreciate your knowledge sharing with community.
I am not sure, if you ever got below kind of request:
Script to find below:
– VDAWorkstaionsetup.exe which are providing RemotePC (Like mainly physical pc) We have it in different MC and DG obviously
– VDAWorkstationsetup.exe which are providing XenDesktop (persistent and non-persistent, static or random)
-VDAServersetup.exe providing Xen App(Published App, Published hosted shared Desktop)
Like out of 3000 vda in Citrix Site, need to get:
500 unregistered hostname (Physical pc hostnames, xendesktop hostnames, xenapp server hostnames)
1000 = RemotePC hostname
500= XenDesktop hostname
1000=XenApp Server hostnames
With multiple sites, some time it is very hard to get full scenario with numbers on hand and this will help for daily management.
Thanks in advance
Piyush
July 6, 2021 at 1:37 pm
See if this script from Super Duper Genius Guy Leech (a friend and fellow CTP) helps.
XenApp/XenDesktop 7.x Availability & Health Summary Script
Thanks
Webster