Add-WindowsCapability failed

Add-WindowsCapability might fail in some scenarios in SCCM environments. Try the following to workaround the issue

Set-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name UseWuServer -Value 0 -Force
Get-Service wuauserv | Restart-Service -Force
#Try Add-WindowsCapability with the feature you would like to install, CertificateServices is listed as example 
Add-WindowsCapability -Online -Name Rsat.CertificateServices.Tools~~~~0.0.1.0

RSAT feature list

    • Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0
    • Rsat.BitLocker.Recovery.Tools~~~~0.0.1.0
    • Rsat.CertificateServices.Tools~~~~0.0.1.0
    • Rsat.DHCP.Tools~~~~0.0.1.0
    • Rsat.Dns.Tools~~~~0.0.1.0
    • Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0
    • Rsat.FileServices.Tools~~~~0.0.1.0
    • Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0
    • Rsat.IPAM.Client.Tools~~~~0.0.1.0
    • Rsat.LLDP.Tools~~~~0.0.1.0
    • Rsat.NetworkController.Tools~~~~0.0.1.0
    • Rsat.NetworkLoadBalancing.Tools~~~~0.0.1.0
    • Rsat.RemoteAccess.Management.Tools~~~~0.0.1.0
    • Rsat.RemoteDesktop.Services.Tools~~~~0.0.1.0
    • Rsat.ServerManager.Tools~~~~0.0.1.0
    • Rsat.Shielded.VM.Tools~~~~0.0.1.0
    • Rsat.StorageMigrationService.Management.Tools~~~~0.0.1.0
    • Rsat.StorageReplica.Tools~~~~0.0.1.0
    • Rsat.SystemInsights.Management.Tools~~~~0.0.1.0
    • Rsat.VolumeActivation.Tools~~~~0.0.1.0
    • Rsat.WSUS.Tools~~~~0.0.1.0

    Export and Import user assignments from one Citrix RemotePC to another

    $srcRemotePC = 'DomainName\65TELW19'
    $destRemotePC = 'DomainName\65TELW20'
    Get-BrokerUser -PrivateDesktopUid (get-BrokerPrivateDesktop -MachineName $srcRemotePC ).Uid | | %{ if($_.Upn -ne "") {
    Add-BrokerUser -Machine $destRemotePC -Name $_.Upn
    }}

    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

    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

    WordPress Appliance - Powered by TurnKey Linux