API for Determining if App is Running on Citrix or Terminal Services

13,967

Solution 1

According to: http://forums.citrix.com/message.jspa?messageID=1363711 you can check the SESSIONNAME environment variable.

Another simpler way is to read the system environment variable "SESSIONNAME". If it exists and starts with "ICA" then you're running within a Citrix session. If it starts with "RDP" then you're running within an RDP session.

I tested it with my PC and locally I get:

C:\>echo %SESSIONNAME%
Console

While remotely I got

C:\>echo %SESSIONNAME%
RDP-tcp1

So it seems like it might be an easy route to go, otherwise it sounds like checking for registry values or if certain dlls exist will be the next best thing.

Solution 2

There is an API function that lets you determine whether a specific user session is displayed on the console (locally) or via one the the remoting protocols Citrix ICA (nowadays called HDX) or Microsoft RDP.

Call WTSQuerySessionInformation with 3rd parameter set to WTSClientProtocolType. The function returns:

  • 0 for console sessions
  • 1 for ICA sessions
  • 2 for RDP sessions

Interestingly the return value of 1 is not documented as WTS_PROTOCOL_TYPE_ICA on MSDN (second link above) any more, but as "This value is retained for legacy purposes.".

Update:

XenDesktop sessions cannot be detected with WTSQuerySessionInformation (it returns 0, meaning Console). If you want a universal solution:

  • Call WTSQuerySessionInformation. If that returns 1 or 2 (ICA or RDP), you are done.
  • If WTSQuerySessionInformation returns 0 (Console), dynamically load wfapi.dll and get the address of WFGetActiveProtocol
  • Call WFGetActiveProtocol with a parameter of WF_CURRENT_SESSION, which is defined as ((DWORD)-1)
  • The return value of WFGetActiveProtocol is the session type. It should be either 0 (Console) or 1 (ICA)

I have described the process in detail here along with a C++ code sample and a working compiled tool that returns the current session's remoting protocol type.

Solution 3

Following @Josh's answer, the code would look like this:

Select Case Environment.GetEnvironmentVariable("SessionName").ToUpper.SubString(0,3))
   Case "ICA" 
      bCitrix = True
   Case "RDP"
      bTerminalServer = True
   Case "CON" 
      bPC = True
End Select

I haven't fully tested it out yet, but it looks like it will do what I want. PCs and Terminal Servers reports correctly.

If someone has a way to test this on a Citrix box, it would be much appreciated!

Solution 4

Based on Helge Klein's revised answer (above) I thought I'd post the VBA code to make this happen to help future VBA users hitting this page. Helge already has the C++ code on his own site. If you find this helpful, please upvote Helge Klein's answer.

Option Explicit

Private Const WTS_CURRENT_SERVER_HANDLE = 0&
Private Const WTS_CURRENT_SESSION As Long = -1

Private Enum WTS_INFO_CLASS
    WTSInitialProgram
    WTSApplicationName
    WTSWorkingDirectory
    WTSOEMId
    WTSSessionId
    WTSUserName
    WTSWinStationName
    WTSDomainName
    WTSConnectState
    WTSClientBuildNumber
    WTSClientName
    WTSClientDirectory
    WTSClientProductId
    WTSClientHardwareId
    WTSClientAddress
    WTSClientDisplay
    WTSClientProtocolType
    WTSIdleTime
    WTSLogonTime
    WTSIncomingBytes
    WTSOutgoingBytes
    WTSIncomingFrames
    WTSOutgoingFrames
    WTSClientInfo
    WTSSessionInfo
    WTSSessionInfoEx
    WTSConfigInfo
    WTSValidationInfo
    WTSSessionAddressV4
    WTSIsRemoteSession
End Enum

Private Declare Function WTSQuerySessionInformation _
    Lib "wtsapi32.dll" Alias "WTSQuerySessionInformationA" ( _
    ByVal hServer As Long, ByVal SessionId As Long, _
    ByVal WtsInfoClass As WTS_INFO_CLASS, _
    ByRef ppBuffer As LongPtr, _
    ByRef pBytesReturned As LongPtr _
    ) As Long

Private Declare Function WFGetActiveProtocol _
    Lib "wfapi.dll" ( _
    ByVal SessionId As Long _
    ) As Long

Private Declare Sub WTSFreeMemory Lib "wtsapi32.dll" ( _
    ByVal pMemory As Long)

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    Destination As Any, Source As Any, ByVal length As Long)

