How To Audit Exchange 2007 Mailbox Moves using PowerShell

by Mike Pfeiffer on April 5, 2010

I was recently asked generate a report that would detail when and who were moving mailboxes in an organization running Exchange 2007. The first option that popped into my head was diagnostic logging, but unfortunately, even when using expert level logging, the events do not provide the name of the user who performed the mailbox move. My next best option was to use the migration logs.

So, what are migration logs? When you use the Move-Mailbox cmdlet to move a mailbox, an XML report file is generated that contains the details of the mailbox move. By default, all of these XML log files are stored in C:\Program Files\Microsoft\Exchange Server\Logging\MigrationLogs or under \Logging\MigrationLogs in your $exinstall path. In order to audit these mailbox moves, we can parse the XML files using PowerShell to grab the details we need.

The following function, Get-MailboxMoveReport, accepts three parameters; Server, Mailbox and Path. Provide the server name using the Server parameter and the local directory containing the XML report files to the Path parameter. If you want to get a report for a specific mailbox, you can provide the name using the Mailbox parameter. If no values are provided for the Server and Path parameters, the function will run against the local server and check the default MigrationLogs location for XML report files.

Function Get-MailboxMoveReport {
    [CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$false)]
        [String]
        $Mailbox,
        [Parameter(Position=1, Mandatory=$false)]
        [String]
        $Server = $env:COMPUTERNAME,
        [Parameter(Position=2, Mandatory=$false)]
        [String]
        $Path = (Join-Path -Path $exinstall -ChildPath "Logging\MigrationLogs")
        )

    process {
        $report = @()
        $path = Join-Path "\\$server" $path.replace(":","$")
        foreach ($item in gci $path *.xml) {
            $report += [xml](Get-Content $item.fullname)
        }        

        $report | %{
            $MovedBy = $_."move-Mailbox".TaskHeader.RunningAs
            $StartTime = $_."move-Mailbox".TaskHeader.StartTime
            $EndTime = $_."move-Mailbox".TaskFooter.EndTime

            $result = $_."move-mailbox".taskdetails.item | %{
                New-Object PSObject -Property @{
                    Mailbox = $_.MailboxName
                    MovedBy = $MovedBy
                    MailboxSize = $_.MailboxSize
                    StartTime = $StartTime
                    EndTime = $EndTime
                    IsWarning = $_.result.IsWarning
                    ErrorCode = $_.result.ErrorCode
                }
            }
        }

        if($Mailbox) {
            $result | ?{$_.Mailbox -eq (Get-Mailbox $Mailbox).DisplayName}
        }
        else {
            $result
        }
    }
}

Here's a screen shot running the above function:

Mailbox Audit

As you can see, the output provides a lot of detail including the status of the mailbox move, the start and end time, the size of the mailbox, and the user that performed the move.

Keep in mind that the Move-Mailbox cmdlet has a ReportFile parameter that can be used to store the XML report file in a specified folder other than the default. So, the report files you need may not always be in the migration logs folder depending on how the moves are being done. The function provides the Path parameter to specify an alternate directory, if needed.

This function is written with PowerShell v2, so you'll need to be running Exchange 2007 SP2 and PowerShell v2 in order to use it.

Related Posts

{ 1 trackback }

uberVU - social comments
April 5, 2010 at 6:18 pm

{ 11 comments… read them below or add one }

ShayLevy April 5, 2010 at 8:19 am

RT @mike_pfeiffer: [Blog] How To Audit #Exchange 2007 Mailbox Moves using #PowerShell http://www.mikepfeiffer.net/2010/04/how-...

Reply

Peter May 27, 2010 at 12:39 am

This script is cool, albeit it only works on xml log files with one single mailbox move. I regularly do multi-moves…. how would that work? Being a ps-noob I’m pretty lost here :)

Reply

Mike Pfeiffer May 27, 2010 at 8:27 am

Hi Peter, thanks for the feedback, much appreciated. I rewrote the function and it should work now with a single xml file with multiple mailbox moves.

Reply

Script errors out May 28, 2010 at 7:08 am

Mike,
The script errors out when I execute. See errors below trying to retrieve a mailbox and then all mailboxes that were migrated and logged in the mailboxes.xml. The path for my xml files is c:\scripts\sg3. What am I doing wrong?
I ran this script from my Exchange 2007 SP2 server using EMS.

-Ray
This is my modified path:
$Path = (Join-Path -Path $exinstall -ChildPath “c:\scripts\sg3″)

