Saturday, September 26, 2009

Exchange 2007 Folder Audit Log Powershell Gui Version 1.0

One of the cool things about new features when they are introduced in Service packs is it gives us all a lot more ways of seeing whats happening day to day within our Email environments and hopefully gives us some windows into miss and poor configuration problems. The folder access audit logs are cool but buried in the windows event logs can be hard to use to gain the clarity that might be useful.

The good thing is with a little bit of powershell, a bit of imagination and some time you can bash together something usefull that will extract that information from the logs, parse (or if your using 2008 xpath). Then group that information into a more usable form and present it back into a Winform so you can interact with it.

I've put together actually two scripts to do this the main difference between these scripts is the method they use to access the log. The first script uses WMI to access the Exchange Audit logs (a couple of weeks okay i said this wouldn't work which i was completly wrong about the issue i was having is that you need to have at least view only Exchange Admin to to query these logs). The second script uses the .NET 3.5 class and Xpath to access the logs but this will only work on platforms that support these new classes eg Vista,7,2008.

This script uses a combination of Exchange Management Shell cmdlets get-mailbox and get-mailuser to get the displayname information about all users and then store these in hashtable which can then be used when referencing information that is retrieved from the event logs. It then retrieves the eventlogs for the defined period and server you select via the GUI and then summerises the number of accesses to each folder and displays them in one grid. You can then select the mailox or useraccount used and get the detail on the raw logs. There is also the ability to export each of the grid to a CSV file from the buttons at the bottom of the GUI.

These two scripts are very beta-ish at the moment because of the amount of data that can build up in these logs depending on your log settings. This means when you query and group this data it can be a big challenge without a proper database what I've found is trying to group more then an hours worth or information can be a bit of issue. I'm also not really decided on the how to best group the data that is coming back I'm leaning more towards a summarised approach but there is always version 2 for that.

I've put a download of the code for the two scripts here the script itself looks like

[System.Reflection.Assembly]::LoadWithPartialName("System.Core")

$UserDNHash = @{ }
$FHash = @{ }
$RHash = @{ }
function FillDnHash(){
$mbServers = get-mailboxserver
$mbServers | foreach-object{
get-mailbox -server $_.Name -ResultSize Unlimited | foreach-object{
$UserDNHash.Add($_.LegacyExchangeDN.ToString(),$_)

}
}
get-mailuser -ResultSize Unlimited | foreach-object{
$UserDNHash.Add($_.LegacyExchangeDN.ToString(),$_)

}
}
FillDnHash

function showdetail(){

if ($GnGroupbyDrop.SelectedItem -eq "UserName Used"){
$rows = $fsTable.Select("AccessedBy = '" + $f2Table.DefaultView[$dgDataGrid.CurrentCell.RowIndex][0] + "'")}
else{
$rows = $fsTable.Select("Mailbox = '" + $f1Table.DefaultView[$dgDataGrid.CurrentCell.RowIndex][0] + "'")}

$frTable.clear()
foreach ($row in $rows){
$frTable.rows.add($row[0].ToString(),$row[1].ToString(),$row[2].ToString(),$row[3].ToString()
,$row[4].ToString(),$row[5].ToString(),$row[6].ToString(),$row[7].ToString(),$row[8].ToString(),
$row[9].ToString())
}
$dgDataGrid1.datasource = $frTable

}

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

$form = new-object System.Windows.Forms.form
# Add DataTable

$Dataset = New-Object System.Data.DataSet
$fsTable = New-Object System.Data.DataTable
$f1Table = New-Object System.Data.DataTable
$f2Table = New-Object System.Data.DataTable
$frTable = New-Object System.Data.DataTable

$f1Table.TableName = "Folder Access Forward"
$f1Table.Columns.Add("Mailbox")
$f1Table.Columns.Add("Inbox #")
$f1Table.Columns.Add("Calendar #")
$f1Table.Columns.Add("FreeBusy #")
$f1Table.Columns.Add("Other #")
$Dataset.tables.add($f1Table)


$f2Table.TableName = "Folder Access Reverse"
$f2Table.Columns.Add("User")
$f2Table.Columns.Add("Inbox #")
$f2Table.Columns.Add("Calendar #")
$f2Table.Columns.Add("FreeBusy #")
$f2Table.Columns.Add("Other #")
$Dataset.tables.add($f2Table)


