Skip to main content

Outlook Anywhere logon report powershell GUI

One of all the more useful reasons to learn scripting is the ability to turn information that is recorded in one format in a seemless useless rable of bits, into a format this is more useful to ordinary humans in everyday situations. One the examples of this are the IIS logs which in Exchange contain information about users accessing OWA, Accessing ActiveSync and Outlook Anywhere. Like everything in IT there are a few ways of tackling how you go about turing log information into something useful, one of the more popular ways to do this is using the Log parser which is a brilliant tool for those that aren't comfortable doing a lot of coding. You cant beat this tool for speed and efficiency and if your parsing logs often you should learn to use it. More recently the exLogAnalyser has been released which is interesting and it looks like a really great piece of coding that lacks a very very important ingredients for a tool like this. The documentation is spartan and it was written to solve a problem rather then help other people solve their own problems. (It has great potential you just need think about how people will use it!).

If you want to do something a lot more creative with the information your parsing out of log files this is when you need to do some serious number crunching and processing. The first really big problem when parsing a log file is that these files can get really large and reading a very large raw log file that contains millions of lines can be very slow if you want to process the information in everyline in a specific way. If done poorly it can mean your script will start consuming a lot of memory or maybe just take a lot of time. This is where using LogParser generally has a large advantage but the cost is what you can creatively do with the result so the trade off with using Powershell for very large log files is you get a lot of functionality but you pay a big cost in performance when you do it. The good thing however is that not everybody has that problem of very large log files (its really only if you have a large number of users) so for those that don't or dont mind spending some CPU cycles on processing then I've got a few scripts that you might find usefull. The parse engine is built on a previous post which works well in parsing the log file in a more reliable way. One goal I had for this script was to create something that would track logons so it would show me the logon/logoff time the duration and how many time a users logs on. To do this the script uses a hashtable and looks for the contiuned appearance of a certain entry from a user in a log file. If there is a gap of more then 30 mintues between the last entry it track this as a logon and if the users reappears in the sequence this becomes a new logon.

The script shows summaries or raw information and does time conversation to local time as well. I've posted a download of this script here the script iteself looks like the following.

