KQL to report on user login duration

Copper Contributor

We are seeing an issue with login delays for users, whereby it sometimes takes many minutes for a user to be presented with a desktop.

 

As part of troubleshooting this issue, I'd like to create a KQL query that will generate data showing the amount of time that passes from a user clicking the relevant hostpool icon to getting to a desktop. Reading the following MS article Monitor Azure Virtual Desktop glossary - Azure | Microsoft Learn I've gained some understanding on the logon stages and how information is logged.

 

Amending scripts I've found on the web, I'm able to generate a report which shows login times :

WVDConnections
| where Type =~"WVDConnections" and State =~ "Started"
| project SessionHostName , UserName ,CorrelationId, TimeLogonStarted=TimeGenerated
| join kind = leftsemi
(
// Only include connections that actually reached the host to prevent short (failed) attempts from skewing the data
WVDCheckpoints
| where Source == "RDStack"
and Name == "RdpStackConnectionEstablished"
)
on CorrelationId
| join kind= leftouter (
WVDCheckpoints
) on CorrelationId
| extend DurationFromLogon=datetime_diff("Second",TimeGenerated, TimeLogonStarted)
| where Name=~"ShellReady"
// exclude diviations where ShellReady occurs at start of WVDCheckPoints
and TimeGenerated > TimeLogonStarted
| project UserName, SessionHost=SessionHostName, TimeLogonStarted, TimeLogonEnded=TimeGenerated, DurationFromLogon
| order by SessionHost, TimeLogonEnded desc

 

Within AVD Monitoring, under the Connection Performance section of Insights, there is a section for the Top 20 instances of highest time to connect. I've amended the KQL of this to show all connection times within a time period:

let newSessions = {NewSessions};
WVDConnections
| where _ResourceId =~ "{HostPool}"
| as ConnectionData
| where State == "Started"
| where TimeGenerated {timedrilldown}
| join kind = leftsemi
(
// Only include connections that actually reached the host to prevent short (failed) attempts from skewing the data
WVDCheckpoints
| where Source == "RDStack" and Name == "RdpStackConnectionEstablished" and _ResourceId =~ "{HostPool}"
) on CorrelationId
| join kind=leftsemi // remove connections that do not have LogonDelay as we cannot show details
( WVDCheckpoints
| where _ResourceId =~ "{HostPool}" and Name == "LogonDelay"
| extend LogonType = tostring(Parameters.LogonType)
| where (newSessions and LogonType == "DirectSession") or (not(newSessions) and LogonType =="TemporarySession")
| project LogonDelay= TimeGenerated, CorrelationId
) on CorrelationId
| join kind = leftsemi
(
WVDCheckpoints
| where _ResourceId =~ "{HostPool}"
| where Name == "LoadBalancedNewConnection"
| extend LoadBalanceOutcome=Parameters.LoadBalanceOutcome
| where (newSessions and LoadBalanceOutcome == "NewSession") or (not(newSessions) and LoadBalanceOutcome in ('Disconnected', 'Active'))
) on CorrelationId
| join kind = inner
(
WVDCheckpoints // new session
| where newSessions
| where _ResourceId =~ "{HostPool}"
| where Name =~ "ShellReady" or
( Name =~ "LaunchExecutable" and Parameters.connectionStage == "RdpShellAppExecuted" or Name=~"RdpShellAppExecuted")
| project ProductiveTime=TimeGenerated, CorrelationId
| summarize ProductiveTime=min(ProductiveTime) by CorrelationId
| union (
ConnectionData // existent Sessions
| where not(newSessions)
| where State == "Connected"
| project ProductiveTime=TimeGenerated, CorrelationId
)
) on CorrelationId
| join kind = leftouter
(
WVDCheckpoints
| where _ResourceId =~ "{HostPool}"
| where Name =~ "OnCredentialsAcquisitionCompleted"
| project CorrelationId, credaquire = Parameters.DurationMS
//OnCredentialsAcquisitionCompleted 2021-09-13T16:48:50.4440000Z Client {"CredentialType":"SavedPassword","DurationMS":"9","Success":"True"}
) on CorrelationId
| join kind = leftouter
(
WVDCheckpoints
| where _ResourceId =~ "{HostPool}"
| where Name =~ "SSOTokenRetrieval"
| project CorrelationId, ssotokeretrieval = Parameters.DurationMS
) on CorrelationId
| extend ProductiveTime = (ProductiveTime - TimeGenerated) / 1s - (coalesce(credaquire,0)/1000) - (coalesce(ssotokeretrieval,0)/1000)
| where ProductiveTime > 0
| project UserName, TimeGenerated, Hostname = trim_end("[.].*", SessionHostName), ProductiveTime, CorrelationId
| order by TimeGenerated

 

 

However, reviewing the data generated by the above queries, there are inconsistencies between the data that is returned.

 

For example, the first KQL query will return user logins that are not shown in the AVD Insights KQL.

 

Could anyone advise on why this may be, or perhaps someone has some KQL that achieves my aim?

2 Replies
There could be several reasons why there are inconsistencies between the data returned by the two KQL queries. One possible reason is that the queries are using different filters or criteria to select and join the data. Another reason could be that the data is being sampled differently or at different intervals, leading to variations in the results.

To troubleshoot this issue, you may want to start by comparing the filters and criteria used in the two KQL queries to see if they are consistent. You may also want to check the sampling intervals for the data to see if they match. Additionally, you may want to review the logs and metrics to ensure that they are being collected and recorded accurately and consistently.

As for a KQL query that achieves your aim, it looks like you are already using two different queries to capture login times. One query is focused on logon stages and the other on connection performance. Depending on your specific needs and environment, you may want to try adjusting the filters, criteria, and sampling intervals in these queries to generate more consistent and accurate results. Alternatively, you could try combining elements of both queries to create a new query that captures both logon stages and connection performance in a more comprehensive way.



Hello @JonKilner 

 

Do you have solved the problem.

 

I use this code;

 

WVDConnections
| where TimeGenerated > ago(24h)
| where State == "Started"
| where SessionHostName has "<SessionHostName>"
| project CorrelationId, UserName, ConnectionType, StartTime=TimeGenerated, _ResourceId
| join (WVDConnections
| where State == "Connected"
| project EndTime=TimeGenerated, CorrelationId)
on CorrelationId
| project Duration = EndTime - StartTime, UserName, _ResourceId
| summarize AvgDuration=avg(Duration) by _ResourceId, Username
| extend Multi=split(_ResourceId, "/")
| project AvgDuration, ResourceGroup=Multi[4], HostPool=Multi[8]
| sort by AvgDuration desc
| where AvgDuration > timespan(00:01:00)