$fsTable.TableName = "Folder Access Detail"
$fsTable.Columns.Add("RecordID")
$fsTable.Columns.Add("DateTime",[DATETIME])
$fsTable.Columns.Add("Mailbox")
$fsTable.Columns.Add("FolderName")
$fstable.Columns.Add("FolderPath")
$fsTable.Columns.Add("AccessedBy")
$fsTable.Columns.Add("IPAddress")
$fsTable.Columns.Add("MachineName")
$fsTable.Columns.Add("ProgramName")
$fsTable.Columns.Add("ApplicationID")
$Dataset.tables.add($fsTable)

$frtable.TableName = "Folder Access Detail Show"
$frTable.Columns.Add("RecordID")
$frtable.Columns.Add("DateTime",[DATETIME])
$frtable.Columns.Add("Mailbox")
$frtable.Columns.Add("FolderName")
$frtable.Columns.Add("FolderPath")
$frtable.Columns.Add("AccessedBy")
$frtable.Columns.Add("IPAddress")
$frtable.Columns.Add("MachineName")
$frtable.Columns.Add("ProgramName")
$frtable.Columns.Add("ApplicationID")
$Dataset.tables.add($frtable)

$sdetailButton = new-object System.Windows.Forms.Button
$sdetailButton.Location = new-object System.Drawing.Size(550,19)
$sdetailButton.Size = new-object System.Drawing.Size(120,23)
$sdetailButton.Text = "Show Folder Detail"
$sdetailButton.visible = $True
$sdetailButton.Add_Click({showDetail})
$form.Controls.Add($sdetailButton)

$gllogButton = new-object System.Windows.Forms.Button
$gllogButton.Location = new-object System.Drawing.Size(440,19)
$gllogButton.Size = new-object System.Drawing.Size(90,23)
$gllogButton.Text = "Get Logs"
$gllogButton.visible = $True
$gllogButton.Add_Click({getLogs})
$form.Controls.Add($gllogButton)

# Add Server DropLable
$snServerNamelableBox = new-object System.Windows.Forms.Label
$snServerNamelableBox.Location = new-object System.Drawing.Size(10,20)
$snServerNamelableBox.size = new-object System.Drawing.Size(80,20)
$snServerNamelableBox.Text = "ServerName"
$form.Controls.Add($snServerNamelableBox)

# Add Server Drop Down
$snServerNameDrop = new-object System.Windows.Forms.ComboBox
$snServerNameDrop.Location = new-object System.Drawing.Size(90,20)
$snServerNameDrop.Size = new-object System.Drawing.Size(100,30)
get-mailboxserver | ForEach-Object{$snServerNameDrop.Items.Add($_.Name)}
$form.Controls.Add($snServerNameDrop)


# Add DateTimePickers Button

$dpDatePickerFromlableBox = new-object System.Windows.Forms.Label
$dpDatePickerFromlableBox.Location = new-object System.Drawing.Size(10,50)
$dpDatePickerFromlableBox.size = new-object System.Drawing.Size(90,20)
$dpDatePickerFromlableBox.Text = "Logged Between"
$form.Controls.Add($dpDatePickerFromlableBox)

$dpTimeFrom = new-object System.Windows.Forms.DateTimePicker
$dpTimeFrom.Location = new-object System.Drawing.Size(110,50)
$dpTimeFrom.Size = new-object System.Drawing.Size(190,20)
$form.Controls.Add($dpTimeFrom)

$dpDatePickerFromlableBox1 = new-object System.Windows.Forms.Label
$dpDatePickerFromlableBox1.Location = new-object System.Drawing.Size(10,70)
$dpDatePickerFromlableBox1.size = new-object System.Drawing.Size(50,20)
$dpDatePickerFromlableBox1.Text = "and"
$form.Controls.Add($dpDatePickerFromlableBox1)

$dpTimeFrom1 = new-object System.Windows.Forms.DateTimePicker
$dpTimeFrom1.Location = new-object System.Drawing.Size(110,70)
$dpTimeFrom1.Size = new-object System.Drawing.Size(190,20)
$form.Controls.Add($dpTimeFrom1)

