I once needed to use C# to call into the Citrix Fast Connect API. This is how I did it.
I used the authmanager SDK to enumerate apps.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Net;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Security.Policy;
using System.Text;
using System.Xml;
using Microsoft.Win32.SafeHandles;
using Citrix.DeliveryServices.Clients.AuthManager;
namespace UseCtxCredApi
{
class Program
{
static void Main( string[] args )
{
// Pass credentials securely to fast connect
MSV1_0_INTERACTIVE_LOGON logon = new MSV1_0_INTERACTIVE_LOGON();
logon.LogonDomainName = FastConnectWrapper.InitLsaString("imp");
logon.MessageType = MSV1_0_LOGON_SUBMIT_TYPE.MsV1_0InteractiveLogon;
logon.Password = FastConnectWrapper.InitLsaString( "Citrix1" );
logon.UserName = FastConnectWrapper.InitLsaString( "user1" );
FastConnectWrapper m = new FastConnectWrapper();
int result = 100;
LOGONSSOUSER_ERROR_CODE transmittionErrorCode = m.MyLogonSsoUser( ref logon, 1, 1,ref result );
if(transmittionErrorCode != LOGONSSOUSER_ERROR_CODE.LOGONSSOUSER_OK)
Console.WriteLine("There was a problem trasmitting the credentials to the single sign-on compoment.");
// Now enumerate applications
try
{
ConnectionManager.Initialize();
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
throw;
}
int authResult = 0;
try
{
Connection pConnection = ConnectionManager.CreateConnection("HTTP API C++ test");
LogonStatus loggedOnStatus = pConnection.GetLogonStatus("http://imp-xd1.imp.net/Citrix/Authentication/auth/v1/token");
if (loggedOnStatus != LogonStatus.LoggedOn)
{
Console.WriteLine("Not Logged in");
return;
}
Console.WriteLine( "Logged in" );
string enumeration_result;
// Now everything is initialised, do some tasks.
// These may or may not throw, but as good practice expect an AuthManagerException from anything using the SDK.
//enumeration_result = DoResourceAccess();
enumeration_result = DoResourceAccess_GetSilent();
if (String.IsNullOrEmpty(enumeration_result))
return;
// get first ica launch resource in the enumeration results.
XmlDocument doc = new XmlDocument();
doc.Load( new System.IO.StringReader( enumeration_result ) );
XmlNode firstApp = doc.SelectSingleNode("//*[local-name()='launchica'][1]");
if( firstApp != null)
PostData( firstApp.InnerText );
}
catch (AuthManagerException ex)
{
authResult = 2;
}
try
{
ConnectionManager.Uninitialize();
}
catch (AuthManagerException ex)
{
authResult = 3;
}
if( authResult != 0)
Console.WriteLine("Problem using the AuthManager SDK.");
Console.WriteLine("Press any key to perform fast connect Logoff");
Console.ReadKey();
m.MyLogoffSsoUser(0);
}
// This sample demonstrates making a POST request for an ICA launch
// allowing authentication prompts,
// and prints all the HTTP response data to the console if successful.
static void PostData(string launchuri)
{
try
{
// Create a connection. This object can be reused for the lifetime of this task.
Connection pConnection = ConnectionManager.CreateConnection("HTTP API C++ test");
// Create a request. A client must create a new request for each attempt
// to access resources. (i.e. GetResponse is valid for only one call.)
using (IAMHttpRequest request = pConnection.CreateAMHttpRequest())
{
// Set up the request parameters
//request.Uri = new Uri( "http://sf3.xd.local/Citrix/SF3/resources/v2/Q29udHJvbGxlci5Ob3RlcGFk/launch/ica" );
request.Uri = new Uri( launchuri );
// Set method and upload data
request.Method = "POST";
const string requestBody = "<?xml version=\"1.0\" encoding=\"utf-8\"?><q1:launchparams xmlns:q1=\"http://citrix.com/delivery-services/1-0/launchparams\"></q1:launchparams>";
request.Body = Encoding.UTF8.GetBytes(requestBody);
// Add any required headers.
// Note that User-Agent and Connection headers are reserved.
request.AddHeader("Content-Type","application/vnd.citrix.launchparams+xml");
request.AddHeader("Accept","*/*");
// Send the request and get a response
// Send the request and get a response. This may throw an HttpException or HttpsTrustException
IAMHttpResponse response = request.GetResponse();
// Do something with the response data, e.g. Send to std::wcout.
// Print the HTTP status info.
System.Console.WriteLine("HTTP "+ response.StatusCode + " " + response.ReasonPhrase);
// Print all headers
var headers = response.Headers;
foreach (string header in headers)
{
Console.WriteLine(header);
}
// Print the response body.
// This example assumes the response is purely narrow characters
// (and that sizeof(char) == sizeof(AMByte)), and copies the data
// straight into to a string.
using (MemoryStream ms = new MemoryStream())
{
byte[] buffer = new byte[16*1024];
Stream stream = response.GetResponseBodyStream();
int r = stream.Read(buffer, 0, buffer.Length);
while (r > 0)
{
ms.Write(buffer, 0, r);
r = stream.Read(buffer, 0, buffer.Length);
}
string icafile = System.Text.Encoding.UTF8.GetString(ms.ToArray());
System.Console.WriteLine( icafile );
}
}
}
catch(AuthManagerException ex)
{
if(ex.Result == AuthManagerResult.AuthCancelledByUser)
{
Console.WriteLine("Cancelled");
}
else if(ex.Result == AuthManagerResult.Aborted)
{
Console.WriteLine("Aborted");
}
else
{
// Deal with errors, either from the request or the Auth Manager connection
Console.WriteLine("Error: " + ex.Message);
}
}
}
private static string DoResourceAccess()
{
string data = String.Empty;
try
{
// Typically, a client creates and re-uses a single connection object for a task.
Connection pConnection = ConnectionManager.CreateConnection("HTTP API C++ test");
LogonStatus a = pConnection.GetLogonStatus( "http://imp-xd1.imp.net/Citrix/Authentication/auth/v1/token" );
// Create a request. A client must create a new request for each attempt
// to access resources. (i.e. GetResponse is valid for only one call.)
using (IAMHttpRequest request = pConnection.CreateAMHttpRequest())
{
// Set up the request parameters.
//request.Uri = new Uri( "http://sf3.xd.local/Citrix/SF3/resources/v2" );
request.Uri = new Uri( "http://imp-xd1.imp.net/citrix/store/resources/v2" );
// Optionally set the reference URL if the resource URL above
// is not for a store in Receiver's service records.
//request.ReferenceUri = new Uri( "http://sf3.xd.local/Citrix/SF3" );
request.ReferenceUri = new Uri( "http://imp-xd1.imp.net/citrix/store" );
// Set flags to allow interactive logon if authentication is required.
request.AuthenticationFlags = AuthenticationFlags.AllowLogon | AuthenticationFlags.Interactive;
// Optionally set string to append to user agent if needed to identify
// the Receiver component making the request.
request.UserAgentSuffix = "AuthManCppSdkSample/5.0";
// GET is the default method so this next line is not actually required.
request.Method = "GET";
// Add any required headers.
// Note that User-Agent and Connection headers are reserved.
request.AddHeader("Accept","*/*");
// Send the request and get a response. This may throw an HttpException or HttpsTrustException
IAMHttpResponse response = request.GetResponse();
// Do something with the response data, e.g. Send to std::wcout.
// Print the HTTP status info.
System.Console.WriteLine("HTTP "+ response.StatusCode + " " + response.ReasonPhrase);
// Print all headers
var headers = response.Headers;
foreach (string header in headers)
{
Console.WriteLine(header);
}
// Print the response body.
// This example assumes the response is purely narrow characters
// (and that sizeof(char) == sizeof(AMByte)), and copies the data
// straight into to a string.
using (MemoryStream ms = new MemoryStream())
{
byte[] buffer = new byte[16*1024];
Stream stream = response.GetResponseBodyStream();
int r = stream.Read(buffer, 0, buffer.Length);
while (r > 0)
{
ms.Write(buffer, 0, r);
r = stream.Read(buffer, 0, buffer.Length);
}
data = System.Text.Encoding.UTF8.GetString(ms.ToArray());
System.Console.WriteLine(data);
}
var certificate = response.ServerCertificate;
}
}
catch(AuthenticationException ex)
{
// Deal with authentication errors.
// Alternatively this could be handled by catching just the base
// AuthManagerException which exposes the same data but may also contain
// non-authentication error codes. This is shown in the samples below.
if(ex.Result == AuthManagerResult.AuthCancelledByUser )
{
Console.WriteLine("Cancelled");
}
else
{
Console.WriteLine("Authentication failed: " + ex.Message);
}
}
catch(HttpsTrustException trustEx)
{
// There is a trust problem such as an invalid, revoked or expired server certificate.
Console.WriteLine("A server certificate trust problem occurred: " + trustEx.Message);
throw;
}
catch(HttpException httpEx)
{
// There are network problems such as DNS problems or failure to open a connection to the server.
Console.WriteLine("An HTTP layer problem occurred: " + httpEx.Message);
throw;
}
catch(AuthManagerException ex)
{
// Deal with errors, either from the request or the Auth Manager connection
if(ex.Result == AuthManagerResult.Aborted)
{
Console.WriteLine("Aborted");
}
else
{
Console.WriteLine("Error: " + ex.Message);
}
}
return data;
}
// This abbreviated sample demonstrates making a GET request to retrieve a
// resources enumeration from StoreFront but disallows logon prompts so it's
// suitable for a non-user initiated background activity.
// Refer to the sample above for a more complete sample of resource access.
static string DoResourceAccess_GetSilent()
{
string data = String.Empty;
try
{
// Typically, a client creates and re-uses a single connection object for a task.
Connection pConnection = ConnectionManager.CreateConnection( "HTTP API C++ test" );
LogonStatus a = pConnection.GetLogonStatus("http://imp-xd1.imp.net/Citrix/Authentication/auth/v1/token");
using (IAMHttpRequest request = pConnection.CreateAMHttpRequest())
{
// Set up the request parameters.
//request.Uri = new Uri( "http://sf3.xd.local/Citrix/SF3/resources/v2" );
request.Uri = new Uri( "http://imp-xd1.imp.net/citrix/store/resources/v2" );
// Optionally set the reference URL if the resource URL above
// is not for a store in Receiver's service records.
request.ReferenceUri = new Uri( "http://imp-xd1.imp.net/citrix/store" );
//request.ReferenceUri = new Uri( "http://sf3.xd.local/Citrix/SF3" );
// The request is made 'silent' by excluding the
// AUTHENTICATION_FLAGS_ALLOW_INTERACTIVE flag.
request.AuthenticationFlags = AuthenticationFlags.AllowLogon;
// Optionally set string to append to user agent if needed to identify
// the Receiver component making the request.
request.UserAgentSuffix = "AuthManCppSdkSample/5.0";
// GET is the default method so this next line is not actually required.
request.Method = "GET";
// Add any required headers.
// Note that User-Agent and Connection headers are reserved.
request.AddHeader("Accept", "*/*");
// Send the request and get a response. This may throw an HttpException or HttpsTrustException
IAMHttpResponse response = request.GetResponse();
// Do something with the response data, e.g. Send to std::wcout.
// Print the HTTP status info.
System.Console.WriteLine( "HTTP " + response.StatusCode + " " + response.ReasonPhrase );
// Print all headers
var headers = response.Headers;
foreach(string header in headers)
{
Console.WriteLine( header );
}
// Print the response body.
// This example assumes the response is purely narrow characters
// (and that sizeof(char) == sizeof(AMByte)), and copies the data
// straight into to a string.
using(MemoryStream ms = new MemoryStream())
{
byte[] buffer = new byte[16 * 1024];
Stream stream = response.GetResponseBodyStream();
int r = stream.Read( buffer, 0, buffer.Length );
while(r > 0)
{
ms.Write( buffer, 0, r );
r = stream.Read( buffer, 0, buffer.Length );
}
data = System.Text.Encoding.UTF8.GetString( ms.ToArray() );
System.Console.WriteLine( data );
return data;
}
}
}
catch(HttpException httpEx)
{
// There are network problems or the server may be untrusted.
Console.WriteLine("An HTTP layer problem occurred: " + httpEx.Message);
throw;
}
// if interactive authentication was not required then there will be
// a response available...
catch(AuthManagerException ex)
{
// This error is expected if the silent request couldn't be
// completed because authentication required a logon prompt to be shown.
if (ex.Result == AuthManagerResult.AuthInteractionNotAllowed)
{
Console.WriteLine("Request couldn't complete silently: " + ex.Message);
}
else
{
Console.WriteLine("Authentication failed: " + ex.Message);
}
}
return data;
}
} // Program
class FastConnectWrapper
{
[DllImport( "CtxCredApi.dll", EntryPoint = "LogonSsoUser", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
private static extern LOGONSSOUSER_ERROR_CODE LogonSsoUser(ref MSV1_0_INTERACTIVE_LOGON pNewCredentials,
int bDisconnectCurrentUser,
int bRestartPna,
ref int pDwResult );
[DllImport( "CtxCredApi.dll", EntryPoint = "LogoffSsoUser",CallingConvention = CallingConvention.Winapi)]
private static extern UInt32 LogoffSsoUser(
UInt32 timeout );
public LOGONSSOUSER_ERROR_CODE MyLogonSsoUser( ref MSV1_0_INTERACTIVE_LOGON newCredentials,
int disconnectCurrentUser,
int restartPna,
ref int result )
{
return LogonSsoUser( ref newCredentials, disconnectCurrentUser, restartPna, ref result );
}
public UInt32 MyLogoffSsoUser( UInt32 timeout )
{
return LogoffSsoUser( timeout );
}
public static LSA_UNICODE_STRING InitLsaString( string s )
{
// Unicode strings max. 32KB
if(s.Length > 0x7ffe)
throw new ArgumentException( "String too long" );
LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING();
lus.Buffer = s;
lus.Length = (ushort) (s.Length * sizeof( char ));
lus.MaximumLength = (ushort) (lus.Length + sizeof( char ));
return lus;
}
}
/// <summary>
/// Type of SSO logon error code (Citrix)
/// </summary>
public enum LOGONSSOUSER_ERROR_CODE
{
LOGONSSOUSER_OK = 0,
LOGONSSOUSER_UNABLE_TO_GET_PIPE_NAME = -1,
LOGONSSOUSER_UNABLE_TO_CONNECT_TO_SSO = -2,
LOGONSSOUSER_UNABLE_TO_SEND_REQUEST = -3,
LOGONSSOUSER_INVALID_RESPONSE = -4
};
public enum MSV1_0_LOGON_SUBMIT_TYPE
{
MsV1_0InteractiveLogon = 2,
MsV1_0Lm20Logon,
MsV1_0NetworkLogon,
MsV1_0SubAuthLogon,
MsV1_0WorkstationUnlockLogon = 7,
MsV1_0S4ULogon = 12,
MsV1_0VirtualLogon = 82,
MsV1_0NoElevationLogon = 82
};
/// <summary>
/// Interactive logon structure for use with Citrix API (msdn)
/// </summary>
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct MSV1_0_INTERACTIVE_LOGON
{
public MSV1_0_LOGON_SUBMIT_TYPE MessageType;
public LSA_UNICODE_STRING LogonDomainName;
public LSA_UNICODE_STRING UserName;
public LSA_UNICODE_STRING Password;
}
[StructLayout( LayoutKind.Sequential, CharSet = CharSet.Unicode )]
public struct LSA_UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
[MarshalAs( UnmanagedType.LPWStr )]
public string Buffer;
}
internal class FastConnectLibraryWrapper
{
[DllImport("FastConnectLib.dll")]
internal static extern UInt32 LogoffSsoUser( UInt32 timeout );
[DllImport( "FastConnectLib.dll" )]
internal static extern LOGONSSOUSER_ERROR_CODE LogonSsoUser( ref MSV1_0_INTERACTIVE_LOGON pNewCredentials,
int bDisconnectCurrentUser,
int bRestartPna,
ref UInt32 pDwResult );
public UInt32 callLogoffSsoUser( UInt32 timeout )
{
return LogoffSsoUser( timeout );
}
public LOGONSSOUSER_ERROR_CODE callLogonSsoUser( ref MSV1_0_INTERACTIVE_LOGON pNewCredentials,
int bDisconnectCurrentUser,
int bRestartPna,
ref UInt32 pDwResult )
{
return LogonSsoUser( ref pNewCredentials, bDisconnectCurrentUser, bRestartPna, ref pDwResult );
}
}
}