Script: Get-CsVoiceCalls.ps1 – See Realtime Call Info From Lync Management Shell
Description
I had a need to see real-time call data. Lync MVP/MCM Tom Pacyk has a really cool script for logging concurrent calls to .csv over time by querying perfmon counters at regular intervals. That works great when you’re trying to determine concurrent for scale planning. But I wanted to see who was on the phone – not just the number of current calls.
This script will look at the LCSCDR database, which is installed when you install a Monitoring Server in your topology. The script gathers information about current calls in progress, and displays them in list style. It’s fairly basic.
As you can see, the list shows the two users in the call, their ID number, who initiated the call, when it was made, etc. In this screen shot, User 1 was connected from outside the environment (IsUser1Internal is false), while User2 was in a company office with WAN connectivity to the Lync infrastructure (IsUser2Internal is True).
If a user has initiated or received a call to/from a PSTN number, then User2Uri will read “PSTN call”.
As mentioned above, a Monitoring Server does need to be installed in your topology so that the LCSCDR database is created and updated. But the script can be run from anywhere.
You could also wrap this into a function and toss it in your PowerShell profile to make easily accessible.
This script was more of a proof of concept into querying SQL from PowerShell, and the required query. If you have suggestions, let me know!
Installation
Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.
Download the script from below to a server with PowerShell. Run the script in PowerShell using
Get-CsVoiceCalls.ps1 -server [SQL server]
If the lcscdr database is in a named instance, specify the instance
Get-CsVoiceCalls.ps1 -server [SQL server] -instance [instance]
Where [SQL server] is the FQDN of the database server containing your LCSCDR database.
You can also hard code the SQL server FQDN and the instance in the script. Look for
[string]$server,
and change it to include the SQL server FQDN, such as
[string]$server = "sqlserver.domain.local",
and look for
[string]$instance,
and change it to include the instance name, such as
[string]$instance = "Lync",
And then you can just call the script by name using
Get-CsVoiceCalls.ps1
There is also full comment based help, using
Get-Help Get-CsVoiceCalls.ps1
Donations
I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at https://www.paypal.me/PatRichard. Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.
Known Issues
- The logic that looks for the database in named instances still needs more testing. If you run it in an environment where you have the lcscdr database in a named instance, please let me know your results.
- I tried to adapt to correcting the time display. The data is apparently stored in SQL in GMT. So I’ve coded the script to display it according to the local time zone (according to Windows). Let me know if your results aren’t as expected.
- If a user receives a call from a PSTN number, the SessionStartedById field is blank. Working around that has been problematic, but I’m still trying.
Download
Changelog
See the changelog for this script to see what has changed between versions.
Very cool script Pat, I tried running it against our regional monitoring databases, and got this error when running against the region where the LCSCDR is in the default instance:
Exception calling “ExecuteReader” with “0” argument(s): “The server principal “DOMAIN\MyLyncAdminAccount” is not able to access the database “LcsCDR” under the current security context.”
At C:\Powershell\Get-CsVoiceCalls.ps1:86 char:32
+ $data = $sqlCmd.ExecuteReader <<<< ()
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
When I tried running against the other regions where there is a SQL cluster/instance, I got this:
Get-WmiObject : Invalid namespace
At C:\Powershell\Get-CsVoiceCalls.ps1:67 char:20
+ if ((Get-WMIObject <<<< -Class "Win32_PerfFormattedData_MsSqlServer_SqlServerDatabases" -Computer $server | ? {$
_.name -match "lcscdr"}) -ne $null){
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], ManagementException
+ FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
Lastly, I modified the script into a v1.3 version that changes the prompt to allow you to input the site name instead of the database instance, since it's usually easier for me to remember that then wherever the SQL DBs are. I added the section to validate the central site name and determine the SQL FQDN of the monitoring database from that:
[CmdletBinding(SupportsShouldProcess = $True)]
Param (
[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[string]$site,
[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[string]$database = “master”,
[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[string]$query2 = “select s.[SessionIdTime],u1.UserUri as User1Uri,[User1Id],u2.UserUri as User2Uri,[User2Id],[User1EndpointId],[User2EndpointId],[TargetUserId],[SessionStartedById],[OnBehalfOfId],[ReferredById],[IsUser1Internal],[IsUser2Internal],[ResponseTime],[DiagnosticId] FROM [LcsCDR].[dbo].[SessionDetails] s left outer join [LcsCDR].[dbo].[Users] u1 on s.User1Id = u1.UserId left outer join [LcsCDR].[dbo].[Users] u2 on s.User2Id = u2.UserId where ResponseCode=200 and s.SessionEndTime is null and (User1Id is null or User2Id is null or User1Id != User2Id) and MediaTypes = 16 and s.[SessionIdTime] >= dateadd(dd,0, datediff(dd,0, getDate()))”,
[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[string]$query = “select s.[SessionIdTime],u1.UserUri as User1Uri,[User1Id],u2.UserUri as User2Uri,[User2Id],[TargetUserId],[SessionStartedById],[OnBehalfOfId],[ReferredById],[IsUser1Internal],[IsUser2Internal],[ResponseTime],[DiagnosticId] FROM [LcsCDR].[dbo].[SessionDetails] s left outer join [LcsCDR].[dbo].[Users] u1 on s.User1Id = u1.UserId left outer join [LcsCDR].[dbo].[Users] u2 on s.User2Id = u2.UserId where ResponseCode=200 and s.SessionEndTime is null and (User1Id is null or User2Id is null or User1Id != User2Id) and MediaTypes = 16 and s.[SessionIdTime] >= dateadd(dd,0, datediff(dd,0, getDate()))”,
[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[string]$ConnectionName = “Lync SQL Query”,
[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[int]$CommandTimeout = 15,
[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $False, Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[ValidateRange(-12,14)]
[int]$GMTOffsetHours = ([System.TimeZoneInfo]::Local).BaseUtcOffset.Hours,
[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $False, Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[ValidateRange(0,60)]
[int]$GMTOffsetMinutes = ([System.TimeZoneInfo]::Local).BaseUtcOffset.Minutes
)
if ((Get-Date).IsDaylightSavingTime()){
$GMTOffsetHours = $GMTOffsetHours+1
}
Write-Verbose “Validating that Central Site exists”
if (Get-CsService -MonitoringDatabase | Where-Object {$_.SiteId -like “*$site”}) {
Write-Verbose “Central Site $site exists”
$MonServerDB = Get-CsService -MonitoringDatabase | Where-Object {$_.SiteId -like “*$site”}
$serverFQDN = $MonServerDB.Identity.Replace(“MonitoringDatabase:”,””)
if ($MonServerDB.SqlInstanceName -ne $null) {
$server = $serverFQDN + “\” + $MonServerDB.SqlInstanceName
} else {
$server = $serverFQDN
}
Write-Verbose “SQL Instance is calculated as $server”
} else {
Write-Host “Central Site $site was not found”
Exit
}
You’re welcome to edit further, I’m just an intermediate Powershell hack… 🙂
Here is the error I receive when using a SQL Named Instance. Looks like the script is indeed only looking for a Defaul Instance. But maybe there is another method to allow for the definition of the named instance. Perhaps an additional parameter that if provided, uses a different connection logic.
PS Microsoft.PowerShell.Core\FileSystem::\\domain.local\lync\LyncShare\Install\Scripts> .\Get-C
sVoiceCalls.ps1 -server RTLCDB1\LyncFE
Security Warning
Run only scripts that you trust. While scripts from the Internet can be useful, this script can potentially harm your computer. Do you want to run
\\domain.local\lync\LyncShare\Install\Scripts\Get-CsVoiceCalls.ps1?
[D] Do not run [R] Run once [S] Suspend [?] Help (default is “D”): r
Get-WmiObject : Invalid namespace
At \\realtime.com\lync\LyncShare\Install\Scripts\Get-CsVoiceCalls.ps1:67 char:20
+ if ((Get-WMIObject <<<< -Class "Win32_PerfFormattedData_MsSqlServer_SqlServerDatabases" -Computer $server | ? {$_.name -match "lcscdr"}) -ne $
null){
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], ManagementException
+ FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
Database does not exist
Ok, thanks for letting me know. I didn’t think it would work on a named instance. I’ll see if I can figure out a solution.
Hi Pat,
Neat script. I’ve tried running it on my environment, but the LCSCDR is not the default SQL instance. The script just says “database does not exist”. Any way around this? I’ve tried defining the instance when running the script but it doesn’t like that. Thanks,
Not yet. I’m gonna try and figure that out this weekend.
Try this and let me know the results. Open the script, find the line
[string]$ConnString = "Server=$server;Integrated Security=SSPI;Database=$database;Application Name=$connectionName"
and change it to
[string]$ConnString = "Server=$server\instancename;Integrated Security=SSPI;Database=$database;Application Name=$connectionName"
where “instancename” is the named instance. If that works, I’ll make that a little more graceful.
Pat, That last comment helps a bit with it, I think the trick now is that the WMI class you’re looking for could change depending on the instance name. We have lcscdr databases in different instance names depending on region, and in one region the class to get is this (Win32_PerfFormattedData_MSSQLPROD4_MSSQLPROD4Databases), where in another is this (Win32_PerfFormattedData_MSSQLPROD2_MSSQLPROD2Databases). I’ll see if I can combine some of your work and some the work I did to query the instance name to see if we can create the appropriate WMI class to query.
Okay. I’m working through the assumption that the connection string I supplied would work. I have most of that part done. I might pull out the WMI call or work around it if a named instance is specified.
Let me know what you find.
v1.4 was released earlier this week and supports named instances.
Great script 😀
We have noticed that the scrip don’t display Group Conversations and conferences
Maybe we get this in version 1.5 ? 😉
Thanks in avance
Yep noticed that when the caller is admitted to a conference the calls disappear. We are actually interested in conference attendee numbers, not necessarily calls to PSTN. Any pointers?
This script displays current/active calls.
Is there a script that will generate a log of all calls specified with a time period?
The reporting tools in Lync only shows a maximum of 1000 records when the time period specified has more than 1000 records. I want to use the CDR Reports for billing purposes on a monthly basis.
Not that I’ve done.
Thank you Pat for the interesting script.
Unfortunately, on my system, this script reports an active call only after ten minutes it was established. Therefore calls that last less than 10 minutes are never reported.
Do you have an idea why this occurs?
Is there any setting that I can change to fix this issue?
Hello Pat,
I would like to ask you if it is possible for you to send me script for Lync2010 which should be available at http://www.confusedamused.com/notebook/monitoring-ocs-and-lync-peak-call-capacity/#comment-2604. mpaulovic AT yahoo dot com
Thank you!
Michal
I checked with Tom, and he is going to work on getting his scripts back online.
The script will not work properly with Lync 2013 as the logging has changed and the database entries are now only written at the end of the call.
Hi great script but i found a small bug in the following line.
Write-Verbose “GMT Offest: $GMTOffsetHours:$GMTOffsetMinutes”
You need an ` in front of the : to escape the character.
Thanks. I’ve updated my version and will have a new version released soon. Thanks for the info.
Hello Pat, was there a newer version fro Lync2013/Sfb2015 server ever released?
I never investigated what the correct SQL query would be. If anyone wants to take a stab at it….
original query is:
select s.[SessionIdTime],u1.UserUri as User1Uri,[User1Id],u2.UserUri as User2Uri,[User2Id],[TargetUserId],[SessionStartedById],[OnBehalfOfId],[ReferredById],[IsUser1Internal],[IsUser2Internal],[ResponseTime],[DiagnosticId] FROM [LcsCDR].[dbo].[SessionDetails] s left outer join [LcsCDR].[dbo].[Users] u1 on s.User1Id = u1.UserId left outer join [LcsCDR].[dbo].[Users] u2 on s.User2Id = u2.UserId where ResponseCode=200 and s.SessionEndTime is null and (User1Id is null or User2Id is null or User1Id != User2Id) and MediaTypes = 16 and s.[SessionIdTime] >= dateadd(dd,0, datediff(dd,0, getDate()))
@Pat Richard
Also had another query that I was playing around with, but doesn’t seem to work:
select s.[SessionIdTime],u1.UserUri as User1Uri,[User1Id],u2.UserUri as User2Uri,[User2Id],[User1EndpointId],[User2EndpointId],[TargetUserId],[SessionStartedById],[OnBehalfOfId],[ReferredById],[IsUser1Internal],[IsUser2Internal],[ResponseTime],[DiagnosticId] FROM [LcsCDR].[dbo].[SessionDetails] s left outer join [LcsCDR].[dbo].[Users] u1 on s.User1Id = u1.UserId left outer join [LcsCDR].[dbo].[Users] u2 on s.User2Id = u2.UserId where ResponseCode=200 and s.SessionEndTime is null and (User1Id is null or User2Id is null or User1Id != User2Id) and MediaTypes = 16 and s.[SessionIdTime] >= dateadd(dd,0, datediff(dd,0, getDate()))
@Pat Richard
Thanks Pat for the feedback! Due to MSFT messing with the CDR schema in every major release, probably it is a huge effort to develop the solid query for Lync2013/Sfb2015…
Is there an updated version of this (or in the making?)? Can’t get this to show ‘realtime’ data on newest SfB onprem.
Not until I can find a valid SQL query for SfB.
Hi Pat
Had an error:
At C:\appl\tools\Get-CsVoiceCalls.ps1:78 char:29
+ Write-Verbose “GMT Offest: $GMTOffsetHours:$GMTOffsetMinutes”
+ ~~~~~~~~~~~~~~~~
Variable reference is not valid. ‘:’ was not followed by a valid variable name character. Consider using ${} to delimit the name.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : InvalidVariableReferenceWithDrive
Fixed it with masking the : with `
Write-Verbose “GMT Offest: $GMTOffsetHours`:$GMTOffsetMinutes”
Regards,
Manfred
@Manfred
hi, i am seeing the same error returned.
ITNOA
Hi Pat
Please upload it to GitHub to we can contribute it and support it
thx
ITNOA
Hi Pat
Please upload this script to GitHub and added to powershell script gallery to we can contribute it and support it.
thx