$dpTimeFrom2 = new-object System.Windows.Forms.DateTimePicker
$dpTimeFrom2.Format = "Time"
$dpTimeFrom2.value = [DateTime]::get_Now().AddHours(-1)
$dpTimeFrom2.ShowUpDown = $True
$dpTimeFrom2.Location = new-object System.Drawing.Size(310,50)
$dpTimeFrom2.Size = new-object System.Drawing.Size(190,20)
$form.Controls.Add($dpTimeFrom2)

$dpTimeFrom3 = new-object System.Windows.Forms.DateTimePicker
$dpTimeFrom3.Format = "Time"
$dpTimeFrom3.ShowUpDown = $True
$dpTimeFrom3.Location = new-object System.Drawing.Size(310,70)
$dpTimeFrom3.Size = new-object System.Drawing.Size(190,20)
$form.Controls.Add($dpTimeFrom3)


# Add DropLable
$GnGroupbylableBox = new-object System.Windows.Forms.Label
$GnGroupbylableBox.Location = new-object System.Drawing.Size(200,20)
$GnGroupbylableBox.size = new-object System.Drawing.Size(100,20)
$GnGroupbylableBox.Text = "Group Results By"
$form.Controls.Add($GnGroupbylableBox)


$GnGroupbyDrop = new-object System.Windows.Forms.ComboBox
$GnGroupbyDrop.Location = new-object System.Drawing.Size(310,20)
$GnGroupbyDrop.Size = new-object System.Drawing.Size(110,30)
$GnGroupbyDrop.Items.Add("Mailbox Accessed")
$GnGroupbyDrop.Items.Add("UserName Used")
$GnGroupbyDrop.Add_SelectedValueChanged({if ($GnGroupbyDrop.SelectedItem -eq "UserName Used"){$dgDataGrid.datasource = $f2Table}
else{$dgDataGrid.datasource = $f1Table}
})
$form.Controls.Add($GnGroupbyDrop)


# Add Export 1st Button

$exButton1 = new-object System.Windows.Forms.Button
$exButton1.Location = new-object System.Drawing.Size(10,620)
$exButton1.Size = new-object System.Drawing.Size(125,20)
$exButton1.Text = "Export Summary Grid"
$exButton1.Add_Click({exportTable})
$form.Controls.Add($exButton1)

# Add Export 2sn Button

$exButton2 = new-object System.Windows.Forms.Button
$exButton2.Location = new-object System.Drawing.Size(550,620)
$exButton2.Size = new-object System.Drawing.Size(135,20)
$exButton2.Text = "Export Details Grid"
$exButton2.Add_Click({exportDetail})
$form.Controls.Add($exButton2)

# Add DataGrid View

$dgDataGrid = new-object System.windows.forms.DataGridView
$dgDataGrid.Location = new-object System.Drawing.Size(10,100)
$dgDataGrid.size = new-object System.Drawing.Size(530,500)
$dgDataGrid.AutoSizeRowsMode = "AllHeaders"
$form.Controls.Add($dgDataGrid)

$dgDataGrid1 = new-object System.windows.forms.DataGridView
$dgDataGrid1.Location = new-object System.Drawing.Size(550,100)
$dgDataGrid1.size = new-object System.Drawing.Size(450,500)
$dgDataGrid1.AutoSizeRowsMode = "AllHeaders"
$form.Controls.Add($dgDataGrid1)