[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")

function openLog{
$exFileName = new-object System.Windows.Forms.openFileDialog
$exFileName.ShowHelp = $true
$exFileName.ShowDialog()
$fnFileNamelableBox.Text = $exFileName.FileName
Populatetable
}

#
function Populatetable{
$logTable.Clear()
$sumTable.Clear()
$fname = $fnFileNamelableBox.Text
$mbcombCollection = @()
$FldHash = @{}
$usHash = @{}
$sumHash = @{}
$fieldsline = (Get-Content $fname)[3]
$fldarray = $fieldsline.Split(" ")
$fnum = -1
foreach ($fld in $fldarray){
$FldHash.add($fld,$fnum)
$fnum++
}

get-content $fname | Where-Object -FilterScript { $_ -ilike “*MSRPC*” } | %{
$lnum ++
if ($lnum -eq $rnma){ Write-Progress -Activity "Read Lines" -Status $lnum
$rnma = $rnma + 1000
}
$linarr = $_.split(" ")
$uid = $linarr[$FldHash["cs-username"]] + $linarr[$FldHash["c-ip"]]
if ($linarr[$FldHash["cs-username"]].length -gt 2){
if ($usHash.Containskey($uid) -eq $false){
if ($linarr.Length -gt 0){$lfDate = $linarr[$FldHash["date"]]}
if ($linarr.Length -gt 1){$lfTime = $linarr[$FldHash["time"]]}
$ldLogDatecombine = $lfDate + " " + $lfTime
if ($ltimeconv.Checked -eq $true){
$LogDate = Get-Date($ldLogDatecombine)
$LocalLogDate = $LogDate.tolocaltime()
$ldLogDatecombine = $LocalLogDate.ToString("yyyy-MM-dd HH:mm:ss")
}
$usrobj = "" | select UserName,IpAddress,LogonTime,LogOffTime,Duration,NumberofRequests,BytesSent,BytesRecieved
$usrobj.UserName = $linarr[$FldHash["cs-username"]]
$usrobj.IpAddress = $linarr[$FldHash["c-ip"]]
$usrobj.LogonTime = $ldLogDatecombine
$usrobj.LogOffTime = $ldLogDatecombine
$usrobj.Duration = 0
$usrobj.NumberofRequests = 0
$usrobj.BytesSent = $linarr[$FldHash["sc-bytes"]]
$usrobj.BytesRecieved = $linarr[$FldHash["cs-bytes"]]
$usHash.add($uid,$usrobj)

}
else{
if ($linarr.Length -gt 0){$lfDate = $linarr[$FldHash["date"]]}
if ($linarr.Length -gt 1){$lfTime = $linarr[$FldHash["time"]]}
$ldLogDatecombine = $lfDate + " " + $lfTime
if ($ltimeconv.Checked -eq $true){
$LogDate = Get-Date($ldLogDatecombine)
$LocalLogDate = $LogDate.tolocaltime()
$ldLogDatecombine = $LocalLogDate.ToString("yyyy-MM-dd HH:mm:ss")
}
$duration = New-TimeSpan $usHash[$uid].LogOffTime $ldLogDatecombine
if ([INT]$duration.Totalminutes -gt 30){
$mbcombCollection += $usHash[$uid]
$usHash.remove($uid)
$usrobj = "" | select UserName,IpAddress,LogonTime,LogOffTime,Duration,NumberofRequests,BytesSent,BytesRecieved
$usrobj.UserName = $linarr[$FldHash["cs-username"]]
$usrobj.IpAddress = $linarr[$FldHash["c-ip"]]
$usrobj.LogonTime = $ldLogDatecombine
$usrobj.LogOffTime = $ldLogDatecombine
$usrobj.BytesSent = $linarr[$FldHash["sc-bytes"]]
$usrobj.BytesRecieved = $linarr[$FldHash["cs-bytes"]]
$usrobj.Duration = 0
$usrobj.NumberofRequests = 0
$usHash.add($uid,$usrobj)
}
else{
$usHash[$uid].LogOffTime = $ldLogDatecombine
$lgduration = New-TimeSpan $usHash[$uid].LogonTime $ldLogDatecombine
$usHash[$uid].Duration = [INT]$lgduration.Totalminutes
$usrobj.NumberofRequests = [INT]$usrobj.NumberofRequests + 1
$usrobj.BytesSent = [INT]$usrobj.BytesSent + [INT]$linarr[$FldHash["sc-bytes"]]
$usrobj.BytesRecieved = [INT]$usrobj.BytesRecieved + [INT]$linarr[$FldHash["cs-bytes"]]
}


}
}
}

$usHash.GetEnumerator() | sort LogonTime -descending | foreach-object {
$logTable.Rows.Add($_.value.UserName,$_.value.IpAddress,$_.value.LogonTime,$_.value.LogOffTime,$_.value.Duration,$_.value.NumberofRequests,$_.value.BytesSent,$_.value.BytesRecieved)
if($sumHash.contains($_.value.UserName)){
$sumHash[$_.value.UserName].NumberofLogons = $sumHash[$_.value.UserName].NumberofLogons + 1
$sumHash[$_.value.UserName].Duration = [INT]$sumHash[$_.value.UserName].Duration + [INT]$_.value.Duration
$sumHash[$_.value.UserName].BytesSent = [INT]$sumHash[$_.value.UserName].BytesSent + [INT]$_.value.BytesSent
$sumHash[$_.value.UserName].BytesRecieved = [INT]$sumHash[$_.value.UserName].BytesRecieved + [INT]$_.value.BytesRecieved
}
else{
$usrobj = "" | select UserName,NumberofLogons,Duration,BytesSent,BytesRecieved
$usrobj.UserName = $_.value.UserName
$usrobj.NumberofLogons = 1
$usrobj.Duration = $_.value.Duration
$usrobj.BytesSent = $_.value.BytesSent
$usrobj.BytesRecieved = $_.value.BytesRecieved
$sumHash.add($_.value.UserName,$usrobj)
}
}
$sumHash.GetEnumerator() | sort LogonTime -descending | foreach-object {
$sumTable.Rows.Add($_.value.UserName,$_.value.NumberofLogons,$_.value.Duration,$_.value.BytesSent,$_.value.BytesRecieved)
}
$dgDataGrid.DataSource = $sumTable
}

$Dataset = New-Object System.Data.DataSet
$logTable = New-Object System.Data.DataTable
$logTable.TableName = "RCPLogons"
$logTable.Columns.Add("UserName");
$logTable.Columns.Add("IpAddress");
$logTable.Columns.Add("LogonTime");
$logTable.Columns.Add("LogOffTime");
$logTable.Columns.Add("Duration",[INT64]);
$logTable.Columns.Add("NumberofRequests",[INT64]);
$logTable.Columns.Add("Sent",[INT64]);
$logTable.Columns.Add("Recieved",[INT64]);

$sumTable = New-Object System.Data.DataTable
$sumTable.TableName = "RpcSummary"
$sumTable.Columns.Add("UserName");
$sumTable.Columns.Add("NumberofLogons",[INT64]);
$sumTable.Columns.Add("Duration",[INT64]);
$sumTable.Columns.Add("Sent",[INT64]);
$sumTable.Columns.Add("Recieved",[INT64]);

$form = new-object System.Windows.Forms.form
$form.Text = "Outlook Anywhere Log Tool"


# Add DataGrid View

# Local Time Converstion CheckBox

$ltimeconv = new-object System.Windows.Forms.CheckBox
$ltimeconv.Location = new-object System.Drawing.Size(310,7)
$ltimeconv.Size = new-object System.Drawing.Size(150,20)
$ltimeconv.Checked = $true
$ltimeconv.Text = "Convert to Local Time"
$form.Controls.Add($ltimeconv)

# Content
$cmClickMenu = new-object System.Windows.Forms.ContextMenuStrip
$cmClickMenu.Items.add("test122")

# Add Open Log file Button

$olButton = new-object System.Windows.Forms.Button
$olButton.Location = new-object System.Drawing.Size(20,19)
$olButton.Size = new-object System.Drawing.Size(75,23)
$olButton.Text = "Select file"
$olButton.Add_Click({openLog})
$form.Controls.Add($olButton)

# Add FileName Lable
$fnFileNamelableBox = new-object System.Windows.Forms.Label
$fnFileNamelableBox.Location = new-object System.Drawing.Size(110,25)
$fnFileNamelableBox.forecolor = "MenuHighlight"
$fnFileNamelableBox.size = new-object System.Drawing.Size(200,20)
$form.Controls.Add($fnFileNamelableBox)

# Add Refresh Log file Button

$refreshButton = new-object System.Windows.Forms.Button
$refreshButton.Location = new-object System.Drawing.Size(390,29)
$refreshButton.Size = new-object System.Drawing.Size(75,23)
$refreshButton.Text = "Refresh"
$refreshButton.Add_Click({Populatetable})
$form.Controls.Add($refreshButton)

# Show Summary CheckBox

$SsumBox = new-object System.Windows.Forms.CheckBox
$SsumBox.Location = new-object System.Drawing.Size(510,7)
$SsumBox.Size = new-object System.Drawing.Size(200,20)
$SsumBox.Checked = $true
$SsumBox.Add_Click({if ($SsumBox.Checked -eq $true){$dgDataGrid.DataSource = $sumTable}
else {$dgDataGrid.DataSource = $logTable}})

$SsumBox.Text = "Show Summary"
$form.Controls.Add($SsumBox)

$dgDataGrid = new-object System.windows.forms.DataGrid
$dgDataGrid.AllowSorting = $True
$dgDataGrid.Location = new-object System.Drawing.Size(12,81)
$dgDataGrid.size = new-object System.Drawing.Size(1024,750)
$form.Controls.Add($dgDataGrid)



$form.topmost = $true
$form.Add_Shown({$form.Activate()})
$form.ShowDialog()

Popular posts from this blog

Testing and Sending email via SMTP using Opportunistic TLS and oAuth in Office365 with PowerShell

As well as EWS and Remote PowerShell (RPS) other mail protocols POP3, IMAP and SMTP have had OAuth authentication enabled in Exchange Online (Official announcement here ). A while ago I created  this script that used Opportunistic TLS to perform a Telnet style test against a SMTP server using SMTP AUTH. Now that oAuth authentication has been enabled in office365 I've updated this script to be able to use oAuth instead of SMTP Auth to test against Office365. I've also included a function to actually send a Message. Token Acquisition  To Send a Mail using oAuth you first need to get an Access token from Azure AD there are plenty of ways of doing this in PowerShell. You could use a library like MSAL or ADAL (just google your favoured method) or use a library less approach which I've included with this script . Whatever way you do this you need to make sure that your application registration  https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-

How to test SMTP using Opportunistic TLS with Powershell and grab the public certificate a SMTP server is using

Most email services these day employ Opportunistic TLS when trying to send Messages which means that wherever possible the Messages will be encrypted rather then the plain text legacy of SMTP.  This method was defined in RFC 3207 "SMTP Service Extension for Secure SMTP over Transport Layer Security" and  there's a quite a good explanation of Opportunistic TLS on Wikipedia  https://en.wikipedia.org/wiki/Opportunistic_TLS .  This is used for both Server to Server (eg MTA to MTA) and Client to server (Eg a Message client like Outlook which acts as a MSA) the later being generally Authenticated. Basically it allows you to have a normal plain text SMTP conversation that is then upgraded to TLS using the STARTTLS verb. Not all servers will support this verb so if its not supported then a message is just sent as Plain text. TLS relies on PKI certificates and the administrative issue s that come around certificate management like expired certificates which is why I wrote th

The MailboxConcurrency limit and using Batching in the Microsoft Graph API

If your getting an error such as Application is over its MailboxConcurrency limit while using the Microsoft Graph API this post may help you understand why. Background   The Mailbox  concurrency limit when your using the Graph API is 4 as per https://docs.microsoft.com/en-us/graph/throttling#outlook-service-limits . This is evaluated for each app ID and mailbox combination so this means you can have different apps running under the same credentials and the poor behavior of one won't cause the other to be throttled. If you compared that to EWS you could have up to 27 concurrent connections but they are shared across all apps on a first come first served basis. Batching Batching in the Graph API is a way of combining multiple requests into a single HTTP request. Batching in the Exchange Mail API's EWS and MAPI has been around for a long time and its common, for email Apps to process large numbers of smaller items for a variety of reasons.  Batching in the Graph is limited to a m
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.