Errors
[PS] C:\Scripts>.\Get-MailboxMoveReport.ps1
Missing closing ‘)’ in expression.
At C:\Scripts\Get-MailboxMoveReport.ps1:5 char:9
+ [ <<<.\Get-MailboxMoveReport.ps1 -mailbox latitude
Missing closing ‘)’ in expression.
At C:\Scripts\Get-MailboxMoveReport.ps1:5 char:9
+ [ <<<

Reply

Mike Pfeiffer May 28, 2010 at 7:22 am

Hi Ray,

A couple things…First, the default path is going to use your MigrationLogs folder, but to override that you can just use the Path parameter, there’s no need to hard code it.

Also, you just want to dot source that file, so first you import the function:

[PS] C:\Scripts>. .\Get-MailboxMoveReport.ps1

Then you can call the function and specify the path using the path parameter:

Get-MailboxMoveReport -Mailbox -Path c:\scripts\sg3

Reply

Script errors out May 28, 2010 at 9:33 am

Mike,

That worked fine but only retrieves one mailbox where I moved 20 mailboxes. I am using a script to migrate the mailboxes and it appears that the xml file only contains one of the mailboxes and the remaining mailbox migrations are logged as multiple text files in the migration log directory. Not sure why. So this is why your script only retrieved one mailbox. The script works great and thanks for the quick response.

-Ray

PS C:\myscripts> get-mailboxmovereport -path c:\myscripts

StartTime : 05/27/2010 17:22:02
IsWarning : False
ErrorCode : 0
MailboxSize : 0KB
EndTime : 05/27/2010 17:22:13
MovedBy : BSG\dalesadminroot
Mailbox : BSG Websphere Webtrend

PS C:\myscripts> get-mailboxmovereport -path c:\myscripts

StartTime : 05/27/2010 17:22:02
IsWarning : False
ErrorCode : 0
MailboxSize : 0KB
EndTime : 05/27/2010 17:22:13
MovedBy : BSG\dalesadminroot
Mailbox : BSG Websphere Webtrend

PS C:\myscripts>

Reply

Personne June 8, 2010 at 12:49 pm

I loved your script a lot, so I’ve made some modification in order to fit my needs (will include the transfer rate). I’ll try later to make something cleaner, dealing with those strings as timestamps is not clean, and not convenient, for instance we cannot sort by StartTime because of their string type

Function Get-MailboxMoveReport {
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$false)]
[String]
$Mailbox, #not necessary has a parameter anymore
[Parameter(Position=1, Mandatory=$false)]
[String]
$Server = $env:COMPUTERNAME,
[Parameter(Position=2, Mandatory=$false)]
[String]
$Path = (Join-Path -Path $exinstall -ChildPath “Logging\MigrationLogs”)
)

process {
$report = @()
$path = Join-Path “\\$server” $path.replace(“:”,”$”)
# modif: list only the move xml file, the Logging\MigrationLogs folder do also includes the Export-mailbox log
foreach ($item in gci $path move*.xml ) {
$report += (Get-Content $item.fullname)
}

#$report
$dbx = @() # we will populate this table with every result we find

$report | %{
$MovedBy = $_.”move-Mailbox”.TaskHeader.RunningAs
$StartTime = $_.”move-Mailbox”.TaskHeader.StartTime
$EndTime = $_.”move-Mailbox”.TaskFooter.EndTime

$result = $_.”move-mailbox”.taskdetails.item | %{
write-host -fore Green $_.MailboxName

# will calculate the speed
$ts=$null #in case of XML error, and duration is not existant
$speed=$null #in case of XML error, and duration is not existant
if ($_.duration) { # a bit ugly but it works,
$t=$_.duration.split(“.:”) #it is required since the duration is not caster as a timestamp
if ($t.count -eq 4) {$ts=new-timespan -days $t[0] -hours $t[1] -minutes $t[2] -seconds $t[3] }
if ($t.count -eq 3) {$ts=new-timespan -hours $t[0] -minutes $t[1] -seconds $t[2]}
$speed = $_.mailboxsize / $ts.totalseconds # expect a mailboxsize with KB at the end.
}

New-Object PSObject -Property @{
Mailbox = $_.MailboxName
MovedBy = $MovedBy
MailboxSize = $_.MailboxSize
StartTime = $StartTime
EndTime = $EndTime
Duration = $_.Duration
Durationinsec = $ts.totalseconds
Speed_in_Bpbs = $speed
IsWarning = $_.result.IsWarning
ErrorCode = $_.result.ErrorCode
}
}
$dbx += $result #add, add , add

}
return $dbx #return table
}
}

# Usage
$gg=Get-MailboxMoveReport -server mail01-sd
$gg | ? {$_.mailbox -match “username”}

Reply

Mike Pfeiffer June 8, 2010 at 1:08 pm

Awesome, thanks for the feedback and for sharing :)

Reply

David Keating April 25, 2011 at 6:44 am

Is the Exchange admins have the EMC installed on their workstation, the migration logs will be local on their workstation, correct?

Reply

Rajan October 27, 2011 at 8:22 am

Mike,

When i am trying to dot source the powershell file, getting the below error message. Can you help.

[PS] E:\scripts>. .\Get-MailboxMoveReport.ps1
Missing closing ‘)’ in expression.
At E:\scripts\Get-MailboxMoveReport.ps1:5 char:9
+ [ <<<

Reply

Mike Pfeiffer October 30, 2011 at 9:09 am

From the error, it seems like you’re using PowerShell v1, and this function requires v2.

Reply

Leave a Comment

Previous post:

Next post: