Friday, July 07, 2017

Exchange Hybrid Mailbox Move - Corruption Due To Missing Security Principals (ACL issues)

About midway through 2016, a change was introduced to Exchange Online whereby if a security principal could not be successfully validated/mapped to an Exchange Online object, it would be marked as a bad item. Previously, the behaviour was that invalid permissions would simply be ignored, and administrators were then left to wonder why some permissions no longer worked after the migration. With this new behaviour, corrupt/invalid permissions are now logged so that administrators will know that there are problems with permissions.  The following article covers this in more detail: https://blogs.technet.microsoft.com/exchange/2017/05/30/toomanybaditemspermanentexception-error-when-migrating-to-exchange-online/?replytocom=310635#respond  

The end result users granted mailbox permissions but have since left the organisation and had their AD user account deleted will show as a corrupt item when that mailbox is migrated to Exchange Online.  These errors were previously hidden from the log but they're not marked as corruptions which means you need to raise that corruption level for your mailbox move/s.

In order to decipher which corruptions are are genuine are which are related to ACL issues, you can run the following script.  The script can be run against a migration batch in PowerShell for Exchange Online to determine and output which mailbox migrations have genuine corruptions and require investigation and which do not.

3 files will be output to directory specified with a subfolder with a timestamp prepended.

    1. A summary CSV output including the mailboxes queried along with the details in the table above.
    2. An output of all corrupt bad items found, including ACL security principals, should they need to be queried.

    3. A migrations XML output for each mailbox migration - this can imported into any PowerShell session at a later date and queried; this allows you to remove the migration job but keep a logged record.

Here is an example of what is output to screen:




<#
.NOTES
    Author: Ben Owens
    LinkedIn: https://www.linkedin.com/in/owensben/
    Creation Date: 04/07/2017
    Purpose: This script will query all the mailboxes in a migration batch 
    and detail which of the reported corruptions are genuine and which 
    corruptions related to ACL security principals that don't exist in the source or target forests.  
    This is to help manage the  change in behaviour as covered in the article 
    https://blogs.technet.microsoft.com/exchange/2017/05/30/toomanybaditemspermanentexception-error-when-migrating-to-exchange-online/?replytocom=310635#respond 
.INPUTS
    You will be prompted to enter a batch name when you ruin the script
    
.OUTPUTS
    Configure the $LogDirectory variable at the top of the script.
    3 files will be output to directory specified with a subfolder with a timestamp prepended.
    1. A summary CSV output including the mailboxes queried along with the details in the table above.
    2. An output of all corrupt bad items found, including ACL security principals, should they need to be queried.
    3. A migrations XML output for each mailbox migration - this can imported into any PowerShell session at a later date and queried; this allows you to remove the migration job but keep a logged record.

#>

$LogDate = get-date -f yyyyMMddhhmmss
$LogDirectory = Split-Path -parent "C:\BenTemp\$LogDate\*.*"
$Results = @()

$BatchID = Read-Host -Prompt 'Enter the batch name here'
Mkdir $LogDirectory

$MigrationUsers = Get-MigrationUser -BatchID $BatchID

ForEach ($User in $MigrationUsers) {
    $Statistics = Get-MoveRequestStatistics -Identity $User.Identity -IncludeReport
    #$MoveStatistics = Get-MoveRequestStatistics -Identity $User.Identity
    $MoveStatus = $Statistics | Select -ExpandProperty Status | Select -ExpandProperty Value
    $MoveStatusDetail = $Statistics | Select -ExpandProperty StatusDetail | Select -ExpandProperty Value
    $Identity = Get-User $User.Identity | Select -ExpandProperty Name
    $XMLPath = $logdirectory + "\" + $Identity + "_MigrationReport.xml"
    $Statistics | Export-CliXml $XMLPath
    $ReportedFailures = $Statistics.Report.BadItems
    $CSVPath = $logdirectory + "\" + $Identity + "_CorruptItemsOverview.csv"
    If ($ReportedFailures -eq $NULL) {
        Write-Host "No bad items to report for" $Identity
    }
    Else {
        $ReportedFailures | Select Kind,FolderName,WellKnownFolderType,Subject,Failure,Category | Export-CSV $CSVPath -NoTypeInformation
    }
    $ReportedSourceACLFailures = $ReportedFailures | Where {$_.Category -like "SourcePrincipalError"}
    $ReportedTargetACLFailures = $ReportedFailures | Where {$_.Category -like "TargetPrincipalError"}
    $FilteredReportedFailures = $ReportedFailures | Where {$_.Category -notlike "SourcePrincipalError"}
    $FilteredReportedFailures = $FilteredReportedFailures | Where {$_.Category -notlike "TargetPrincipalError"}
    
    If ($ReportedFailures -eq $NULL) {
        $Result = new-object PSObject -Property @{
                        #Identity = $MigrationUsers.Identity;
                        Identity = $Identity;
                        MoveStatus = $MoveStatus;
                        MoveStatusDetail = $MoveStatusDetail;
                        Corruptions = $ReportedFailures.count
                        GenuineCorruptions = $FilteredReportedFailures.count
                        SourcePrincipalErrors = $ReportedSourceACLFailures.count
                        TargetPrincipalErrors = $ReportedTargetACLFailures.count
                        Comment = "No corruptions to investigate"
                    }
                    $Results += $Result
    }

    ElseIf ($FilteredReportedFailures -eq $NULL) {
        $Result = new-object PSObject -Property @{
                        #Identity = $MigrationUsers.Identity;
                        Identity = $Identity;
                        MoveStatus = $MoveStatus;
                        MoveStatusDetail = $MoveStatusDetail
                        Corruptions = $ReportedFailures.count
                        GenuineCorruptions = $FilteredReportedFailures.count
                        SourcePrincipalErrors = $ReportedSourceACLFailures.count
                        TargetPrincipalErrors = $ReportedTargetACLFailures.count
                        Comment = "Only ACL issues - no need to investigate"
                    }
                    $Results += $Result
    }
    Else {
        $Result = new-object PSObject -Property @{
                        #Identity = $MigrationUsers.Identity;
                        Identity = $Identity;
                        MoveStatus = $MoveStatus;
                        MoveStatusDetail = $MoveStatusDetail;
                        Corruptions = $ReportedFailures.count
                        GenuineCorruptions = $FilteredReportedFailures.count
                        SourcePrincipalErrors = $ReportedSourceACLFailures.count
                        TargetPrincipalErrors = $ReportedTargetACLFailures.count
                        Comment = "Investigation required!"
                    }
                    $Results += $Result
    }
}

$Results | ft Identity, MoveStatus, MoveStatusDetail,Corruptions,SourcePrincipalErrors,TargetPrincipalErrors,GenuineCorruptions,Comment
$ResultsPath = $logdirectory + "\" + "_ReportSummary.csv"
$Results | Select Identity, MoveStatus, MoveStatusDetail,Corruptions,SourcePrincipalErrors,TargetPrincipalErrors,GenuineCorruptions,Comment | Export-CSV $ResultsPath -NoTypeInformation

Write-Host "Go to" $LogDirectory "for output log files" -ForegroundColor Yellow

1 comment:

  1. This is exceptional work and super useful. Thanks very much for the effort you've made to produce this.

    ReplyDelete