powershellpowershell-5.0exchange-server-2013exchange-management-shellpowershell-jobs

Powershell Speed Up Get-MessageTrackingLog


Currently I am trying to get an output of all disabled users and their message counts in exchange. This is easy enough through a foreach loop:

    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://aserversomewhere.local/PowerShell/ -Authentication Kerberos -Credential $UserCredential
    Import-PSSession $Session -AllowClobber    

    Import-Module ActiveDirectory    

    $Users = Get-ADUser -filter * -Properties * -SearchBase "OU=Disabled User Accounts,DC=Private,DC=Private"

    $Today = (Get-Date).ToShortDateString()
    $OneMonthAgo = (Get-Date).AddMonths(-1).ToShortDateString()

    $results = @()

    $OnPrem = $Users | Where-Object {$_.mDBUseDefaults -eq "True"}
    $365 = $Users | Where-Object{$_.mDBUseDefaults -ne "True"}

    Write-Host "Start Date: " $OneMonthAgo -ForegroundColor Green
    Write-Host "Total Users OnPrem: " ($OnPrem.mail).Count -ForegroundColor Green

    foreach($User in $OnPrem)
    {
        Write-Host "Checking User: "$User.DisplayName -ForegroundColor Yellow
        $MessageCount = Get-MessageTrackingLog -recipients $User.Mail -Start $OneMonthAgo.ToString() | Where-Object {$_.EventID -eq "RECEIVE"} | Measure-Object

        Write-Host $User.Name": MessageCount: "$MessageCount.Count -ForegroundColor Cyan

         $Object = New-Object PSObject -Property @{
            User = $User.Name
            Email = $User.Mail
            Type = "OnPrem"
            DisabledDate = $User.Modified
            Location = $User.Office
            MessagesReceived = $MessageCount.Count
         }

         $script:results += $Object
    }

The issue is this takes several hours to complete because it is being ran one user at a time. My Goal is to run multiple inquiries at a time either through jobs or in parallel. This needs to be ran in blocks of 10 due to the policy restrictions on the exchange server.

Edit (more information on why):

The reason to find the message counts of the users is, they are disabled and sitting an a disabled OU. The reason for this is their mail is fw to another recipient. Or, their mailbox has been delegated. This is intended for house keeping. The results of this search will be filtered by MessageCount = 0. Then it will either be reported/saved as csv/users removed.

Disclosure: I am very ignorant on running jobs or running in parallel within powershell. And, my google-foo seems to be broken. Any guidance or help with this would be very appreciated.

Version info:

Name             : Windows PowerShell ISE Host
Version          : 5.1.15063.966

UPDATE:

After Following Shawn's guidance, I was able to successfully speed up these requests quite significantly.

Updated code:

$RunSpaceCollection = @()
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, 10)
$RunspacePool.ApartmentState = "MTA"
$RunspacePool.Open()


$UserCredential = Get-Credential

Import-Module ActiveDirectory


$Users = Get-ADUser -filter * -Properties * -SearchBase "OU=Disabled User Accounts,DC=private,DC=private"

$Today = (Get-Date).ToShortDateString()
$OneMonthAgo = (Get-Date).AddMonths(-1).ToShortDateString()

[Collections.ArrayList]$results = @()

$OnPrem = $Users | Where-Object {$_.mDBUseDefaults -eq "True"}
$365 = $Users | Where-Object{$_.mDBUseDefaults -ne "True"}

Write-Host "Start Date: " $OneMonthAgo -ForegroundColor Green
Write-Host "Total Users OnPrem: " ($OnPrem.mail).Count -ForegroundColor Green

$scriptblock = {
    Param (
        [System.Management.Automation.PSCredential]$Credential,
        [string]$emailAddress,
        [string]$startTime,
        [string]$userName,
        [string]$loginName,
        [string]$DisabledDate,
        [string]$OfficeLocation
    )      

    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://someserver.local/PowerShell/ -Authentication Kerberos -Credential $Credential
    Import-PSSession $Session -AllowClobber -DisableNameChecking -Verbose:$false | Out-Null

    $MessageCount = Get-MessageTrackingLog -recipients $emailAddress -Start $startTime.ToString() -ResultSize unlimited  

    $Object = New-Object PSObject -Property @{
        User = $userName
        Login = $loginName
        Email = $emailaddress
        Type = "OnPrem"
        DisabledDate = $DisabledDate
        Location = $OfficeLocation
        MessagesReceived = $MessageCount.Count.ToString()
        }           

     $Object


}

foreach($User in $OnPrem)
{
    $Powershell = [PowerShell]::Create()
    $null = $Powershell.AddScript($scriptblock)
    $null = $Powershell.AddArgument($UserCredential)
    $null = $Powershell.AddArgument($user.mail)
    $null = $Powershell.AddArgument($OneMonthAgo)
    $null = $Powershell.AddArgument($user.Name)
    $null = $Powershell.AddArgument($user.samaccountname)
    $null = $Powershell.AddArgument($user.Modified)
    $null = $Powershell.AddArgument($user.Office)
    $Powershell.RunspacePool = $RunspacePool   

    [Collections.ArrayList]$RunSpaceCollection += New-Object -TypeName PSObject -Property @{
        RunSpace = $Powershell.BeginInvoke()
        PowerShell = $Powershell
    }

}

 While($RunspaceCollection) {        

    Foreach($Runspace in $RunSpaceCollection.ToArray())
    {        
        If ($Runspace.Runspace.IsCompleted) {           

            [void]$results.Add($Runspace.PowerShell.EndInvoke($Runspace.Runspace))          

            $Runspace.PowerShell.Dispose()
            $RunspaceCollection.Remove($Runspace)

        }
    }    
 }

