$srcRemotePC = '
DomainName\65TELW19'
$destRemotePC = 'DomainName\65TELW20'
Get-BrokerUser -PrivateDesktopUid (get-BrokerPrivateDesktop -MachineName $srcRemotePC ).Uid | | %{ if($_.Upn -ne "") {
Add-BrokerUser -Machine $destRemotePC -Name $_.Upn
}}
Set Citrix RemotePC PublishedName Match with Hostname
Get-BrokerPrivateDesktop | % {Set-BrokerPrivateDesktop -PublishedName $_.MachineName.Split("\")[1] }
Identify VDI workers that blue screened in vSphere
Typical recommendation in VDI is to turn off OS Memory dump collection and auto reboot on the bluescreen. Optimization tools from citrix/vmware take care of this setting automatically. But this masks the events if there a real issue with master image. Following PowerShell snippet will provide information if any of the worker vm’s have crashed.
Import-Module vmware.powercli
Connect-VIServer vcenterserver
Get-ViEvent -Username User | Where {$_.FullFormattedMessage -like "*Guest operating system has crashed." -AND $_.CreatedTime -gt (Get-Date).AddDays(-1)} | Select CreatedTime,FullFormattedMessage
Mapping Printer for All Users on Windows
rundll32 printui.dll,PrintUIEntry /ga /n\\server\printer
ref > https://www.papercut.com/kb/Main/MakingPrintersAvailableToAllUsers#easier-methods-to-deploy-printers
Good places to find installer silent switches and post optimization/cleanup tasks
How does browser know what Certificate to provide for Client Cert Authentication
Ever wonder how the browser provided the client cert when multiple are available for the user?
The answer is the server that is requesting the client cert for authentication has an option to ask for the cert that is signed by a specific Distinguished Name CA, which is part of the server communication. You can see this in wireshark capture, No need for SSL decryption, as the DN CA information is part of server hello messages which happens before the encryption beings between the server and client.


As long as the browser can find the matching cert with a private key signed by DN CA, it has the ability to silently provide to the server based on browser settings. If there is more than one issued by the DN CA, it will prompt the user to make the selection. If the browser finds a matching cert but it doesn’t have the private key, it will skip that cert.
If the server just asks for client cert without providing DN CA, then the browser displays all available client cert that have the private key and prompts the user to make the selection.
Powershell Get Citrix Current Session Logon Time
$code = @'
using System;
using System.Runtime.InteropServices;
namespace WFAPI
{
public enum WF_INFO_CLASS
{
WFVersion, // OSVERSIONINFO
WFInitialProgram,
WFWorkingDirectory,
WFOEMId,
WFSessionId,
WFUserName,
WFWinStationName,
WFDomainName,
WFConnectState,
WFClientBuildNumber,
WFClientName,
WFClientDirectory,
WFClientProductId,
WFClientHardwareId,
WFClientAddress,
WFClientDisplay,
WFClientCache,
WFClientDrives,
WFICABufferLength,
WFLicenseEnabler,
RESERVED2,
WFApplicationName,
WFVersionEx,
WFClientInfo,
WFUserInfo,
WFAppInfo,
WFClientLatency,
WFSessionTime,
WFLicensingModel
}
[StructLayout(LayoutKind.Sequential)]
public struct WF_SESSION_TIME
{
public long ConnectTime;
public long DisconnectTime;
public long LastInputTime;
public long LogonTime;
public long CurrentTime;
}
public class Program
{
[DllImport("wfapi64.dll", CharSet=CharSet.Unicode,SetLastError=true)]
public static extern bool WFQuerySessionInformation(System.IntPtr hServer, int sessionId, WF_INFO_CLASS WFInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);
[DllImport("wfapi64.dll", ExactSpelling = true, SetLastError = false)]
public static extern void WFFreeMemory(IntPtr memory);
public const int WF_CURRENT_SESSION = -1;
public static string GetClientName()
{
System.IntPtr buffer = IntPtr.Zero;
uint bytesReturned;
try
{
bool sessionInfo = WFQuerySessionInformation(System.IntPtr.Zero, WF_CURRENT_SESSION, WF_INFO_CLASS.WFClientName, out buffer, out bytesReturned);
return Marshal.PtrToStringUni(buffer);
}
catch
{
return string.Empty;
}
finally
{
WFFreeMemory(buffer);
buffer = IntPtr.Zero;
}
}
public static string getSessionLogonTime()
{
System.IntPtr buffer = IntPtr.Zero;
string sessionLogonTime = "";
uint bytesReturned = 0;
try
{
bool sessionInfo = WFQuerySessionInformation(System.IntPtr.Zero, WF_CURRENT_SESSION, WF_INFO_CLASS.WFSessionTime, out buffer, out bytesReturned);
if(sessionInfo)
{
WF_SESSION_TIME WFSessionTime = (WF_SESSION_TIME)Marshal.PtrToStructure(buffer, typeof(WF_SESSION_TIME));
sessionLogonTime = Convert.ToString(DateTime.FromFileTime(WFSessionTime.LogonTime));
}
}
catch
{
return string.Empty;
}
finally
{
WFFreeMemory(buffer);
buffer = IntPtr.Zero;
}
return sessionLogonTime;
}
}
}
'@
Add-Type -TypeDefinition $code -Language CSharp
[WFAPI.Program]::getSessionLogonTime()
WFAPI disconnect sessions that exceed certain idle time [override policy]
/*
Author - Siva Mulpuru [sivamulpuru.com]
Usage - WFAPI "maxIdleTimeInMin" "GatewaySNIP"
Comment - Workaround for Citrix Virtal Apps [FKA XenApp] to disconnect AGW connections when exceed passed value. Designed to run as scheduled job.
Allows non AGW connections to use serverIdleTimeout policy while providing override for AGW connections.
*/
using System;
using System.Runtime.InteropServices;
namespace WFAPI
{
class Program
{
[DllImport("wfapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool WFQuerySessionInformation(System.IntPtr hServer, int sessionId, WF_INFO_CLASS WFInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);
[DllImport("wfapi.dll", ExactSpelling = true, SetLastError = false)]
public static extern void WFFreeMemory(IntPtr memory);
[DllImport("wfapi.dll", ExactSpelling = true, SetLastError = false)]
public static extern bool WFDisconnectSession(System.IntPtr hServer, int sessionId, bool bWait);
[DllImport("wfapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool WFEnumerateSessions(IntPtr hServer,
[MarshalAs(UnmanagedType.U4)] Int32 Reserved,
[MarshalAs(UnmanagedType.U4)] Int32 Version,
ref IntPtr pSessionInfo,
[MarshalAs(UnmanagedType.U4)]
ref Int32 pCount);
public enum WF_INFO_CLASS
{
WFVersion,
WFInitialProgram,
WFWorkingDirectory,
WFOEMId,
WFSessionId,
WFUserName,
WFWinStationName,
WFDomainName,
WFConnectState,
WFClientBuildNumber,
WFClientName,
WFClientDirectory,
WFClientProductId,
WFClientHardwareId,
WFClientAddress,
WFClientDisplay,
WFClientCache,
WFClientDrives,
WFICABufferLength,
WFLicenseEnabler,
RESERVED2,
WFApplicationName,
WFVersionEx,
WFClientInfo,
WFUserInfo,
WFAppInfo,
WFClientLatency,
WFSessionTime,
WFLicensingModel
}
[StructLayout(LayoutKind.Sequential)]
public struct WF_SESSION_TIME
{
public long ConnectTime;
public long DisconnectTime;
public long LastInputTime;
public long LogonTime;
public long CurrentTime;
}
public enum WF_CONNECTSTATE_CLASS
{
WFActive, // User logged on to WinStation
WFConnected, // WinStation connected to client
WFConnectQuery, // In the process of connecting to client
WFShadow, // Shadowing another WinStation
WFDisconnected, // WinStation logged on without client
WFIdle, // Waiting for client to connect
WFListen, // WinStation is listening for connection
WFReset, // WinStation is being reset
WFDown, // WinStation is down due to error
WFInit // WinStation in initialization
}
[StructLayout(LayoutKind.Sequential)]
public struct WF_CLIENT_ADDRESS
{
public int AddressFamily;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] Address;
}
[StructLayout(LayoutKind.Sequential)]
public struct WF_SESSION_INFO
{
public Int32 SessionID;
[MarshalAs(UnmanagedType.LPStr)]
public String pWinStationName;
public WF_CONNECTSTATE_CLASS State;
}
static DateTime getSessionLastInteractionTime(int sessionid)
{
IntPtr buffer = IntPtr.Zero;
DateTime lastInteractionTime = DateTime.Now;
uint bytesReturned = 0;
try
{
if (WFQuerySessionInformation(IntPtr.Zero, sessionid, WF_INFO_CLASS.WFSessionTime, out buffer, out bytesReturned))
{
WF_SESSION_TIME WFSessionTime = (WF_SESSION_TIME)Marshal.PtrToStructure(buffer, typeof(WF_SESSION_TIME));
lastInteractionTime = DateTime.FromFileTime(WFSessionTime.LastInputTime);
}
}
finally
{
WFFreeMemory(buffer);
}
return lastInteractionTime;
}
static string getClientIP(int sessionid)
{
IntPtr buffer = IntPtr.Zero;
uint bytesReturned;
string clientIPAddress = "0.0.0.0";
try
{
if (WFQuerySessionInformation(IntPtr.Zero, sessionid, WF_INFO_CLASS.WFClientAddress, out buffer, out bytesReturned))
{
WF_CLIENT_ADDRESS si = (WF_CLIENT_ADDRESS)Marshal.PtrToStructure((System.IntPtr)buffer, typeof(WF_CLIENT_ADDRESS));
clientIPAddress = si.Address[2] + "." + si.Address[3] + "." + si.Address[4] + "." + si.Address[5];
}
}
finally
{
WFFreeMemory(buffer);
}
return clientIPAddress;
}
static void Main(string[] args)
{
int maxIdleTimeInMin = Convert.ToInt32(args[0]);
string gatewaySNIP = args[1];
//Console.WriteLine("{0} {1}", maxIdleTimeInMin, gatewaySNIP);
IntPtr pSessionInfo = IntPtr.Zero;
int Count = 0;
if (WFEnumerateSessions(IntPtr.Zero,0,1,ref pSessionInfo,ref Count))
{
try
{
Int32 dataSize = Marshal.SizeOf(typeof(WF_SESSION_INFO));
Int64 current = (int)pSessionInfo;
for (int i = 0; i < Count; i++)
{
WF_SESSION_INFO si = (WF_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WF_SESSION_INFO));
current += dataSize;
if (si.State == WF_CONNECTSTATE_CLASS.WFActive)
{
//Console.WriteLine("{0} {1} {2}", si.SessionID, getSessionLastInteractionTime(si.SessionID), getClientIP(si.SessionID));
if(gatewaySNIP.CompareTo(getClientIP(si.SessionID)) == 0 && (DateTime.Now - getSessionLastInteractionTime(si.SessionID)).TotalMinutes > maxIdleTimeInMin)
{
bool result = WFDisconnectSession(IntPtr.Zero, si.SessionID, false);
Console.WriteLine("{0} {1} is {2}", "Disconnect result for SessionID", si.SessionID, result);
}
}
}
}
finally
{
WFFreeMemory(pSessionInfo);
}
}
}
}
}
Automate Citrix Session Launch for Load tests or health probing
Requirements
- Citrix Workspace app Version 1808 for Windows or later.
- Storefront Store needs to have http auth module enabled.
Step 1: Retrieve Resource Name of the app/desktop you would like to automate
“C:\Program Files (x86)\Citrix\ICA Client\AuthManager\storebrowse.exe” -U <username> -P <password> -D <domain> -M 0x2000 -E “https://<storefrontserver>/Citrix/<storename>/discovery”
First column of the output would have the resource name. In My case it’s a windows 10 VDI >
‘Controller.W10-QA $S2-4‘ ‘W10-QA’ ‘\’ ” https://storefront/Citrix/XDOnly-Internal/resources/v2/Q29udHJvbGxlci5XMTAtUUEgJFMyLTQ-/launch/ica
Step 2: Get the ICA file for the desired Resource Name
“C:\Program Files (x86)\Citrix\ICA Client\AuthManager\storebrowse.exe” -U <username> -P <password> -D <domain> -L “Controller.W10-QA $S2-4” “https://<storefrontserver>/Citrix/<storename>/discovery”
-L with resource name will retrieve the ICA file and save it to %AppData%\Local\Citrix\storebrowse\cache\launch.ica
Step 3: Launch the ICA file
%AppData%\Local\Citrix\storebrowse\cache\launch.ica
Reference > https://docs.citrix.com/en-us/citrix-workspace-app-for-windows/store-browse.html
How to prevent creation of System and Recovery partitions for VDI Master
During the initial windows install screen, press Shift-F10 to launch the command window, and using diskpart create primary partition.
select disk 0
create partition primary
exit
ref > https://www.howtogeek.com/192772/what-is-the-system-reserved-partition-and-can-you-delete-it/