Occasionally, we have Citrix servers that ‘die’ in a peculiar way. What happens may vary when they die but the usual symptoms are something like:
- The server is still somewhat responsive. It responds to pings, RPC requests (tasklist /s %servername%)
- The server is not responsive. You cannot RDP to it, console CTRL-ALT-DEL fails, etc.
This is frustrating because the services appear to be operating so the Citrix server will say, “hey, I’m working! I can take sessions!” And usually these servers won’t have any sessions because logons actually fail so their “XenApp Server Load” is low, so its priority for sessions to be directed to it is higher! So how do we detect these servers with these issues? Unfortunately, I haven’t seen any events in the Event Viewer or anything that stands out to search and find these troublesome servers. Using ControlUp, sometimes it’s obvious because that troublesome server will have a much lower session count than other servers or something else is at fault and triggers the ‘Stress Level’ to go critical. But these flags don’t usually exist if the problem has just occurred, they usually are more visible after time has passed.
Our helpdesk asked if there was a way they could test servers to help pinpoint a troublesome one. I came up with a “Script-based Action” that targets a specific Citrix server and lists all published applications on that server. You then select the application and it generates a ICA file and tries to launch it. You need to have permission to the application on Citrix and Powershell remoting enabled on the XenApp servers/ZDC’s . So if your a Citrix admin and PS Remoting is enabled this script will work out of the box.
However, I tried to make the script dynamic so you could query the XenApp servers from a standalone server without installing Citrix Powershell SDK locally. To do this I use PowerShell remoting so you need to have PowerShell remoting enabled on your Citrix servers in your environment. Secondly, if you have ‘lower’ privilege users you need to grant them the ability to connect to the servers via PowerShell remoting (by default only Administrators have access). To grant them access you need to do the following:
1 2 3 |
Set-PSSessionConfiguration -Name Microsoft.PowerShell32 -showSecurityDescriptorUI -Confirm:$false Set-PSSessionConfiguration -Name Microsoft.PowerShell -showSecurityDescriptorUI -Confirm:$false Restart-service -Name "WinRM" |
And in the ‘Set-PSSessionConfiguration’ command you need to enable the ‘Invoke’ permissions on your support group:
As well, you need to grant view properties on your Citrix farm since the group needs to query application properties, and workergroups (if you publish your applications to workergroups):
Now that we have our permissions configured we can create our ControlUp Script-Based action:
So what does this look like?
And the script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
<# .SYNOPSIS Connects to Citrix XenApp 6.5 server and finds all applications published for that server. The script then displays a GUI with the list and presents you a choice to launch your application. Requires you to have rights to query XenApp for the published applications list, zones and you must have rights to launch the application. .PARAMETER serverNameToFind The name of the server you selected. .UPDATED 2016-08-27 - Allowed for caching of multiple different farms - Tested with XenApp 5.0 successfully with XenApp Commands CTP4 and PSRemoting enabled on 2003. #> #grab the server we are looking for from the powershell arguments $serverNameToFind = $args[0] $ErrorActionPreference = "Stop" #Remote onto the server targted and pull its ZDC from there... The theory is the local ZDC for the #server will pull the app list faster than a remote ZDC... $ReturnXAZDC = $null write-host "Getting Farm Name..." $ReturnXAFarm = Invoke-Command -ComputerName $serverNameToFind -ScriptBlock { Add-PsSnapin Citrix.XenApp.Commands #Get Zones Try { $XAFarmName = (get-xafarm).FarmName } Catch { # capture any failure and display it in the error section, then end the script with a return # code of 1 so that CU sees that it was not successful. Write-Error $Error[0] -ErrorAction Continue Exit 1 } return $XAFarmName } -Args $serverNameToFind write-host "Connecting to server remotely to get its local Zone Data Collector..." $ReturnXAZDC = Invoke-Command -ComputerName $serverNameToFind -ScriptBlock { Add-PsSnapin Citrix.XenApp.Commands #Get server information $serverObject = (get-xaserver -servername $args[0]).ZoneName #Get Zones Try { $XAZone = get-xaZone } Catch { # capture any failure and display it in the error section, then end the script with a return # code of 1 so that CU sees that it was not successful. Write-Error $Error[0] -ErrorAction Continue Exit 1 } $ZDC = $null foreach ($Zone in get-xazone) { if ($serverObject -eq $Zone.ZoneName) { $ZDC = $zone.DataCollector } } return $ZDC } -Args $serverNameToFind write-host "Remote server reported back its local ZDC: $ReturnXAZDC" if (-not($ReturnXAZDC -eq $null)) { $ZDC = $ReturnXAZDC } else { Write-Error "Unable to determine ZDC remotely. ZDC detected as: $ZDC" -ErrorAction Continue Write-Error $Error[1] -ErrorAction Continue Exit 1 } #Ensure Citrix Receiver is installed so we can launch our custom ICA file. $icaLauncher = $null if (Test-Path "${env:ProgramFiles(x86)}\Citrix\ICA Client\wfcrun32.exe") { $icaLauncher = "${env:ProgramFiles(x86)}\Citrix\ICA Client\wfcrun32.exe" } if (Test-Path "${env:ProgramFiles}\Citrix\ICA Client\wfcrun32.exe") { $icaLauncher = "${env:ProgramFiles}\Citrix\ICA Client\wfcrun32.exe" } if ($icaLauncher -eq $null) { write-warning "Citrix Receiver not installed or detected." EXIT 1 } $allApps = $null #see if there is a recent cached appList and use that instead... if (test-path "$env:temp\$ReturnXAFarm.xml") { write-host "Found a previously cached app list." if (Test-Path "$env:temp\$ReturnXAFarm.xml" -OlderThan (Get-Date).AddDays(-1)) { write-host "Cache is expired." remove-item "$env:temp\$ReturnXAFarm.xml" } else { $allApps = import-clixml "$env:temp\$ReturnXAFarm.xml" } } #this next process may take a while (45seconds in my testing) if you have lots of applications/servers/etc. If a cached #copy of the app list is present we skip this process. We connect to the ZDC as a new PS-Session as invoke-command #appears to have a size limit or timeout that the "Get-XAApplicationReport" will fail on if the list is too large. #New-PSSession appears to handle this without issue. if (-not(test-path "$env:temp\$ReturnXAFarm.xml")) { Write-Host "Getting a list of applications and all servers they are published on..." Try { $SessionCitrix = New-PSSession -ComputerName $ReturnXAZDC $allApps = Invoke-Command -session $SessionCitrix -ScriptBlock { Add-PsSnapin Citrix.XenApp.Commands #Get server information $allApps = Get-XAApplicationReport -BrowserName * | select DisplayName,Enabled,Accounts,FolderPath,@{N='ServerNames';E={$_.serverNames+($_|?{$_.WorkerGroupNames}|%{Get-XAWorkerGroup $_.WorkerGroupNames}).ServerNames | sort}} $allAppsWithoutDisabled = @() foreach ($app in $allApps) { if ($app.Enabled -eq $true) { $allAppsWithoutDisabled = $allAppsWithoutDisabled += $app } } return $allAppsWithoutDisabled } # Close Session Remove-PSSession $SessionCitrix } Catch { # capture any failure and display it in the error section, then end the script with a return # code of 1 so that CU sees that it was not successful. Write-Error $Error[0] -ErrorAction Continue Exit 1 } } if (-not($allApps -eq $null)) { #we cache the application list in the %temp% in case they do subsequent launches $allApps | export-clixml "$env:temp\$ReturnXAFarm.xml" } else { Write-Error "No Applications found on the server or an error getting application list" Exit 1 } #get the group membership of this user $groupMembership = ([ADSISEARCHER]"samaccountname=$($env:USERNAME)").Findone().Properties.memberof -replace '^CN=([^,]+).+$','$1' | sort #remove apps from the list that the user doesn't have access too $allAppsIHaveAccessTo = @() foreach ($app in $allApps) { foreach ($membership in $groupMembership) { if ($app.Accounts -like "*$membership*") { $allAppsIHaveAccessTo = $allAppsIHaveAccessTo += $app break } } } $appListOnServer = @() foreach ($app in $allAppsIHaveAccessTo) { if ($app.ServerNames -like $serverNameToFind) {$appListOnServer += $app.DisplayName}} $appListOnServer = $appListOnServer | sort #blank the selected combobox variable $global:SelObj = $null Function Select-GUIObject ($ObjName, $Object) { #Generated Form Function function GenerateForm { ######################################################################## # Code Generated By: SAPIEN Technologies PrimalForms (Community Edition) v1.0.7.0 # Generated By: Alan Renouf (http://virtu-al.net) ######################################################################## #region Import the Assemblies [reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null [reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null #endregion #region Generated Form Objects $form1 = New-Object System.Windows.Forms.Form #ensure it starts out on-top $form1.TopMost = $true $button1 = New-Object System.Windows.Forms.Button $label1 = New-Object System.Windows.Forms.Label $comboBox1 = New-Object System.Windows.Forms.ComboBox $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState #endregion Generated Form Objects #---------------------------------------------- #Generated Event Script Blocks #---------------------------------------------- #Provide Custom Code for events specified in PrimalForms. $button1_OnClick= { $global:SelObj = $comboBox1.SelectedItem $form1.close() } $handler_label2_Click= { } $OnLoadForm_StateCorrection= {#Correct the initial state of the form to prevent the .Net maximized form issue $form1.WindowState = $InitialFormWindowState $Object | Foreach { $comboBox1.items.add($_) $comboBox1.SelectedIndex=0 } $Combobox1.visible = $true $label1.visible = $true $button1.visible = $true $form1.Text = "Please select a $ObjName" } #---------------------------------------------- #region Generated Form Code $form1.AutoScaleMode = 0 $form1.Text = "Please wait loading $ObjName...." $form1.Name = "form1" $form1.DataBindings.DefaultDataSourceUpdateMode = 0 $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Height = 66 $System_Drawing_Size.Width = 392 $form1.ClientSize = $System_Drawing_Size $form1.FormBorderStyle = 1 $form1.Controls.Add($InfoLabel) $button1.TabIndex = 2 $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Height = 23 $System_Drawing_Size.Width = 75 $button1.Size = $System_Drawing_Size $button1.Name = "button1" $button1.UseVisualStyleBackColor = $True $button1.Visible = $False $button1.Text = "OK" $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 300 $System_Drawing_Point.Y = 21 $button1.Location = $System_Drawing_Point $button1.DataBindings.DefaultDataSourceUpdateMode = 0 $button1.add_Click($button1_OnClick) $form1.Controls.Add($button1) $label1.TabIndex = 1 $label1.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",8.25,1,3,1) $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Height = 23 $System_Drawing_Size.Width = 50 $label1.Size = $System_Drawing_Size $label1.Name = "label1" $label1.Visible = $False $label1.Text = "$ObjName" $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 8 $System_Drawing_Point.Y = 26 $label1.Location = $System_Drawing_Point $label1.DataBindings.DefaultDataSourceUpdateMode = 0 $form1.Controls.Add($label1) $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 64 $System_Drawing_Point.Y = 21 $comboBox1.Location = $System_Drawing_Point $comboBox1.Visible = $False $comboBox1.DataBindings.DefaultDataSourceUpdateMode = 0 $comboBox1.FormattingEnabled = $True $comboBox1.Name = "comboBox1" $comboBox1.TabIndex = 0 $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Height = 21 $System_Drawing_Size.Width = 217 $comboBox1.Size = $System_Drawing_Size $form1.Controls.Add($comboBox1) #endregion Generated Form Code #Save the initial state of the form $InitialFormWindowState = $form1.WindowState #Init the OnLoad event to correct the initial state of the form $form1.add_Load($OnLoadForm_StateCorrection) #Show the Form) $form1.ShowDialog($this)| Out-Null } #End Function #Call the Function GenerateForm } Select-GUIObject "App" ($appListOnServer) if ($selObj -eq $null) { Write-error "No App Selected" EXIT 1 } else { write-host "Selected App: $selObj" write-host "Creating ICA file" $icaContent = "[Encoding]`n" $icaContent += "InputEncoding = ISO8859_1`n" $icaContent += "[WFClient]`n" $icaContent += "Version=2`n" $icaContent += "EnableSSOnThruICAFile=On`n" $icaContent += "ProxyType=Auto`n" $icaContent += "HttpBrowserAddress={0}:28001`n" -f $ZDC,$IMAPort $icaContent += "ConnectionBar=0`n" $icaContent += "`n" $icaContent += "[ApplicationServers]`n" $icaContent += "{0}=`n" -f $SelObj $icaContent += "`n" $icaContent += "[{0}]`n" -f $SelObj $icaContent += "Address={0}`n" -f $serverNameToFind $icaContent += "InitialProgram=#{0}`n" -f $SelObj $icaContent += "CGPAddress=*:2598`n" $icaContent += "ClientAudio=On`n" $icaContent += "DesiredColor=8`n" $icaContent += "TWIMode = True`n" $icaContent += "KeyboardTimer = 0`n" $icaContent += "MouseTimer = 0`n" $icaContent += "ConnectionBar=0`n" $icaContent += "EnableSSOnThruICAFile=On `n" $icaContent += "UseLocalUserAndPassword=On `n" $icaContent += "TransportDriver=TCP/IP`n" $icaContent += "WinStationDriver=ICA 3.0`n" $icaContent += "BrowserProtocol=HTTPonTCP`n" $icaContent += "Compress=On`n" $icaContent += "EncryptionLevelSession=Encrypt`n" $icaContent += "[Encrypt]`n" $icaContent += "DriverNameWin32=PDCRYPTN.DLL`n" $icaContent += "DriverNameWin16=PDCRYPTW.DLL`n" $icaContent += "[Compress]`n" $icaContent += "DriverName=PDCOMP.DLL`n" $icaContent += "DriverNameWin16=PDCOMPW.DLL`n" $icaContent += "DriverNameWin32=PDCOMPN.DLL`n" $icaContent | out-file -Encoding ASCII $env:TEMP\launcher.ica -Force } write-host "Launching Citrix Receiver" try { & "$icaLauncher" "$env:TEMP\launcher.ica" } Catch { Write-Error "Unable to launch ICA file." Exit 1 } |