SMDR phone logging
2012-Apr-09, Monday 10:59 amAmong the hodge-podge of responsibilities I have at my job, I also manage 6 PBX phone systems of the Mitel / Intertel CS-5000 family. Thanks to a useful hint that my boss found in a Perl script via Google, I was able to establish phone system logging using Powershell. My programming will save us over $10,000 that we would have been charged by our vendor. That's half my annual salary, so I've earned my keep for a while. :)
I've redacted some of the details from my code for security reasons, but I wanted to make the source code public so it's easier for the next person to find useful material through a web search engine.
You turn on SMDR by using the DbStudio Session Manager software that comes with your PBX system. That application directory also contains \DiagMon\Diagmon.exe, a program that can also be used to monitor (but not log, that I can tell) SMDR output from your phone panels. I intend to use DiagMon with packet sniffing software to reverse engineer the protocol for handling password-enabled SMDR. It's really hard to come by programmer-focused PBX documentation. I even contacted Mitel, but they defer to local vendors who have a financial disincentive to provide such documentation. :(# log-smdr.ps1 # This Powershell script connects to a Mitel/Intertel CS-5000 series phone system, # directs it to open its SMDR stream, then logs the results to a text file. # Station Message Detail Recording (SMDR) is a system feature that provides # a detailed record of outgoing and incoming calls. # This script is intended to be restarted at midnight each night. # Turning on SMDR reporting is a two-step process in DbStudio Session Manager. # 1) System / Maintenance / SMDR, set "SMDR Output Active" to yes # ("Output Port" should already be set to "NONE") # 2) System / Sockets / SMDR, set "Enable" to yes # (System / Cabinet / Sockets / SMDR, in some older systems) # http://technologyordie.com/mitel-5000-smdr-script (example perl script) # http://www.codeproject.com/Articles/12893/TCP-IP-Chat-Application-Using-C param ([string]$site='needparm', [string]$logdir='C:\script\log\', [boolean]$debug=$false) # convert $site parameter into ip/port combination switch ($site) { 'S1' { $ip='192.168.0.1'; $port=4000 } 'S2' { $ip='192.168.0.1'; $port=4000 } 'S3' { $ip='192.168.0.1'; $port=4000 } default { $ip='192.168.0.1'; $port=4000 } } # find a new filename for logging this session $scriptps1 = $MyInvocation.MyCommand.Name $scriptname = $scriptps1.substring(0,($scriptps1.length - 4)) $datelog = get-date -f 'yyyyMMdd' $version = 0 do { $version += 1 $filelog = $logdir + $scriptname + '.' + $datelog + '.' + $site + '.' + $version + '.txt' } while (test-path $filelog) set-content $filelog $null if ($debug) {write-host 'logfile:' $filelog} # create a tcp socket and a destination # fyi, the TcpClient object did not work with SMDR (perhaps some default values interfered?) # so I took it down a layer to the Socket object which works nicely $sockfam = [System.Net.Sockets.AddressFamily]::InterNetwork $socktype = [System.Net.Sockets.SocketType]::Stream $sockpro = [System.Net.Sockets.ProtocolType]::TCP $socket = New-Object System.Net.Sockets.Socket $sockfam, $socktype, $sockpro $ipaddr = [System.Net.IpAddress]::Parse($ip) $ipdest = New-Object System.Net.IPEndPoint $ipaddr, $port # establish the connection if ($debug) {write-host 'connecting:' $ip ':' $port} $socket.Connect($ipdest) if ($socket.Connected -eq $false) { if ($debug) {write-host 'connection: FAILED'} return } if ($debug) {write-host 'connection: OPEN'} # send the SMDR command ASCII character string (no password) # 2 002 02 00000010 STX Start of Text # 0 000 00 00000000 NUL Null char # 132 204 84 10000100 „ Double low-9 quotation mark $cmd = @([byte]2,[byte]0,[byte]0,[byte]0,[byte]132,[byte]0) $socket.Send($cmd) if ($debug) {write-host 'connection: CMD SENT'} # the socket's .Receive() will Block, causing the program to freeze until bytes are received, # so set up a stream which provides the useful .DataAvailable property instead # incoming records are always 86 bytes long $bytes = new-object System.Byte[] 86 $stream = new-object System.Net.Sockets.NetworkStream($socket) do { start-sleep -m 1000 if ($stream.DataAvailable) { $socket.Receive($bytes) | out-null # script hangs here until bytes are received $result = [System.Text.Encoding]::ASCII.GetString($bytes[0..84]) # ignore trailing CR/LF if ($debug) {write-host $result} add-content $filelog $result # append data to text log file } $datetest = get-date -f 'yyyyMMdd' } while ($datetest -eq $datelog) if ($debug) {write-host 'connection: CLOSING'} $socket.close() if ($debug) {write-host 'connection: CLOSED'} $socket = $null
Here's a redacted version of the debug output from this script:
My next project will take these .TXT log files, parse them, and store the data fields in a SQL Server table. Then I can do lots of fun stuff like calculate the average length of time that people are spending in our phone tree before they reach a live person, or determine if people are hanging up in frustration before reaching any person.connecting to 192.168.0.1 : 4000 connection: OPEN 6 connection: CMD SENT Q Station Message Detailed Recording 17:01:24 04-07-2012 Q TYP EXT# TRUNK DIALED DIGITS START ELAPSED COST ACCOUNT CODE Q R NET 1234 P0001 94567 17:11 00:03:34 $00.00 R NET 1234 P0001 95678 17:16 00:00:09 $00.00 R NET 2345 P0001 96789 17:21 00:00:14 $00.00 R IN 3456 90001 17:19 00:04:56 $00.00