function getlogs{
$DateTo = New-Object System.DateTime $dpTimeFrom.value.year,$dpTimeFrom.value.month,$dpTimeFrom.value.day,$dpTimeFrom2.value.hour
,$dpTimeFrom2.value.minute,$dpTimeFrom2.value.second
$DateFrom = New-Object System.DateTime $dpTimeFrom1.value.year,$dpTimeFrom1.value.month,$dpTimeFrom1.value.day,
$dpTimeFrom3.value.hour,$dpTimeFrom3.value.minute,$dpTimeFrom3.value.second

$DateFromTS = New-TimeSpan -Start $DateFrom -End ([System.DateTime]::Now)
$DateToTS = New-TimeSpan -Start $DateTo -End ([System.DateTime]::Now)

$f1Table.Clear()
$f2Table.Clear()
$fsTable.Clear()

$elLogQuery = ""
$eqEventLogQuery = new-object System.Diagnostics.Eventing.Reader.EventLogQuery("Exchange Auditing", [System.Diagnostics.Eventing.Reader.PathType]::LogName, $elLogQuery);
$eqEventLogQuery.Session = new-object System.Diagnostics.Eventing.Reader.EventLogSession($snServerNameDrop.SelectedItem)
$lrEventLogReader = new-object System.Diagnostics.Eventing.Reader.EventLogReader($eqEventLogQuery)

for($eventInstance = $lrEventLogReader.ReadEvent();$eventInstance -ne $null; $eventInstance = $lrEventLogReader.ReadEvent()){
[System.Diagnostics.Eventing.Reader.EventLogRecord]$erEventRecord = [System.Diagnostics.Eventing.Reader.EventLogRecord]$eventInstance
if($erEventRecord.Properties[5].Value -match "" -eq $false){
$exAuditObject = "" | select RecordID,TimeCreated,FolderPath,FolderName,Mailbox,AccessingUser,MailboxLegacyExchangeDN,AccessingUserLegacyExchangeDN
,MachineName,Address,ProcessName,ApplicationId
$exAuditObject.RecordID = $erEventRecord.RecordID
$exAuditObject.TimeCreated = $erEventRecord.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss")
$exAuditObject.FolderPath = $erEventRecord.Properties[0].Value.ToString()
$exAuditObject.FolderName = $erEventRecord.Properties[1].Value.ToString()
$exAuditObject.Mailbox = $erEventRecord.Properties[2].Value.ToString()
$mbMailbox = $erEventRecord.Properties[5].Value.ToString()
$exAuditObject.AccessingUser = $erEventRecord.Properties[3].Value.ToString()
$AccessingUser = $erEventRecord.Properties[4].Value.ToString()
$exAuditObject.AccessingUserLegacyExchangeDN = $erEventRecord.Properties[4].Value.ToString()
$exAuditObject.MailboxLegacyExchangeDN = $erEventRecord.Properties[5].Value.ToString()
$exAuditObject.MachineName = $erEventRecord.Properties[8].Value.ToString()
$exAuditObject.Address = $erEventRecord.Properties[9].Value.ToString()
$exAuditObject.ProcessName = $erEventRecord.Properties[10].Value.ToString()
$exAuditObject.ApplicationId = $erEventRecord.Properties[12].Value.ToString()
if ($exAuditObject.MailboxLegacyExchangeDN.ToString() -ne $exAuditObject.AccessingUserLegacyExchangeDN.ToString()){
$fsTable.Rows.add($exAuditObject.RecordID,$exAuditObject.TimeCreated.ToString(),
$UserDNHash[$mbMailbox].DisplayName,$exAuditObject.FolderName.ToString(),
$exAuditObject.FolderPath.ToString(),$UserDNHash[$AccessingUser].DisplayName,$exAuditObject.Address.ToString()
,$exAuditObject.MachineName.ToString(),$exAuditObject.ProcessName.ToString(),$exAuditObject.ApplicationId.ToString())

if($Fhash.containskey($mbMailbox)){
switch($exAuditObject.FolderName.ToString()){
"Inbox" {$Fhash[$mbMailbox].InboxCount = $Fhash[$mbMailbox].InboxCount + 1}
"Calendar" {$Fhash[$mbMailbox].CalendarCount = $Fhash[$mbMailbox].CalendarCount + 1}
"FreeBusy Data" {$FHash[$mbMailbox].FreeBusyCount = $FHash[$mbMailbox].FreeBusyCount + 1}
default {$Fhash[$mbMailbox].OtherCount = $Fhash[$mbMailbox].OtherCount + 1}
}
}
else{
$coCustObj = "" | select MailboxName,InboxCount,CalendarCount,FreeBusyCount,OtherCount
$coCustObj.MailboxName = $mbMailbox
$coCustObj.InboxCount = 0
$coCustObj.CalendarCount = 0
$coCustObj.FreeBusyCount = 0
$coCustObj.OtherCount = 0
$Fhash.add($mbMailbox,$coCustObj)
switch($folderName){
"Inbox" {$Fhash[$mbMailbox].InboxCount = $Fhash[$mbMailbox].InboxCount + 1}
"Calendar" {$Fhash[$mbMailbox].CalendarCount = $Fhash[$mbMailbox].CalendarCount + 1}
"FreeBusy Data" {$FHash[$mbMailbox].FreeBusyCount = $FHash[$mbMailbox].FreeBusyCount + 1}
default {$Fhash[$mbMailbox].OtherCount = $Fhash[$mbMailbox].OtherCount + 1}
}
}

if($RHash.containskey($AccessingUser)){
switch($exAuditObject.FolderName.ToString()){
"Inbox" {$RHash[$AccessingUser].InboxCount = $RHash[$AccessingUser].InboxCount + 1}
"Calendar" {$RHash[$AccessingUser].CalendarCount = $RHash[$AccessingUser].CalendarCount + 1}
"FreeBusy Data" {$RHash[$AccessingUser].FreeBusyCount = $RHash[$AccessingUser].FreeBusyCount + 1}
default {$RHash[$AccessingUser].OtherCount = $RHash[$AccessingUser].OtherCount + 1}

}
}
else{
$coCustObj = "" | select MailboxName,InboxCount,FreeBusyCount,CalendarCount,OtherCount
$coCustObj.MailboxName = $AccessingUser
$coCustObj.InboxCount = 0
$coCustObj.CalendarCount = 0
$coCustObj.FreeBusyCount = 0
$coCustObj.OtherCount = 0
$Rhash.add($AccessingUser,$coCustObj)
switch($exAuditObject.FolderName.ToString()){
"Inbox" {$RHash[$AccessingUser].InboxCount = $RHash[$AccessingUser].InboxCount + 1}
"Calendar" {$RHash[$AccessingUser].CalendarCount = $RHash[$AccessingUser].CalendarCount + 1}
"FreeBusy Data" {$RHash[$AccessingUser].FreeBusyCount = $RHash[$AccessingUser].FreeBusyCount + 1}
default {$RHash[$AccessingUser].OtherCount = $RHash[$AccessingUser].OtherCount + 1}
}
}
}
}
}
foreach($key in $Fhash.keys){
$f1Table.rows.add($UserDNHash[$Fhash[$key].MailboxName].DisplayName,$Fhash[$key].InboxCount,
$Fhash[$key].CalendarCount,$Fhash[$key].FreeBusyCount,$Fhash[$key].OtherCount)
}
foreach($key in $Rhash.keys){
$f2Table.rows.add($UserDNHash[$Rhash[$key].MailboxName].DisplayName,$Rhash[$key].InboxCount,
$Rhash[$key].CalendarCount,$Rhash[$key].FreeBusyCount,$Rhash[$key].OtherCount)
}

if ($GnGroupbyDrop.SelectedItem -eq "UserName Used"){$dgDataGrid.datasource = $f2Table}
else{$dgDataGrid.datasource = $f1Table}

}