Public Function SessionType() As String
    Dim ResultCode As Long
    Dim p As LongPtr
    Dim ppBuffer As LongPtr
    Dim pBytesReturned As Long
    Dim ClientProtocolType As Integer
    ResultCode = WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSClientProtocolType, ppBuffer, pBytesReturned)

    If ResultCode = 0 Then
        p = ppBuffer
        CopyMemory ClientProtocolType, ByVal p, pBytesReturned
        WTSFreeMemory ppBuffer
    End If

    Select Case ClientProtocolType
      Case 0:
        On Error Resume Next
        ResultCode = WFGetActiveProtocol(WTS_CURRENT_SESSION)
        If Err.Number = 53 Then
          SessionType = "Console"
        ElseIf Err.Number = 0 Then
          If ResultCode = 1 Then
            SessionType = "Citrix"
          Else
            SessionType = "Console"
          End If
        End If
        Err.Clear
        On Error GoTo 0
      Case 1:
        SessionType = "Citrix"
      Case 2:
        SessionType = "RDP"
      Case Else
        SessionType = "Other (" & ClientProtocolType & ")"
    End Select
End Function

I've tested this on XenApp and XenDesktop.

Share:
13,967
John Cruz
Author by

John Cruz

I'm a CD in Texas

Updated on July 28, 2022

Comments

  • John Cruz
    John Cruz almost 2 years

    I'm looking for an API/function I can call to determine if software is running on Citrix, Terminal Services, or a stand-alone PC. Optimally, it would work something like this:

    Select Case APIWhatSystem.Type.ToString
       Case "Citrix"
          bCitrix = True
       Case "TS"
          bTerminalServices = True
       Case "PC"
          bPC = True
    End Select
    

    I would prefer something that worked from an API call as opposed to looking at something in the registry as we're having more and more customers that are locking down the registry.

    Thanks.

  • John Cruz
    John Cruz over 13 years
    Do you think it would be safe to assume if that function returns ANY number greater than 0, the app is not running on a PC? ie. It's gotta be running on some kind of server. I'm thinking like 5 years in the future... either way a non-zero should always indicate something other than a PC, right?
  • Helge Klein
    Helge Klein over 13 years
    Yes, I think if would be safe to assume that anything > 0 is ... NOT LOCAL. Please note the difference in wording. It can very well run on a PC, but with today's hot topic "VDI" aka virtual desktops served over some remoting protocol (think 2008 R2 SP1 - RemoteFX) the likelihood increases steadily of PCs becoming 1-session terminal servers soon.
  • mischab1
    mischab1 about 12 years
    Works for me. On my citrix server ?environ("SessionName") returns ICA-tcp#56.
  • user545829
    user545829 over 11 years
    Notice, that in Windows 2008 TS RemoteApp mode this env.variable is absent
  • DHW
    DHW almost 10 years
    If the application is running under Citrix XenApp, then WTSClientProtocolType will return 1. If, however, the application is running under Citrix XenDesktop, then WTSClientProtocolType will return 0. I am still looking for a way to detect it that doesn't involve enumerating the devices and looking for known virtual drivers.
  • Helge Klein
    Helge Klein almost 10 years
    @DHW: XenApp and XenDesktop are fundamentally different in the way they interface with the OS. Whereas XenApp officially tells the OS that it uses a remoting protocol XenDesktop redirects the console session. The user session therefore is not a WTS session and WTSQuerySessionInformation does not help. You should ask another question regarding XenDesktop.
  • DHW
    DHW almost 10 years
    @HelgeKlein There are a lot of websites tracing back to your solution here - and a number of those sites are asking exactly what I'm asking - so I'm thinking my not-so-neat solution will do. I checked which registry keys were present in the virtualized desktop for XenDesktop 2.0 and compared it to ones still in play in the current version. I settled on testing for the existence of HKEY_LOCAL_MACHINE\SOFTWARE\Citrix\VirtualDesktopAgent - So your solution comes back with Console, I'll check for the registry key. If it exists, I conclude its Citrix. Close enough for all practical purposes.
  • Helge Klein
    Helge Klein almost 10 years
    @DHW: Sounds good enough to me.
  • Helge Klein
    Helge Klein over 9 years
    @DHW: I now have a better solution for XenDesktop and updated my answer accordingly.
  • DHW
    DHW over 9 years
    @HelgeKlein I'm not sure on the specifics of the XenDesktop implementation - but wfapi.dll does not exist in the XenDesktop build I'm using.
  • Helge Klein
    Helge Klein over 9 years
    @DHW: Which version of XenDesktop is that? To be more specific: the version of the VDA software installed on the virtual Windows machines is relevant.
  • DHW
    DHW over 9 years
    XenDesktop 5.6. Win7. Don't know if its relevant but its launched using Program Neighborhood Agent (pnagent)
  • Helge Klein
    Helge Klein over 9 years
    @DHW: I just check on a Windows 7 x64 machine with the XenDesktop 5.6 VDA installed. Wfapi.dll and Wfapi64.dll are in the subdirectory "ICAService" of the VDA installation directory. It should also be in the path so you can find it on the console with "where wfapi.dll".
  • DHW
    DHW over 9 years
    @HelgeKlein Ah. See it. C:\Program Files\Citrix\ICAService\wfapi.dll I was looking in Windows tree.
  • Coxy
    Coxy about 4 years
    I just ran this on my infrastructure and I get SESSIONNAME=Console, so it seems to not be a reliable method anymore.