Sunday, November 3, 2024

StoreFront PowerShell Documentation - Version 5.0

I am pleased to announce the release of version 5.0 of my StoreFront PowerShell documentation script. It now supports the latest StoreFront version (version 2407) as of this writing.


The script can create documentation of your StoreFront group in MS Word, HTML, and/or text formats, and MS Word does not need to be installed on your PC. You can optionally include documentation of hardware information of your StoreFront server, as well as the list of installed Citrix software.

No installation is required. Simply unZIP the below into any directory, fire up an elevated PowerShell session, and run the script. Full details may be found in the Read Me file.

StoreFront PowerShell Documentation - Version 5.0

Sam Jacobs was the Director of Technology at Newtek Technology Solutions (formerly IPM, the longest standing Citrix Platinum Partner on the East Coast). With more than 40 years of IT consulting, Sam is a Citrix NetScaler, StoreFront, and Web Interface customization and integration expert. He holds Microsoft Azure Developer and Citrix CCP-N certifications, and was a frequent contributor to the CUGC CTP blog. He has presented advanced breakout sessions at Citrix Synergy for 6 years on ADC (NetScaler) and StoreFront customizations and integration. He is one of the top Citrix Support Forum contributors, and has earned industry praise for the tools he has developed to make NetScaler, StoreFront, and Web Interface easier to manage for administrators and more intuitive for end users. Sam became a Citrix Technology Professional (CTP) in 2015, and can be reached at sjacobsCTP@gmail.com.

Sunday, October 13, 2024

Citrix Workspace App for Windows 2409 Preview

It's here! Citrix Workspace App for Windows 2409 Preview has arrived, and it has loads of new features! Just a few of the many added features are:

A better user experience when launching applications,

Smoother resizing, 

Customization of the Desktop Viewer toolbar, and

Remembering USB connections.

But my absolute favorite enhancement is the Connectivity Strength Indicator (CSI). It gives the user clear insights and actionable advice straight from the Citrix Workspace app. If a call to Citrix Support is warranted, the CSI saves time by providing key metrics that can be relayed to the tech support rep.

In January of last year, I released a 3-part blog post series on setting user expectations by adding a QoS (Quality of Service) indicator to the NetScaler:


Setting User Expectations - Part 1

Setting User Expectations - Part 2

Setting User Expectations - Part 3

The advantage to placing the QoS indicator on the NetScaler logon page is that the user is informed of a degraded connection even before logging onto the gateway, while the CSI doesn't come into play until after a Citrix session is started.

However, the CSI gives the user way more information as well as suggestions for the user to try before calling for support.

  

Of course, another advantage is that the CSI does not require any changes to NetScaler or StoreFront source code and is therefore fully supported by Citrix Support. 😀

Sam Jacobs was the Director of Technology at Newtek Technology Solutions (formerly IPM, the longest standing Citrix Platinum Partner on the East Coast). With more than 40 years of IT consulting, Sam is a Citrix NetScaler, StoreFront, and Web Interface customization and integration expert. He holds Microsoft Azure Developer and Citrix CCP-N certifications, and was a frequent contributor to the CUGC CTP blog. He has presented advanced breakout sessions at Citrix Synergy for 6 years on ADC (NetScaler) and StoreFront customizations and integration. He is one of the top Citrix Support Forum contributors, and has earned industry praise for the tools he has developed to make NetScaler, StoreFront, and Web Interface easier to manage for administrators and more intuitive for end users. Sam became a Citrix Technology Professional (CTP) in 2015, and can be reached at sjacobsCTP@gmail.com.

Sunday, September 15, 2024

Customizing the look of the on-premises NetScaler Gateway logoff page

Citrix Workspace, as well as the Citrix Cloud authentication page, has a new minimalist look that's clean and uncluttered:



Citrix has published a KB article Customizing the on-premises Citrix Gateway authentication page to look identical to Citrix Cloud logon page which will convert a gateway logon page using the RFWebUI theme to match the above:
 



There is one small problem. While the code does a nice job of modifying the gateway logon page, it does not also modify the gateway logout page, which uses the default theme:


This post will show you how to modify the logout page with the same look and feel as the logon page.

 IMPORTANT: This change will affect all gateway vServers on the NetScaler.

Steps:

Backup the file /var/netscaler/gui/vpn/logout.html.

Replace logout.html with the file located here.

After replacing the file, the logout page should now look like this:

A few items to note:
- Citrix Support will not work on any NetScaler page that has had its source code modified. Make sure that you always keep a copy of the original source code handy.
- The code above does not have localization built into it. You would need to modify the code for languages other than English.

Sam Jacobs was the Director of Technology at Newtek Technology Solutions (formerly IPM, the longest standing Citrix Platinum Partner on the East Coast). With more than 40 years of IT consulting, Sam is a Citrix NetScaler, StoreFront, and Web Interface customization and integration expert. He holds Microsoft Azure Developer and Citrix CCP-N certifications, and is a frequent contributor to the CUGC CTP blog. He has presented advanced breakout sessions at Citrix Synergy for 6 years on ADC (NetScaler) and StoreFront customizations and integration. He is one of the top Citrix Support Forum contributors, and has earned industry praise for the tools he has developed to make NetScaler, StoreFront, and Web Interface easier to manage for administrators and more intuitive for end users. Sam became a Citrix Technology Professional (CTP) in 2015, and can be reached at sjacobsCTP@gmail.com.

Sunday, June 9, 2024

Who Put the Server into Maintenance Mode?

A forum user asked how to get a list of servers in maintenance mode, and who put the server into maintenance mode.
To get a list of servers in maintenance mode, you can use the following PowerShell snippet:

Get-BrokerMachine | ? InMaintenanceMode -eq $True | Sort MachineName |
    Select @{Name="Machine";Expression={$_.MachineName.Split('\')[1]}},
        IPAddress, InMaintenanceMode

... and you can use the following snippet to see who has turned on maintenance mode in the past 30 days:

$fromDate = (Get-Date).AddDays(-30)
Get-LogHighLevelOperation -Filter{ StartTime -ge $fromDate -and Text -like "Turn On Maintenance Mode*" } |
    Sort StartTime -Descending |
    Select  @{Name="When";Expression={$_.StartTime}},
            @{Name="Who";Expression={$_.User.Split('\')[1]}},
            @{Name="Server";Expression={$_.Text}}

It would be a bit challenging to attempt to match up the results of the two commands, since the first one is point in time, and the server may have gone in and out of maintenance mode multiple times during the 30-day period.
 

Sam Jacobs is the Director of Technology at Newtek Technology Solutions (formerly IPM, the longest standing Citrix Platinum Partner on the East Coast). With more than 40 years of IT consulting, Sam is a Citrix NetScaler, StoreFront, and Web Interface customization and integration expert. He holds Microsoft Azure Developer and Citrix CCP-N certifications, and was a frequent contributor to the CUGC CTP blog. He has presented advanced breakout sessions at Citrix Synergy for 6 years on ADC (NetScaler) and StoreFront customizations and integration. He is one of the top Citrix Support Forum contributors, and has earned industry praise for the tools he has developed to make NetScaler, StoreFront, and Web Interface easier to manage for administrators and more intuitive for end users. Sam became a Citrix Technology Professional (CTP) in 2015, and can be reached at sjacobsCTP@gmail.com.

Sunday, May 19, 2024

Displaying a List of Current Citrix Sessions

Citrix administrators always need to know who is currently accessing their environment. In this post, we will show you how to create a real-time PowerShell listing of active and disconnected sessions. An HTML report may also be generated. 

As usual, we will be using the Citrix Monitoring Database for our report, so you will need to modify the variables holding the name or IP address of the SQL Server hosting the monitoring database, as well as the database name (and you need permissions to read the database, of course).

The following variables need to be modified
$SQLServer = "SQL Server"  
$SQLDBName = "MonitoringDB" 

The following variables may be modified if you wish to change the location or title of your report.

$reportLocation = "c:\reports\" 
$reportFileName = "CurrentSessions"

We create a SQL query to retrieve the sessions.

$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Integrated Security = True; MultipleActiveResultSets = True"
#[System.Collections.ArrayList]$sessions = @()
$sessions = @()
$strQuery = `
"select
monitordata.session.SessionKey
,startdate
,logonduration
,enddate
,connectionstate
,username
,fullname
,monitordata.machine.Name as MachineName
,monitordata.desktopgroup.Name
,IsRemotePC
,DesktopKind
,SessionSupport
,SessionType
,DeliveryType
,ClientName
,ClientAddress
,ClientVersion
,ConnectedViaHostName
,ConnectedViaIPAddress
,LaunchedViaHostName
,LaunchedViaIPAddress
,IsReconnect
,Protocol
,LogOnStartDate
,LogOnEndDate
,BrokeringDuration
,BrokeringDate
,DisconnectCode
,DisconnectDate
,VMStartStartDate
,VMStartEndDate
,ClientSessionValidateDate
,ServerSessionValidateDate
,EstablishmentDate
,HdxStartDate
,HdxEndDate
,AuthenticationDuration
,GpoStartDate
,GpoEndDate
,LogOnScriptsStartDate
,LogOnScriptsEndDate
,ProfileLoadStartDate
,ProfileLoadEndDate
,InteractiveStartDate
,InteractiveEndDate
,Datediff(minute,logonenddate,DisconnectDate) as 'SessionLength'
, '00:00' as connectDuration
FROM monitordata.session
join monitordata.[user] on monitordata.session.UserId = monitordata.[user].Id
join monitordata.Machine on monitordata.session.MachineId = monitordata.machine.Id
join monitordata.DesktopGroup on monitordata.machine.DesktopGroupId = monitordata.desktopgroup.Id
join monitordata.connection on monitordata.session.SessionKey = monitordata.connection.SessionKey
where UserName <> ''  
and monitordata.session.CurrentConnectionId = monitordata.connection.Id
and enddate IS NULL
order by username,SessionKey" 

$sessions = @(sqlquery -q $strQuery | ?{$_ -notlike "*[0-9]*"})


We calculate the session duration (in minutes).

$now = [datetime]::now

$sessions | %{
$_.sessionlength = [math]::Round(($now - (get-date $_.startdate).ToLocalTime()).totalminutes,0)
$_.connectDuration = $(duration $_.SessionLength)
#Write-Host "$($_.username): start: $((get-date $_.startdate).ToLocalTime()) now: $($now) length: $($_.sessionlength) $(duration $_.SessionLength)"
}


We prepare an HTML report of the sessions if desired, and we also display a list of the session in the PowerShell session.

$header = @" 
<style>
body, TH, TD { font-family: Segoe UI, tahoma, Arial, sans-serif ; font-size:14px; }
h2 { font-family: tahoma; font-size:20px; }
h6 { font-family: tahoma; font-size:11px; }

TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;width: 95%} 
TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;color:white; background-color: #6495ED;} 
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;} 
.odd { color:black; background-color:#ffffff; } 
.even { color:black; background-color:#dddddd; } 
</style>
"@

$message = $null
$XDsessions = @($sessions | sort username |
select @{n='User Name';e={$_.userName}}, @{n='Full Name';e={$_.fullName}}, 
@{n='Start Date/Time';e={$_.startdate.ToLocalTime()}}, 
@{n='State';e={if ($_.connectionState -eq 5) {'Act'} else {'Disc'}}},
@{n='Host';e={$_.MachineName.Split('\')[1]}},
@{n='Duration';e={duration $_.SessionLength}},
@{n='Protocol';e={$_.protocol}}, @{n='Desktop Group';e={$_.name}},
@{n='StoreFront';e={$_.LaunchedViaIPAddress}}, 
@{n='NetScaler';e={$_.ConnectedViaIPAddress}}) 

if ($XDsessions.Count -gt 0) {
$contentHead = "<h2>$($reportName): &nbsp; $now</h2><h6>$($XDsessions.Count) sessions</h6>"
$message = $XDsessions | ConvertTo-Html -head $header -Title $($reportName) -PreContent $contentHead |
Set-AlternatingRows -CSSEvenClass even -CSSOddClass odd

if ($message -ne $null) {
if ($reportFileName -ne "") {
$timeStamp = (Get-Date -Format u).Replace(":",".").Replace("Z","").Replace(" ","_")
$filePathName = "$($reportLocation)$($reportFileName)_$($timeStamp).html"
$message | Out-File $filePathName
}
$XDSessions | ft -AutoSize
$act = ($XDsessions | ? State -eq 'Act').Count
"$($XDsessions.Count) total sessions: $($act) active sessions, $($XDSessions.Count-$act) disconnected sessions."
}


We make use of 3 functions:
Set-AlternatingRows - to color code the HTML report, making it easier to read
duration - if the session duration is longer than a day, will display > x days instead of hours
sqlquery - the function that executes the SQL query and returns the results

Function Set-AlternatingRows {
[CmdletBinding()]
Param(
[Parameter(Mandatory,ValueFromPipeline)]
[string]$Line,
   
[Parameter(Mandatory)]
[string]$CSSEvenClass,
   
[Parameter(Mandatory)]
[string]$CSSOddClass
)
Begin {
$ClassName = $CSSEvenClass
}
Process {
If ($Line.Contains("<tr><td>"))
{ $Line = $Line.Replace("<tr>","<tr class=""$ClassName"">")
If ($ClassName -eq $CSSEvenClass)
{ $ClassName = $CSSOddClass
}
Else
{ $ClassName = $CSSEvenClass
}
}
Return $Line
}
}

function sqlquery ($q) {
$SqlQuery = $q
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
return $DataSet.tables[0]
}

Function duration {
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[int]$mins
)
[string] $strDuration = ""

if ($mins -gt 2880) {
$strDuration = "> $([math]::Floor($mins/1400)) days"
} elseif ($mins -gt 1440) {
$strDuration = "> 1 day"
} else {
$hh = ([math]::Floor($mins/60)).ToString("00")
$mm = ($mins % 60).ToString("00")
$strDuration = "$($hh):$($mm)"
}

return $strDuration
}

You can download the complete script here:  Get-CurrentSessions.ps1. You can open the file in a PowerShell session (or the ISE) and execute it there, or you can save the file on any machine with access to the monitoring database, and execute:

<path to script>\Get-CurrentSessions.ps1

Happy scripting!


Sam Jacobs is the Director of Technology at Newtek Technology Solutions (formerly IPM, the longest standing Citrix Platinum Partner on the East Coast). With more than 40 years of IT consulting, Sam is a Citrix NetScaler, StoreFront, and Web Interface customization and integration expert. He holds Microsoft Azure Developer and Citrix CCP-N certifications, and was a frequent contributor to the CUGC CTP blog. He has presented advanced breakout sessions at Citrix Synergy for 6 years on ADC (NetScaler) and StoreFront customizations and integration. He is one of the top Citrix Support Forum contributors, and has earned industry praise for the tools he has developed to make NetScaler, StoreFront, and Web Interface easier to manage for administrators and more intuitive for end users. Sam became a Citrix Technology Professional (CTP) in 2015, and can be reached at sjacobsCTP@gmail.com.

Sunday, April 14, 2024

Get a List of User Logons

A frequent request of Citrix administrators is the activity in the environment. In this post, we will show you how to create a report of user logons within the past x days. 
The script will accept a parameter specifying the number of days (i.e. List user logons for the past X days). In addition, you will also to modify the variables holding the name or IP address of the SQL Server hosting the monitoring database, as well as the database name (and you need permissions to read the database, of course).

param ([string]$daysCovered)

$SQLServer = "SQL Server"  
$SQLDBName = "CitrixMonitorDB" 

There are also some optional variables to change, such as the location to save the report, the file name, and the title of the report.

$reportLocation = ".\" 
$reportFileName = "UserLogons"
$title = "User Logon Report"


We begin by defaulting to a span of a week if the number of days was not specified. Then we calculate the beginning and ending dates and create a date filter for our SQL query.

if ($daysCovered -eq '') {
# default to a week - back 6 days
$daysCovered = 6
}

# set the starting and ending dates
$eDate = [datetime]::now 
$sDate = $eDate.AddDays(-$daysCovered)

# create the date filter
$filter = "and monitordata.session.StartDate > convert(datetime,'"+(get-date ($sDate).ToUniversalTime() -Format "MM/dd/yyyy HH:mm:ss")+"') "


We create a SQL query to retrieve desktop sessions.

$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Integrated Security = True; MultipleActiveResultSets = True"
[System.Collections.ArrayList]$sessions = @()
[System.Collections.ArrayList]$appsessions = @()
[System.Collections.ArrayList]$sessions = sqlquery -q `
"select
monitordata.session.SessionKey
,monitordata.connection.establishmentDate
,monitordata.session.StartDate
,logonduration
,enddate
,connectionstate
,username
,fullname
,monitordata.machine.HostedMachineName
,monitordata.desktopgroup.Name as appDeskName
,IsRemotePC
,DesktopKind
,SessionSupport
,SessionType
,DeliveryType
,ClientName
,ClientAddress
,ClientVersion
,ConnectedViaHostName
,ConnectedViaIPAddress
,LaunchedViaHostName
,LaunchedViaIPAddress
,IsReconnect
,Protocol
,LogOnStartDate
,LogOnEndDate
,BrokeringDuration
,BrokeringDate
,DisconnectCode
,DisconnectDate
,VMStartStartDate
,VMStartEndDate
,ClientSessionValidateDate
,ServerSessionValidateDate
,EstablishmentDate
,HdxStartDate
,HdxEndDate
,AuthenticationDuration
,GpoStartDate
,GpoEndDate
,LogOnScriptsStartDate
,LogOnScriptsEndDate
,ProfileLoadStartDate
,ProfileLoadEndDate
,InteractiveStartDate
,InteractiveEndDate
,Datediff(minute,logonenddate,DisconnectDate) as 'SessionLength'
from monitordata.session
join monitordata.[user] on monitordata.session.UserId = monitordata.[user].Id
join monitordata.Machine on monitordata.session.MachineId = monitordata.machine.Id
join monitordata.DesktopGroup on monitordata.machine.DesktopGroupId = monitordata.desktopgroup.Id
join monitordata.connection on monitordata.session.SessionKey = monitordata.connection.SessionKey
where UserName <> '' and SessionType = '0'
$filter
order by logonenddate,SessionKey" | ?{$_ -notlike "*[0-9]*"}

We then create a query to retrieve XenApp sessions.

[System.Collections.ArrayList]$appsessions = sqlquery -q `
"select monitordata.session.SessionKey
,monitordata.connection.establishmentDate
,monitordata.session.StartDate
,LogOnDuration
,monitordata.session.EndDate
,ConnectionState
,UserName
,FullName
,monitordata.application.Name as appDeskName
,PublishedName
,monitordata.machine.HostedMachineName
,IsRemotePC
,DesktopKind
,SessionSupport
,DeliveryType
,ClientName
,ClientAddress
,ClientVersion
,ConnectedViaHostName
,ConnectedViaIPAddress
,LaunchedViaHostName
,LaunchedViaIPAddress
,IsReconnect
,Protocol
,LogOnStartDate
,LogOnEndDate
,BrokeringDuration
,BrokeringDate
,DisconnectCode
,DisconnectDate
,VMStartStartDate
,VMStartEndDate
,ClientSessionValidateDate
,ServerSessionValidateDate
,EstablishmentDate
,HdxStartDate
,AuthenticationDuration
,GpoStartDate
,GpoEndDate
,LogOnScriptsStartDate
,LogOnScriptsEndDate
,ProfileLoadStartDate
,ProfileLoadEndDate
,InteractiveStartDate
,InteractiveEndDate
,Datediff(minute,logonenddate,DisconnectDate) as 'SessionLength'
from monitordata.Session
join monitordata.[user] on monitordata.session.UserId = monitordata.[user].Id
join monitordata.Machine on monitordata.session.MachineId = monitordata.machine.Id
join monitordata.DesktopGroup on monitordata.machine.DesktopGroupId = monitordata.desktopgroup.Id
join monitordata.connection on monitordata.session.SessionKey = monitordata.connection.SessionKey
join monitordata.applicationinstance on monitordata.ApplicationInstance.SessionKey = monitordata.session.SessionKey
join monitordata.application on monitordata.application.id = monitordata.ApplicationInstance.ApplicationId
where UserName <> '' and sessiontype = '1' 
$filter
order by logonenddate,SessionKey" | ?{$_ -notlike "*[0-9]*"}

We create an array to contain both XD and XA sessions, filtering by our date criteria above, and sorting by the user's name

$allsessions = $sessions | 
Select-Object @{n='startdate';e={'{0:MM/dd/yy hh:mm tt}' -f $_.startdate.toLocalTime()}},
username, fullname,
@{n='enddate';e={'{0:MM/dd/yy hh:mm tt}' -f $_.enddate.toLocalTime()}}, 
sessionLength, appDeskName

$allsessions += $appsessions | 
Select-Object @{n='startdate';e={'{0:MM/dd/yy hh:mm tt}' -f $_.startdate.toLocalTime()}},
username, fullname,
@{n='enddate';e={'{0:MM/dd/yy hh:mm tt}' -f $_.enddate.toLocalTime()}}, 
sessionLength, appDeskName  

$sortedSessions = $allsessions | sort fullname


We calculate the session duration (in minutes).

$sortedSessions | %{
if ($_.enddate -eq $Null) {
$_.sessionlength = [math]::Round(((Get-Date) - (get-date $_.startDate)).totalminutes,0)
} else {
$_.sessionlength = [math]::Round(((Get-Date $_.enddate) - (Get-Date $_.startDate)).totalminutes,0)
}
}


Finally, we create an HTML report of the sessions, as well as a .CSV file that you can slice and dice to your specifications.

$Header = @" 
<style>
body, TH, TD { font-family: Segoe UI, tahoma, Arial, sans-serif ; font-size:14px; }
h2 { font-family: tahoma; font-size:20px; }

TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;width: 95%} 
TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;color:white; background-color: #6495ED;} 
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;} 
.odd { color:black; background-color:#ffffff; } 
.even { color:black; background-color:#dddddd; } 
</style>
"@
$e = '{0:MM/dd/yy hh:mm tt}' -f $eDate
$s = '{0:MM/dd/yy hh:mm tt}' -f $sDate
$message = $null
$formattedsessions = $sortedSessions | 
Select-Object -unique @{n='Start Date/Time';e={'{0:MM/dd/yy hh:mm tt}' -f $_.startdate}},
@{n='User Name';e={$_.userName}}, @{n='Full Name';e={$_.fullName}}, 
@{n='End Date/Time';e={'{0:MM/dd/yy hh:mm tt}' -f $_.enddate}}, 
@{n='Duration';e={duration($_.sessionLength)}},
@{n='Application/Desktop Group';e={$_.appDeskName}}

if ($formattedsessions.Count -gt 0) {
$sessionsHTML = $formattedsessions | ConvertTo-Html -head $header -Title $title -PreContent "<h2>$($title): &nbsp; $s - $e</h2> $($formattedSessions.Count.ToString('N0')) Sessions" | Set-AlternatingRows -CSSEvenClass even -CSSOddClass odd

if ($sessionsHTML -ne $null) {
$timeStamp = (Get-Date -Format u).Replace(":",".").Replace("Z","").Replace(" ","_")
$filePathName = "$($reportLocation)$($reportFileName)_$($timeStamp).html"
$sessionsHTML | Out-File $filePathName

$filePathName = "$($reportLocation)$($reportFileName)_$($timeStamp).csv"
$formattedsessions | ConvertTo-Csv  -NoTypeInformation | Out-File $filePathName

}

We also make use of 4 functions:
Set-AlternatingRows - to color code the HTML report, making it easier to read
Convert-UTCtoLocal  - to convert start/end times (stored in UTC) to local time
duration - if the session duration is longer than a day, will display > x days instead of hours
sqlquery - the function that executes the SQL query and returns the results

Function Set-AlternatingRows {
[CmdletBinding()]
Param(
[Parameter(Mandatory,ValueFromPipeline)]
[string]$Line,
   
[Parameter(Mandatory)]
[string]$CSSEvenClass,
   
[Parameter(Mandatory)]
[string]$CSSOddClass
)
Begin {
$ClassName = $CSSEvenClass
}
Process {
If ($Line.Contains("<tr><td>"))
{ $Line = $Line.Replace("<tr>","<tr class=""$ClassName"">")
If ($ClassName -eq $CSSEvenClass)
{ $ClassName = $CSSOddClass
}
Else
{ $ClassName = $CSSEvenClass
}
}
Return $Line
}
}

function sqlquery ($q) {
$SqlQuery = $q
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
return $DataSet.tables[0]
}

function Convert-UTCtoLocal
{
  param(
[parameter(Mandatory=$true)]
[String] $UTCTime
  )
$strCurrentTimeZone = (Get-WmiObject win32_timezone).StandardName
$TZ = [System.TimeZoneInfo]::FindSystemTimeZoneById($strCurrentTimeZone)
$LocalTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($UTCTime, $TZ)
}

Function duration {
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[int]$mins
)
[string] $strDuration = ""

if ($mins -gt 2880) {
$strDuration = "> $([math]::Floor($mins/1400)) days"
} elseif ($mins -gt 1440) {
$strDuration = "> 1 day"
} else {
$hh = ([math]::Floor($mins/60)).ToString("00")
$mm = ($mins % 60).ToString("00")
$strDuration = "$($hh):$($mm)"
}

return $strDuration
}

You can download the complete script here:  Get-UserLogons.ps1. Save the file on any machine with access to the monitoring database. Open a PowerShell command prompt or the PowerShell ISE, and execute:

<path to script>\Get-UserLogons.ps1  nn

where nn is the number of days to report on. If you omit nn, the script defaults to 7 days.

Happy scripting!

Sam Jacobs is the Director of Technology at Newtek Technology Solutions (formerly IPM, the longest standing Citrix Platinum Partner on the East Coast). With more than 40 years of IT consulting, Sam is a Citrix NetScaler, StoreFront, and Web Interface customization and integration expert. He holds Microsoft Azure Developer and Citrix CCP-N certifications, and was a frequent contributor to the CUGC CTP blog. He has presented advanced breakout sessions at Citrix Synergy for 6 years on ADC (NetScaler) and StoreFront customizations and integration. He is one of the top Citrix Support Forum contributors, and has earned industry praise for the tools he has developed to make NetScaler, StoreFront, and Web Interface easier to manage for administrators and more intuitive for end users. Sam became a Citrix Technology Professional (CTP) in 2015, and can be reached at sjacobsCTP@gmail.com.

Sunday, March 17, 2024

PowerShell Script to Get Citrix VM details

Quite a number of clients are running a virtual Citrix environment, so here is a script to get a list of basic details on each VM such as name, state, IP address, VM host, and memory. You will need to modify the value of the $VIserver variable.

# load the PowerCLI cmdlets
Import-Module VMware.PowerCLI

# name or IP of the vCenter server
$VIserver   = 'vcenter.domain.com'

# where to store the output
$outputFile = 'c:\temp\VMlist.csv'

# retrieve the vCenter creds
$creds      = Get-Credential

# connect to vCenter
Connect-VIServer $VIserver -Credential $creds

# retrieve and format the information from vCenter
Get-VM | Sort-Object PowerState, Name |

   Select-Object -Property `

   @{n='IP Address'; e={$_.guest.IPAddress[0]}},

   @{n='VM Host'; e={$_.VMHost}},

   @{n='Memory'; e={"$([math]::Round($_.MemoryGB))GB"}},

   @{n='State'; e={$_.PowerState.toString().Substring(7)}} | 

   Export-Csv -Path $outputFile -NoTypeInformation

# disconnect from vCenter - no need to confirm
Disconnect-VIServer -Confirm:$false


Sam Jacobs is the Director of Technology at Newtek Technology Solutions (formerly IPM, the longest standing Citrix Platinum Partner on the East Coast). With more than 40 years of IT consulting, Sam is a Citrix NetScaler, StoreFront, and Web Interface customization and integration expert. He holds Microsoft Azure Developer and Citrix CCP-N certifications, and was a frequent contributor to the CUGC CTP blog. He has presented advanced breakout sessions at Citrix Synergy for 6 years on ADC (NetScaler) and StoreFront customizations and integration. He is one of the top Citrix Support Forum contributors, and has earned industry praise for the tools he has developed to make NetScaler, StoreFront, and Web Interface easier to manage for administrators and more intuitive for end users. Sam became a Citrix Technology Professional (CTP) in 2015, and can be reached at sjacobsCTP@gmail.com.

Sunday, February 18, 2024

Get A List of Unused Citrix Applications

Citrix Director gives administrators the capability to create their own custom reports. However, that feature is only available with a Platinum license. But don't lose hope - you can easily create your own custom reports by querying the Citrix Monitoring database. 
Some of our clients have in excess of 100 published applications. It's no wonder, then, that they may wish to remove unused applications. So, let's use a PowerShell script to create a report of unused applications.
The script will accept a parameter specifying the number of days (i.e. List applications that haven't been used in X days). We will also need the name or IP address of the SQL Server hosting the monitoring database, as well as the database name.

    param ([int] $daysCovered = 30) $SQLServer = "<SQL Server with Citrix Monitor DB>"       $SQLDBName = "CitrixMonitoring"

We then create a SQL query to retrieve the name and last used date of all applications in the monitoring database. If an application has never been used (its LastUsed date is null), we set the date to 1/1/1900).

    # retrieve the data     $SqlConnection = New-Object System.Data.SqlClient.SqlConnection     $SqlConnection.ConnectionString = `
        "Server = $SQLServer; Database = $SQLDBName; Integrated Security = True; `
        MultipleActiveResultSets = True"     [System.Collections.ArrayList]$sessions = @()     $strQuery = `     "SELECT Name AS AppName, LastUsed =        ISNULL((select Max(StartDate)        FROM monitordata.applicationinstance inst        JOIN monitordata.application app on app.id = inst.ApplicationId        where Name = application.Name), '01/01/1900')      FROM monitordata.application application"

We'll need a function to make the actual call to SQL and to fill a dataset with the results.

    Function sqlquery ($q) {         $SqlQuery = $q         $SqlCmd = New-Object System.Data.SqlClient.SqlCommand         $SqlCmd.CommandText = $SqlQuery         $SqlCmd.Connection = $SqlConnection         $SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter         $SqlAdapter.SelectCommand = $SqlCmd         $DataSet = New-Object System.Data.DataSet         $SqlAdapter.Fill($DataSet)         return $DataSet.tables[0]     }

We'll call the function, passing the SQL query that we created above.

[System.Collections.ArrayList]$apps = sqlquery -q $strQuery | ?{$_ -notlike "*[0-9]*"}

Finally, we calculate the cutoff date, and list the applications not used since then.

    Write-Host "Applications not used in the past $($daysCovered) days:"     $cutoff = (Get-Date).AddDays(-$DaysCovered)     $apps | ? LastUsed -lt $cutoff |         Select AppName, @{Name="Last Used";Expression=`
            {if($_.LastUsed -eq [DateTime]'1/1/1900') {'... never used...'} `
            else {$_.LastUsed}} }

You can download the complete script here:  Get-StaleApplications.ps1. Save the file on a machine with access to the monitoring database. Open a PowerShell command prompt or the PowerShell ISE, and execute:

<path to script>\Get-StaleApplications.ps1  nn

where nn is the cutoff number of days to report on. If you omit nn, the script defaults to 30 days.

Happy scripting!

Sam Jacobs is the Director of Technology at Newtek Technology Solutions (formerly IPM, the longest standing Citrix Platinum Partner on the East Coast). With more than 40 years of IT consulting, Sam is a Citrix NetScaler, StoreFront, and Web Interface customization and integration expert. He holds Microsoft Azure Developer and Citrix CCP-N certifications, and was a frequent contributor to the CUGC CTP blog. He has presented advanced breakout sessions at Citrix Synergy for 6 years on ADC (NetScaler) and StoreFront customizations and integration. He is one of the top Citrix Support Forum contributors, and has earned industry praise for the tools he has developed to make NetScaler, StoreFront, and Web Interface easier to manage for administrators and more intuitive for end users. Sam became a Citrix Technology Professional (CTP) in 2015, and can be reached at sjacobsCTP@gmail.com.