function exportTable{
$exFileName = new-object System.Windows.Forms.saveFileDialog
$exFileName.DefaultExt = "csv"
$exFileName.Filter = "csv files (*.csv)|*.csv"
$exFileName.InitialDirectory = "c:\temp"
$exFileName.Showhelp = $true
$exFileName.ShowDialog()
if ($exFileName.FileName -ne ""){
$logfile = new-object IO.StreamWriter($exFileName.FileName,$true)
$logfile.WriteLine("MailboxName,InboxCount,FreeBusyCount,CalendarCount,OtherCount")
$table = $dgDataGrid.datasource
foreach($row in $table.Rows){
$logfile.WriteLine("`"" + $row[0].ToString() + "`"," + $row[1].ToString() + "," + $row[2].ToString() + "," + $row[3].ToString() + "," + $row[4].ToString())
}
$logfile.Close()
}

}

function exportDetail{
$exFileName = new-object System.Windows.Forms.saveFileDialog
$exFileName.DefaultExt = "csv"
$exFileName.Filter = "csv files (*.csv)|*.csv"
$exFileName.InitialDirectory = "c:\temp"
$exFileName.Showhelp = $true
$exFileName.ShowDialog()
if ($exFileName.FileName -ne ""){
$logfile = new-object IO.StreamWriter($exFileName.FileName,$true)
$logfile.WriteLine("RecordID,TimeCreated,FolderPath,FolderName,Mailbox,
AccessingUser,MailboxLegacyExchangeDN,AccessingUserLegacyExchangeDN,MachineName,
Address,ProcessName,ApplicationId")
$table = $dgDataGrid1.datasource
foreach($row in $table.Rows){
$logfile.WriteLine("`"" + $row[0].ToString() + "`"," + $row[1].ToString() + "," + $row[2].ToString() + "," + $row[3].ToString() + "," + $row[4].ToString() + "," + $row[5].ToString() + "," + $row[6].ToString()+ "," + $row[7].ToString() + "," + $row[8].ToString() + "," + $row[9].ToString())
}
$logfile.Close()
}

}

$form.Text = "Exchange 2007 Mailbox Folder Access Audit Form"
$form.size = new-object System.Drawing.Size(1000,800)
$form.autoscroll = $true
$form.Add_Shown({$form.Activate()})
$form.ShowDialog()

Showing information about a Exchange 2003 user in Powershell

Haven't posted any 2003 scripts for a while so here's one that might be useful especially if your looking at doing a bit of documentation before a migration. There are a few tricks when your using ADSI scripts to get information eg if you want to get the OU check the parent object of the userObject. Or if you want to get the Storage Group use the Parent of the MailStore object. If you want to work out quotas check both the mailstore and the user object. To put this all together into a script that takes the username as a parameter it looks like this I've put a download of this code here.

param([String] $samaccountname)
$root = [ADSI]'LDAP://RootDSE'
$dfDefaultRootPath = "LDAP://" + $root.DefaultNamingContext.tostring()
$dfRoot = [ADSI]$dfDefaultRootPath
$gfGALQueryFilter = "(&(&(&(& (mailnickname=*)(objectCategory=person)(objectClass=user)(samaccountname=" + $samaccountname + ")))))"
$dfsearcher = new-object System.DirectoryServices.DirectorySearcher($dfRoot)
$dfsearcher.Filter = $gfGALQueryFilter
$srSearchResult = $dfsearcher.FindOne()
if ($srSearchResult -ne $null){
$uoUserobject = $srSearchResult.GetDirectoryEntry()
$msStore = [ADSI]("LDAP://" + $uoUserobject.homemdb)
$soServer = [ADSI]("LDAP://" + $msStore.msExchOwningServer)
$sgStorageGroup = $msStore.psbase.Parent
$ffEdbFileFilter = "name='" + $msStore.msExchEDBFile.ToString().replace("\","\\") + "'"
$ffStmFileFilter = "name='" + $msStore.msExchSLVFile.ToString().replace("\","\\") + "'"
$mbEdbSize =get-wmiobject CIM_Datafile -filter $ffEdbFileFilter -ComputerName $soServer.Name
$mbStmSize =get-wmiobject CIM_Datafile -filter $ffStmFileFilter -ComputerName $soServer.Name
[int64]$csCombinedSize = [double]$mbEdbSize.FileSize + [int64]$mbStmSize.FileSize
$msFilter = "LegacyDN='" + $uoUserobject.legacyExchangeDN + "'"
$mbsize = get-wmiobject -class Exchange_Mailbox -Namespace ROOT\MicrosoftExchangev2 -filter $msFilter -ComputerName $soServer.Name
$divval = ($csCombinedSize/1024)/100
$pcStore = ($mbsize.size/$divval)/100
"User DisplayName : " + $uoUserobject.displayName
"OU DisplayName : " + $uoUserobject.psbase.Parent.Name
"OU DistinguishedName : " + $uoUserobject.psbase.Parent.DistinguishedName
"Mail Server : " + $soServer.Name
"Exchange Version : " + $soServer.SerialNumber
"Mailbox Store : " + $msStore.Name
"Storage Group : " + $sgStorageGroup.Name
"MailStore Size : " + "{0:#.00}" -f ($csCombinedSize/1GB) + " GB"
"Mailbox Size : " + "{0:#.00}" -f ($mbsize.Size/1KB) + " MB"
"Percentage of Store Used by User : " + "{0:P1}" -f $pcStore
""
"Quotas"
""
"User Quotas"
""
"mDBStorageQuota : " + "{0:#.00}" -f ($uoUserobject.mDBStorageQuota.Value/1KB) + " MB"
"mDBOverQuotaLimit : " + "{0:#.00}" -f ($uoUserobject.mDBOverQuotaLimit.Value/1KB) + " MB"
"mDBOverHardQuotaLimit : " + "{0:#.00}" -f ($uoUserobject.mDBOverHardQuotaLimit.Value/1KB) + " MB"
""
"Store Quotas "
"mDBStorageQuota : " + "{0:#.00}" -f ($msStore.mDBStorageQuota.Value/1KB) + " MB"
"mDBOverQuotaLimit : " + "{0:#.00}" -f ($msStore.mDBOverQuotaLimit.Value/1KB) + " MB"
"mDBOverHardQuotaLimit : " + "{0:#.00}" -f ($msStore.mDBOverHardQuotaLimit.Value/1KB) + " MB"

}

Sunday, September 13, 2009

Getting the Exchange Auditing event logs programmatically

With the release of Exchange 2007 SP2 some great new auditing options are now possible to let you see how and when rights are being used within your exchange environment. More importantly in a serious situation you could use these features to provide some forensic auditing of what was being accessed in your exchange store when some suspicious activity may have happened. The first place to start if your looking at auditing in Exchange 2007 SP2 is to read the very detailed white paper that has been published http://technet.microsoft.com/en-us/library/ee331009.aspx .

In this post I'm going to look at the options for reading the new Exchange Auditing event logs where folder access information is written. The Exchange Auditing logs themselves if your using Windows 2008 use the new evtx event log framework and are stored under the Application and Services Logs group

With the new .net 3.5 System.Diagnostics.Eventing.Reader class now allows you to easily parse back the information store in the event log. But before looking at this lets look at a more convention approach using Get-Eventlog can be used on both windows 2003 and 2008. Using and then you can do the text from the event log message which you can then parse out. To do a conventional parse you can split it by the Newline character and then split by semicollumn. eg

if($_.Timewritten -gt [System.DateTime]::Now.addhours(-24)){
$arry = $_.Message.ToString().Split("`n")
$mbMailbox = ""
$folderName = ""
$AccessingUser = ""
$processname = ""
$machinename = ""
$ApplicationId = ""
$Address = ""
foreach($line in $arry){
if ($line -match "Mailbox:"){
$laLineArray = $line.Split(":")
if ($laLineArray[1] -match "" -eq $false){
$mbMailbox = $laLineArray[1].Substring(1,($laLineArray[1].length-3))
}
}
if ($line -match "Display Name:"){
$laLineArray = $line.Split(":")
$folderName = $laLineArray[1].Substring(1,($laLineArray[1].length-2))
}
if ($line -match "Accessing User:"){
$laLineArray = $line.Split(":")
$AccessingUser = $laLineArray[1].Substring(1,($laLineArray[1].length-2))
}
if ($line -match "Process Name:"){
$laLineArray = $line.Split(":")
$processname = $laLineArray[1].Substring(1,($laLineArray[1].length-2))
}
if ($line -match "Machine Name:"){
$laLineArray = $line.Split(":")
$machinename = $laLineArray[1].Substring(1,($laLineArray[1].length-2))
}
if ($line -match "Application Id:"){
$laLineArray = $line.Split(":")
$ApplicationId = $laLineArray[1].Substring(1,($laLineArray[1].length-2))
}
if ($line -match "Address:"){
$laLineArray = $line.Split(":")
$Address = $laLineArray[1].Substring(1,($laLineArray[1].length-2))
}
}
}

Its a little messy like any string parse but it does the job, But whats more interesting is if you have Vista and or Windows 2008 you can start using the new eventing classes. Powershell V2 introduces the Get-WinEvent cmdlet which is a nice wrapper around these classes but as V2 isn't out yet and the underlying classes are pretty simple to use anyway i prefer the more direct method. One of the advantages of using thesse new classes is that you can do away with having to parse text out of the message fields as this information is already stored in a structure format you can access using the Eventing.Reader.EventLogRecord properties collection the order or parameters are documented in the Whitepaper i mentioned earlier.

One of the challenges of using the Eventing.Reader class is that you need to work out what the correct XPATH query will be for the data you want to retrieve from the event logs. XPath for those uninitiated is a standard used for querying XML data the event logs only supports Version 1 of XPath. This limitation affects you when you want to query for dattime within a certain timeframe which is something in the real world you generally do a lot. With Version 1 of Xpath you can only do numeric comparisons so when creating a restriction around a timespan one method you can to use the timediff function which gives you the differance between the Time of the log entry and the current time in milliseconds. So mathamatically with a little code you can query a TimeSpan using TimeSpan in Powershell.

So to put this all together into a script that will allow you to query the Exchange Auditing EventLog for events of type 10100 and where the event relates to one user accessing another users mailbox (you need to check if mailboxExchangeLegacyDN is set). This script uses a custom object to store the result and then exports the result to a csv file.

The following three varibles need to be set first before you run the code the first in the servername of the server to query the log. The next two varbile set the time From (Lastest) to the To (earliest) entry you want to retrieve.

$ServerName = "servername"
$DateFrom = [System.DateTime]::Now.addDays(-2)
$DateTo = [System.DateTime]::Now.addDays(-3)

I've put a download of this script here the script looks like

[System.Reflection.Assembly]::LoadWithPartialName("System.Core")

$ServerName = "servername"
$DateFrom = [System.DateTime]::Now.addDays(-2)
$DateTo = [System.DateTime]::Now.addDays(-3)
$DateFromTS = New-TimeSpan -Start $DateFrom -End ([System.DateTime]::Now)
$DateToTS = New-TimeSpan -Start $DateTo -End ([System.DateTime]::Now)
$eacombCollection = @()

$elLogQuery = "<QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Exchange Auditing`">*[System[(EventID=10100) and TimeCreated[timediff(@SystemTime) <=" + $DateToTS.TotalMilliseconds +"] and TimeCreated[timediff(@SystemTime) >= " + $DateFromTS.TotalMilliseconds +"]]]</Select></Query></QueryList>"
$eqEventLogQuery = new-object System.Diagnostics.Eventing.Reader.EventLogQuery("Exchange Auditing", [System.Diagnostics.Eventing.Reader.PathType]::LogName, $elLogQuery);
$eqEventLogQuery.Session = new-object System.Diagnostics.Eventing.Reader.EventLogSession($ServerName);
$lrEventLogReader = new-object System.Diagnostics.Eventing.Reader.EventLogReader($eqEventLogQuery)

for($eventInstance = $lrEventLogReader.ReadEvent();$eventInstance -ne $null; $eventInstance = $lrEventLogReader.ReadEvent()){
[System.Diagnostics.Eventing.Reader.EventLogRecord]$erEventRecord = [System.Diagnostics.Eventing.Reader.EventLogRecord]$eventInstance
if($erEventRecord.Properties[5].Value -match "<NULL>" -eq $false){
$exAuditObject = "" | select RecordID,TimeCreated,FolderPath,FolderName,Mailbox,AccessingUser,
MailboxLegacyExchangeDN,AccessingUserLegacyExchangeDN,MachineName,Address,
ProcessName,ApplicationId
$exAuditObject.RecordID = $erEventRecord.RecordID
$exAuditObject.TimeCreated = $erEventRecord.TimeCreated
$exAuditObject.FolderPath = $erEventRecord.Properties[0].Value.ToString()
$exAuditObject.FolderName = $erEventRecord.Properties[1].Value.ToString()
$exAuditObject.Mailbox = $erEventRecord.Properties[2].Value.ToString()
$exAuditObject.AccessingUser = $erEventRecord.Properties[3].Value.ToString()
$exAuditObject.AccessingUserLegacyExchangeDN = $erEventRecord.Properties[4].Value.ToString()
$exAuditObject.MailboxLegacyExchangeDN = $erEventRecord.Properties[5].Value.ToString()
$exAuditObject.MachineName = $erEventRecord.Properties[8].Value.ToString()
$exAuditObject.Address = $erEventRecord.Properties[9].Value.ToString()
$exAuditObject.ProcessName = $erEventRecord.Properties[10].Value.ToString()
$exAuditObject.ApplicationId = $erEventRecord.Properties[12].Value.ToString()
$eacombCollection +=$exAuditObject
}
}

$eacombCollection | export-csv –encoding "unicode" -noTypeInformation "c:\temp\exauditOutput.csv"