$RunspacePool.Close() 
$RunspacePool.Dispose()


 $results

The issue I am having is every user (except the last 3 users) are showing 0 as the message count. I know this wrong. Could this somehow not be waiting for the query of Get-MessageTrackingLog -sender to finish?

Example (77 Users total):

All but the last three show:

Email : a.b@something.com

DisabledDate : 02/08/2018

Login : a.b

Type : OnPrem

User : a, b

Location : Clearfield, IA

MessagesReceived : 0


Solution

  • In order to speedup Get-MessageTrackingLog, you have to use pools.

    Original:

    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://aserversomewhere.local/PowerShell/ -Authentication Kerberos -Credential $UserCredential
    Import-PSSession $Session -AllowClobber    
    
    Import-Module ActiveDirectory    
    
    $Users = Get-ADUser -filter * -Properties * -SearchBase "OU=Disabled User Accounts,DC=Private,DC=Private"
    
    $Today = (Get-Date).ToShortDateString()
    $OneMonthAgo = (Get-Date).AddMonths(-1).ToShortDateString()
    
    $results = @()
    
    $OnPrem = $Users | Where-Object {$_.mDBUseDefaults -eq "True"}
    $365 = $Users | Where-Object{$_.mDBUseDefaults -ne "True"}
    
    Write-Host "Start Date: " $OneMonthAgo -ForegroundColor Green
    Write-Host "Total Users OnPrem: " ($OnPrem.mail).Count -ForegroundColor Green
    
    foreach($User in $OnPrem)
    {
        Write-Host "Checking User: "$User.DisplayName -ForegroundColor Yellow
        $MessageCount = Get-MessageTrackingLog -recipients $User.Mail -Start $OneMonthAgo.ToString() | Where-Object {$_.EventID -eq "RECEIVE"} | Measure-Object
    
        Write-Host $User.Name": MessageCount: "$MessageCount.Count -ForegroundColor Cyan
    
         $Object = New-Object PSObject -Property @{
            User = $User.Name
            Email = $User.Mail
            Type = "OnPrem"
            DisabledDate = $User.Modified
            Location = $User.Office
            MessagesReceived = $MessageCount.Count
         }
    
         $script:results += $Object
    }
    

    With Jobs:

    $MaxThread = 10
    $RunspacePool = [runspacefactory]::CreateRunspacePool(
        [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
    )
    [void]$RunspacePool.SetMaxRunspaces($MaxThread)
    $RunspacePool.Open()
    
    $UserCredential = Get-Credential
    
    Import-Module ActiveDirectory
    
    
    $Users = Get-ADUser -filter * -Properties * -SearchBase "OU=Disabled User Accounts,DC=private,DC=private"
    
    $Today = (Get-Date).ToShortDateString()
    $OneMonthAgo = (Get-Date).AddMonths(-1).ToShortDateString()
    
    [Collections.ArrayList]$results = @()    
    
    $OnPrem = $Users | Where-Object {$_.mDBUseDefaults -eq "True"}
    $365 = $Users | Where-Object{$_.mDBUseDefaults -ne "True"}
    
    Write-Host "Start Date: " $OneMonthAgo -ForegroundColor Green
    Write-Host "Total Users OnPrem: " ($OnPrem.mail).Count -ForegroundColor Green
    
    
    $OnPremScriptblock = {
        Param (
            [System.Management.Automation.PSCredential]$Credential,
            [string]$emailAddress,
            [string]$startTime,
            [string]$userName,
            [string]$loginName,
            [string]$DisabledDate,
            [string]$OfficeLocation
        )      
    
        $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://aserversomewhere.local/PowerShell/ -Authentication Kerberos -Credential $Credential
        Import-PSSession $Session -AllowClobber -DisableNameChecking -Verbose:$false | Out-Null
    
        $MessageCount = Get-MessageTrackingLog -recipients $emailAddress -Start $startTime.ToString() -ResultSize unlimited  
    
        $Object = New-Object PSObject -Property @{
            User = $userName
            Login = $loginName
            Email = $emailaddress
            Type = "OnPrem"
            DisabledDate = $DisabledDate
            Location = $OfficeLocation
            MessagesReceived = $MessageCount.Count.ToString()
            }           
    
         $Object
    
    
    }
    
    $jobs = New-Object System.Collections.ArrayList
    foreach ($user in $OnPrem){
    
        $PowerShell = [PowerShell]::Create()
    
        $null = $PowerShell.AddScript($OnPremScriptblock)
        $null = $PowerShell.AddArgument($UserCredential)
        $null = $PowerShell.AddArgument($user.mail)
        $null = $PowerShell.AddArgument($OneMonthAgo)
        $null = $PowerShell.AddArgument($user.name)
        $null = $PowerShell.AddArgument($user.samaccountname)
        $null = $PowerShell.AddArgument($user.modified)
        $null = $PowerShell.AddArgument($user.Office)
    
        $PowerShell.RunspacePool = $RunspacePool
    
        [void]$jobs.Add((
        [pscustomobject]@{
            PowerShell = $PowerShell
            Handle = $PowerShell.BeginInvoke()
        }
        ))
    }
    
    While($jobs.handle.IsCompleted -eq $false){
        Write-Host "." -NoNewline
        Start-Sleep -Milliseconds 100
    }
    
    $return = $jobs | foreach{
        $_.PowerShell.EndInvoke($_.Handle)
        $_.PowerShell.Dispose()
    }
    $jobs.Clear()
    $return
    

    The results are stored in $return