I don't have access to AD/Exchange, so I tested with the file system. One thought is that you are adding an object into the $mbObject values, when you only need a string that you can then export.
cls
$report = @()
(measure-command {
Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue | foreach {
$mbObj = New-Object PSObject
$mbObj | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $_.Name
$mbObj | Add-Member -MemberType NoteProperty -Name "UPN" -Value $_.CreationTime
$mbObj | Add-Member -MemberType NoteProperty -Name "Size" -Value $_.VersionInfo
$report += $mbObj
}
}).TotalSeconds
$report.count
(Measure-Command {$report | Export-Csv c:\temp\test.csv -Force}).TotalSeconds
"------------"
$report = @()
(measure-command {
Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue| foreach {
$report += [PSCustomObject] @{
"Display Name" = $_.Name.tostring();
"UPN" = $_.CreationTime.tostring();
"Size" = $_.VersionInfo.tostring()
}
}
}).TotalSeconds
$report.count
(Measure-Command {$report | Export-Csv c:\temp\test.csv -Force}).TotalSeconds
I ran this against C:\programData and got these results.
148.5061734
62466
1.0384613
------------
98.8008639
62466
0.564858
The second technique of building $report was a lot faster. If you run that script, you would need to run it twice to populate the system file cache to get good results.
Try that method.