ExchangeMigration.psm1
# EM (ExchangeMigration) Powershell Module # Author: Pietro Ciaccio | LinkedIn: https://www.linkedin.com/in/pietrociaccio | Twitter: @PietroCiac # Start ################################################################################################################ write-host "" write-host "Exchange Migration Powershell Module / 0.16.1-Alpha" -ForegroundColor yellow -BackgroundColor black # Module wide variables ################################################################################################################ $ModuleSourceCred = $null $ModuleSourceDomain = $null $ModuleSourceEndPoint = $null $ModuleSourcePDC = $null $ModuleSourceDomainSID = $null $ModuleSourceNBDomain = $null $ModuleSourceDN = $null $ModuleTargetCred = $null $ModuleTargetDomain = $null $ModuleTargetEndPoint = $null $ModuleTargetPDC = $null $ModuleTargetDomainSID = $null $ModuleTargetNBDomain = $null $ModuleTargetDN = $null $ModuleDefaultRollbackDatabase = $null $ModuleActivity = "Migrate" $ModuleMode = "LogOnly" $ModuleMoveMailbox = "No" $ModuleLink = "" $ModuleSeparate = $false $ModuleLogPath = "C:\Temp\EM" $ModuleThreads = 8 $ModuleWait = $false $ModuleSourceGALSyncOU = $null $ModuleTargetGALSyncOU = $null $ModuleEMConfigPath = $env:LOCALAPPDATA + "\EM\ExchangeMigration.config" $ModuleEMDataPath = $ModuleLogPath + "\Data\" $ModuleEMBackupPath = $ModuleLogPath + "\BackUp\" # Log management ################################################################################################################ function Start-EMCheckPaths() { $Script:ModuleEMDataPath = $Script:ModuleLogPath + "\Data\" $Script:ModuleEMBackupPath = $Script:ModuleLogPath + "\BackUp\" if (!(test-path $Script:ModuleLogPath)) { try { new-item $Script:ModuleLogPath -Type directory -Force | out-null } catch { throw "Unable to create logging directory '$Script:ModuleLogPath'" } } if (!(test-path $Script:ModuleEMDataPath)) { try { new-item $Script:ModuleEMDataPath -Type directory -Force | out-null } catch { throw "Unable to create logging directory '$Script:ModuleEMDataPath'" } } if (!(test-path $Script:ModuleEMBackupPath)) { try { new-item $Script:ModuleEMBackupPath -Type directory -Force | out-null } catch { throw "Unable to create logging directory '$Script:ModuleEMBackupPath'" } } } function Write-log ($type, $comment) { $type = $type.toupper() if ($comment.length -gt $([int]($Host.UI.RawUI.WindowSize.Width * 0.9))) { $comment = $comment.substring(0, $([int]($Host.UI.RawUI.WindowSize.Width * 0.9))) + "..." } write-host $comment" " -nonewline switch ($type) { "OK" { write-host '[' -nonewline; write-host "$type" -fore green -nonewline; write-host "]" } "WARN" { write-host '[' -nonewline; write-host "$type" -fore yellow -nonewline; write-host "]" } "ERR" { write-host '[' -nonewline; write-host "$type" -fore red -nonewline; write-host "]" } "AR" { write-host '[' -nonewline; write-host "$type" -fore magenta -nonewline; write-host "]" } default { write-host "" } } } function Write-Slog () { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity = $null, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Type = $null, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Comment = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Console = $true ) Process { #check for dir $LogPath = $null; $LogPath = $Script:ModuleLogPath $Ref = ("{0:yyyyMMddHHmmssfff}" -f (get-date)).tostring() $identity = $identity.toupper() if ($comment) {$comment = $comment.trim()} $thisserver = $null; $thisserver = ($env:computername).toupper() $escaped = @('\','/',':','*','?','"','<','>','|') $filename = $null; $filename = $identity $escaped | % {if ($filename -match "\$_"){$filename = $filename -replace "\$_",""}} $out = $null; $out = "$LogPath\EM_$($thisserver)_$($Script:ModuleSourceDomain)_$($Script:ModuleTargetDomain)_$filename.log" [pscustomobject]@{ Ref = $Ref Timestamp = $(get-date) Identity = $($identity.toupper()) Type = $($type.toupper()) Comment = $comment } | % { if ($Console) { write-log $type "$ref $identity $comment" } if (!(test-path $out)) { $_ | convertto-csv -notypeinformation | out-file $out -append -encoding utf8 } else { $_ | convertto-csv -notypeinformation | ? {$_ -notmatch '"ref","timestamp","identity","type","comment"'} | out-file $out -append -encoding utf8 } } if ($type -eq "err") { throw $comment } } } function Read-EMLogs () { <# .SYNOPSIS Get ExchangeMigration Logs. .DESCRIPTION This cmdlet allows you to review the migration or batch logs associated with EM activities. .PARAMETER Identity This is the samaccountname of the Active Directory object or the name of the batch job. This is used to identify the log you wish to review. .PARAMETER Type This is the type of log entry, e.g. LOG, WARN, ERR. .PARAMETER Ref Every log entry has a reference. This parameter allows you to specify which log entry you wish to review. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$Type = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$Ref = $null ) Process { $LogPath = $null; $LogPath = $Script:ModuleLogPath $identity = $identity.toupper() $thisserver = $null; $thisserver = ($env:computername).toupper() $escaped = @('\','/',':','*','?','"','<','>','|') $filename = $null; $filename = $identity $escaped | % {if ($filename -match "\$_"){$filename = $filename -replace "\$_",""}} $out = $null; $out = "$LogPath\EM_$($thisserver)_$($Script:ModuleSourceDomain)_$($Script:ModuleTargetDomain)_$filename.log" try { $log = $null; $log = import-csv $out } catch { throw "Issue getting log data for '$($identity)'" } if ($type) { $log = $log | ? {$_.type -eq $type} } if ($ref) { $log = $log | ? {$_.ref -eq $ref} } $log } } function Start-EMLogsArchive () { <# .SYNOPSIS Archives ExchangeMigration Logs. .DESCRIPTION This cmdlet will package all .log files and compress them into a single zip using a timestamp based naming convention. #> #=============================================================================================================== $LogPath = $null; $LogPath = $Script:ModuleLogPath $timestamp = ("{0:yyyyMMddHHmmss}" -f (get-date)).tostring() $logs = $null; $logs = gci "$LogPath\*.log" try { if ($logs) {$logs | compress-archive -destinationpath "$LogPath\EM$($timestamp).zip"} } catch {throw "Issue archiving"} try { if ($logs) {$logs | rm -force -confirm:$false} } catch {throw "Issue removing logs"} } function Clear-EMData() { <# .SYNOPSIS Deletes all logs, archives, and data. .DESCRIPTION This cmdlet will delete all logs and archives. #> #=============================================================================================================== write-host "This will delete all logs, archives, and data! " -nonewline write-host "Are you sure? " -nonewline; write-host "[Y] Yes" -fore yellow -nonewline; write-host ' [N] No (default is "Y")' -nonewline [ValidateSet('Yes','No','Y','N',$null)][string]$read = Read-Host -Prompt " " if ($read -match "^No$|^N$") { write-log "WARN" "Aborted" break } try { gci c:\temp\EM | remove-item -recurse -force -confirm:$false -ea stop Start-EMCheckPaths write-log "OK" "Done" } catch { write-log "ERR" "Issue deleting" } } # Function to read out the configuration data loaded into the module function Get-EMConfiguration () { <# .SYNOPSIS Displays the ExchangeMigration configuration. .DESCRIPTION This cmdlet displays the configuration that has been loaded for the EM module to use. The EM configuration is the baseline for all activities unless overridden at runtime. #> #=============================================================================================================== [pscustomobject]@{ SourceCred = $ModuleSourceCred SourceDomain = $ModuleSourceDomain SourceEndPoint = $ModuleSourceEndPoint SourceGALSyncOU = $ModuleSourceGALSyncOU SourcePDC = $ModuleSourcePDC SourceDomainSID = $ModuleSourceDomainSID SourceNBDomain = $ModuleSourceNBDomain SourceDN = $ModuleSourceDN TargetCred= $ModuleTargetCred TargetDomain = $ModuleTargetDomain TargetEndPoint = $ModuleTargetEndPoint TargetGALSyncOU = $ModuleTargetGALSyncOU TargetPDC = $ModuleTargetPDC TargetDomainSID = $ModuleTargetDomainSID TargetNBDomain = $ModuleTargetNBDomain TargetDN = $ModuleTargetDN Activity = $ModuleActivity Link = $ModuleLink LogPath = $ModuleLogPath Mode = $ModuleMode MoveMailbox = $ModuleMoveMailbox Threads = $ModuleThreads Wait = $ModuleWait DefaultRollbackDatabase = $ModuleDefaultRollbackDatabase } } function Read-EMData () { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Raw = $false ) Process { $LogPath = $null; $LogPath = $Script:ModuleEMDataPath $thisserver = $null; $thisserver = ($env:computername).toupper() $in = $null; $in = "$LogPath\EM_$($thisserver)_$($Script:ModuleSourceDomain)_$($Script:ModuleTargetDomain)_$identity.emdata" try { $data = $null; if (test-path $in){ $data = gc $in -ea stop | ? {$_} } } catch { throw "Issue getting post migration data for '$($identity)'" } if ($raw -eq $false) { $returndata = $null; if ($data) { $returndata = $data | % { try{ $_ | convertfrom-json }catch{ write-Slog "$identity" "WARN" "'$_' Issue converting from JSON and will be excluded" } } } return $returndata | sort type,data } else { return $data | ? {$_} | sort } } } function Write-EMData () { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity = $null, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Type = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)]$Data = $null ) Process { Start-EMCheckPaths $read = $null; $read = Read-EMData -identity $identity $read = $read | ? {$_.type -ne $type} $LogPath = $null; $LogPath = $Script:ModuleEMDataPath $identity = $identity.toupper() $thisserver = $null; $thisserver = ($env:computername).toupper() $out = $null; $out = "$LogPath\EM_$($thisserver)_$($Script:ModuleSourceDomain)_$($Script:ModuleTargetDomain)_$identity.emdata" try { $obj = $null; $obj = [pscustomobject]@{ Type = $($type.toupper()) Data = $Data } $outputdata = $null; $outputdata = @(); $outputdata += $read | %{$_} $outputdata += $obj $outputdata = $outputdata | sort type,data $outputdata = $outputdata | % {$_ | convertto-json -compress} $outputdata | out-file $out } catch { write-Slog "$identity" "WARN" "'$_' issue converting to JSON and will be excluded" } } } function Update-EMData () { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity = $null, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Type = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)]$Data = $null ) Process { Start-EMCheckPaths $read = $null; $read = Read-EMData -identity $identity -raw $true $LogPath = $null; $LogPath = $Script:ModuleEMDataPath $identity = $identity.toupper() $thisserver = $null; $thisserver = ($env:computername).toupper() $out = $null; $out = "$LogPath\EM_$($thisserver)_$($Script:ModuleSourceDomain)_$($Script:ModuleTargetDomain)_$identity.emdata" try { [pscustomobject]@{ Type = $($type.toupper()) Data = $Data } | % { $update = $null; $update = $_ | convertto-json -compress } $commit = $null $commit = $read | ? {$_ -ne $update} if ($commit) { $commit | out-file $out } else { remove-item $out -confirm:$false -force } } catch { write-Slog "$identity" "WARN" "Issue updating '$out' and will be excluded" } } } # Pre-load, read, and apply configuration ################################################################################################################ # Validate credentials #=============================================================================================================== Add-Type -AssemblyName System.DirectoryServices.AccountManagement function Test-EMCredential() { Param ( [Parameter(mandatory=$true)][string]$DomainName, [Parameter(mandatory=$false)][string]$Username, [Parameter(mandatory=$false)][System.Security.SecureString]$SecurePassword ) Process { try { $Password = New-Object system.Management.Automation.PSCredential("user", $SecurePassword) $Password = $Password.GetNetworkCredential().Password $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Domain,$domainname) $DS.ValidateCredentials($username,$Password) } catch { $false } } } function Read-EMConfiguration() { #=============================================================================================================== if (test-path $Script:ModuleEMConfigPath) { try { $Secureconfig = $null; $Secureconfig = Get-Content $Script:ModuleEMConfigPath $Secureconfig = $Secureconfig | ConvertTo-SecureString $Secureconfig = New-Object system.Management.Automation.PSCredential("user", $Secureconfig) $Config = $Secureconfig.GetNetworkCredential().Password $Config = $Config | ConvertFrom-Json $Config } catch {throw "There was an issue reading the configuration data."} } else { throw "EM configuration file not found." } } function Test-EMConfiguration () { <# .SYNOPSIS Test the ExchangeMigration configuration. .DESCRIPTION This cmdlet will test the credentials and settings that are specified in the EM configuration file. This can be used to rule out any issues that may be related to configuration, e.g. credentials. #> #=============================================================================================================== write-host "" $Config = Get-EMConfiguration # Health checking source if (Test-EMCredential -DomainName $($Config.SourceDomain) -Username $($Config.SourceCred.Username) -SecurePassword $($Config.SourceCred.Password)) { write-log "OK" "Source Credential '$($Config.SourceCred.Username)'" } else { write-log "ERR" "Source Credential '$($Config.SourceCred.Username)'" } if (test-connection $($Config.SourceDomain) -Quiet) { write-log "OK" "Source Domain '$($Config.SourceDomain)'" } else { write-log "ERR" "Source Domain '$($Config.SourceDomain)'" } if (test-connection $($Config.SourceEndPoint) -Quiet) { write-log "OK" "Source End Point '$($Config.SourceEndPoint)'" } else { write-log "ERR" "Source End point '$($Config.SourceEndPoint)'" } if ($(try{Get-ADObject $($Config.SourceGALSyncOU) -Server $($config.SourceDomain) -Credential $($Config.SourceCred)}catch{})){ write-log "OK" "Source GAL OU '$($Config.SourceGALSyncOU)'" } else { write-log "ERR" "Source GAL OU '$($Config.SourceGALSyncOU)'" } # Health checking target if (Test-EMCredential -DomainName $($Config.TargetDomain) -Username $($Config.TargetCred.Username) -SecurePassword $($Config.TargetCred.Password)) { write-log "OK" "Target Credential '$($Config.TargetCred.Username)'" } else { write-log "ERR" "Target Credential '$($Config.TargetCred.Username)'" } if (test-connection $($Config.TargetDomain) -Quiet) { write-log "OK" "Target Domain '$($Config.TargetDomain)'" } else { write-log "ERR" "Target Domain '$($Config.TargetDomain)'" } if (test-connection $($Config.TargetEndPoint) -Quiet) { write-log "OK" "Target End Point '$($Config.TargetEndPoint)'" } else { write-log "ERR" "Target End Point '$($Config.TargetEndPoint)'" } if ($(try{Get-ADObject $($Config.TargetGALSyncOU) -Server $($config.TargetDomain) -Credential $($Config.TargetCred)}catch{})){ write-log "OK" "Target GAL OU '$($Config.TargetGALSyncOU)'" } else { write-log "ERR" "Target GAL OU '$($Config.TargetGALSyncOU)'" } if (test-path $($Config.LogPath)) { write-log "OK" "LogPath '$($Config.LogPath)'" } else { write-log "ERR" "LogPath '$($Config.LogPath)'" } write-host "" } function Enable-EMConfiguration() { #=============================================================================================================== try { $Config = Read-EMConfiguration $SourceCred = $null; $SourceCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $($Config.SourceUsername),$($Config.SourcePassword | ConvertTo-SecureString) $TargetCred = $null; $TargetCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $($Config.TargetUsername),$($Config.TargetPassword | ConvertTo-SecureString) # Committing $Script:ModuleSourceCred = $SourceCred $Script:ModuleSourceDomain = $Config.SourceDomain.toupper() $Script:ModuleSourceEndPoint = $config.SourceEndPoint.toupper() $Script:ModuleSourceGALSyncOU = $Config.SourceGALSyncOU $Script:ModuleSourcePDC = $Config.SourcePDC $Script:ModuleSourceDomainSID = $Config.SourceDomainSID $Script:ModuleSourceNBDomain = $Config.SourceNBDomain $Script:ModuleSourceDN = $Config.SourceDN $Script:ModuleTargetCred = $TargetCred $Script:ModuleTargetDomain = $Config.TargetDomain.toupper() $Script:ModuleTargetEndPoint = $Config.TargetEndPoint.toupper() $Script:ModuleTargetGALSyncOU = $Config.TargetGALSyncOU $Script:ModuleTargetPDC = $Config.TargetPDC $Script:ModuleTargetDomainSID = $Config.TargetDomainSID $Script:ModuleTargetNBDomain = $Config.TargetNBDomain $Script:ModuleTargetDN = $Config.TargetDN $Script:ModuleDefaultRollbackDatabase = $Config.DefaultRollbackDatabase $Script:ModuleLogPath = $Config.LogPath # Checking for mandatory settings if ([string]::IsNullOrEmpty($config.DefaultRollbackDatabase) -OR ` [string]::IsNullOrEmpty($config.LogPath) -OR ` [string]::IsNullOrEmpty($config.SourceDN) -OR ` [string]::IsNullOrEmpty($config.SourceDomain) -OR ` [string]::IsNullOrEmpty($config.SourceDomainSID) -OR ` [string]::IsNullOrEmpty($config.SourceEndPoint) -OR ` [string]::IsNullOrEmpty($config.SourceGALSyncOU) -OR ` [string]::IsNullOrEmpty($config.SourceNBDomain) -OR ` [string]::IsNullOrEmpty($config.SourcePassword) -OR ` [string]::IsNullOrEmpty($config.SourcePDC) -OR ` [string]::IsNullOrEmpty($config.SourceUsername) -OR ` [string]::IsNullOrEmpty($config.TargetDN) -OR ` [string]::IsNullOrEmpty($config.TargetDomain) -OR ` [string]::IsNullOrEmpty($config.TargetDomainSID) -OR ` [string]::IsNullOrEmpty($config.TargetEndPoint) -OR ` [string]::IsNullOrEmpty($config.TargetGALSyncOU) -OR ` [string]::IsNullOrEmpty($config.TargetNBDomain) -OR ` [string]::IsNullOrEmpty($config.TargetPassword) -OR ` [string]::IsNullOrEmpty($config.TargetPDC) -OR ` [string]::IsNullOrEmpty($config.TargetUsername)) { write-log "WARN" "Parameter/s missing from configuration. Invoking 'Write-EMConfiguration'." write-host "" Write-EMConfiguration break } # Check configuration before continuing if ($Script:ModuleSourceDomain -eq $Script:ModuleTargetDomain) { write-log "ERR" "Source and target domains cannot be of the same value. Run 'Write-EMConfiguration' with the -SourceDomain or -TargetDomain parameters to correct the issue." } if ($Script:ModuleSourceEndPoint -eq $Script:ModuleTargetEndPoint) { write-log "ERR" "Source and target endpoints cannot be of the same value. Run 'Write-EMConfiguration' with the -SourceEndPoint or -TargetEndPoint parameters to correct the issue." } Start-EMCheckPaths write-log "OK" "Configuration successfully enabled." write-log "LOG" "Logging to '$Script:ModuleLogPath'." write-host "" write-host "Use 'Get-EMConfiguration' to review." write-host "To create a new configuration use the 'Write-EMConfiguration' cmdlet." write-host "To test configuration data settings use 'Test-EMConfiguraion'." write-host "" } catch {throw "There was an issue enabling the configuration data. $($_.exception.message)"} } function Write-EMConfiguration() { <# .SYNOPSIS Creates the ExchangeMigration configuration file and loads it for EM to use. .DESCRIPTION Creates the ExchangeMigration configuration file and loads it for EM to use. This file is called ExchangeMigration.config and is stored C:\Users\samaccountname\AppData\Local\EM\. .PARAMETER SourceDomain Specify the source domain. .PARAMETER SourceEndPoint Specify the source end point for the source Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER SourceGALSyncOU Specify the OU where you would like GALSync objects to be created / moved to in the source domain. .PARAMETER TargetDomain Specify the target domain. .PARAMETER TargetEndPoint Specify the target end point for the target Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER TargetGALSyncOU Specify the OU where you would like GALSync objects to be created / moved to in the target domain. .PARAMETER LogPath Specify the log path to be used by EM. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false)][string]$SourceGALSyncOU = $Script:ModuleSourceGALSyncOU, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false)][string]$TargetEndPoint = $Script:ModuleTargetEndPoint, [Parameter(mandatory=$false)][string]$TargetGALSyncOU = $Script:ModuleTargetGALSyncOU, [Parameter(mandatory=$false)][string]$DefaultRollbackDatabase = $Script:ModuleDefaultRollbackDatabase, [Parameter(mandatory=$false)][string]$LogPath = $Script:ModuleLogPath ) Process { if (!($sourceCred)) { write-host "" write-host "Username for Source administrator? (As Domain\UPN)> " -nonewline $SourceUsername = read-host write-host "Password for Source administrator?> " -nonewline; $SourcePassword = read-host -AsSecureString | ConvertFrom-SecureString $SourceCred = $null; $SourceCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $($SourceUsername),$($SourcePassword | ConvertTo-SecureString) } else { $SourceUsername = $SourceCred.UserName $SourcePassword = $SourceCred.password | ConvertFrom-SecureString } if (!($SourceDomain)) {$SourceDomain = read-host "SourceDomain"} if (!($SourceEndPoint)) {$SourceEndPoint = read-host "SourceEndPoint"} if (!($SourceGALSyncOU)) {$SourceGALSyncOU = read-host "SourceGALSyncOU"} if (!($DefaultRollbackDatabase)) {$DefaultRollbackDatabase = read-host "DefaultRollbackDatabase"} try { get-addomain -Server $sourcedomain -credential $sourcecred -ea stop | % { $sourcepdc = $_.pdcemulator $sourcedomainsid = $_.domainsid.value $sourcenbdomain = $_.netbiosname.tostring() $sourcedn = $_.distinguishedname } } catch { throw "Issue getting domain information for source domain '$sourcedomain'. $($_.exception.message)" } if (!($targetcred)) { write-host "" write-host "Username for Target administrator? (As Domain\UPN)> " -nonewline $TargetUsername = read-host write-host "Password for Target administrator?> " -nonewline; $TargetPassword = read-host -AsSecureString | ConvertFrom-SecureString $TargetCred = $null; $TargetCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $($TargetUsername),$($TargetPassword | ConvertTo-SecureString) } else { $TargetUsername = $TargetCred.UserName $TargetPassword = $TargetCred.password | ConvertFrom-SecureString } if (!($TargetDomain)) {$TargetDomain = read-host "TargetDomain"} if (!($TargetEndPoint)) {$TargetEndPoint = read-host "TargetEndPoint"} if (!($TargetGALSyncOU)) {$TargetGALSyncOU = read-host "TargetGALSyncOU"} try { get-addomain -Server $targetdomain -credential $targetcred -ea stop | % { $targetpdc = $_.pdcemulator $targetdomainsid = $_.domainsid.value $targetnbdomain = $_.netbiosname.tostring() $targetdn = $_.distinguishedname } } catch { throw "Issue getting domain information for target domain '$targetdomain'. $($_.exception.message)" } try { $Config = $null; $Config = [pscustomobject]@{ SourceUsername = $SourceUsername SourcePassword = $SourcePassword SourceDomain = $SourceDomain SourceEndPoint = $SourceEndPoint SourcePDC = $sourcepdc SourceDomainSID = $sourcedomainsid SourceNBDomain = $SourceNBDomain SourceDN = $SourceDN TargetUsername = $TargetUsername TargetPassword = $TargetPassword TargetDomain = $TargetDomain TargetEndPoint = $TargetEndPoint TargetPDC = $Targetpdc TargetDomainSID = $Targetdomainsid TargetNBDomain = $TargetNBDomain TargetDN = $TargetDN LogPath = $LogPath SourceGALSyncOU = $SourceGALSyncOU TargetGALSyncOU = $TargetGALSyncOU DefaultRollbackDatabase = $DefaultRollbackDatabase } } catch { throw "There is an issue with the information provided."} try { $config = $config | convertto-json } catch {throw "There was an issue converting the configuration data to JSON."} try { $Secureconfig = $null; $Secureconfig = $Config | ConvertTo-SecureString -Force -AsPlainText $Secureconfig = $Secureconfig | ConvertFrom-SecureString } catch {throw "There was an issue protecting the configuration data."} if (!(test-path $($Script:ModuleEMConfigPath -replace "ExchangeMigration.config"))) { try { new-item $($Script:ModuleEMConfigPath -replace "ExchangeMigration.config") -Type directory -Force | out-null } catch { throw "Unable to create logging directory '$LogPath'" } } write-host "" try{ $Secureconfig | out-file $Script:ModuleEMConfigPath -Force -Confirm:$false write-log "OK" "Configuration written to '$Script:ModuleEMConfigPath'" } catch {throw "There was an issue writing the configuration to disk."} Enable-EMConfiguration } } write-host "" # Load ExchangeMigration.config if present if (Test-Path $Script:ModuleEMConfigPath) { write-log "OK" "Configuration detected. '$Script:ModuleEMConfigPath'" Enable-EMConfiguration } else { write-log "WARN" "Configuration not detected. Invoking 'Write-EMConfiguration'" Write-EMConfiguration } # Module wide functions ################################################################################################################ Function Compare-ObjectProperties { Param( [PSObject]$ReferenceObject, [PSObject]$DifferenceObject ) $objprops = $ReferenceObject | Get-Member -MemberType Property,NoteProperty | % Name $objprops += $DifferenceObject | Get-Member -MemberType Property,NoteProperty | % Name $objprops = $objprops | Sort | Select -Unique $diffs = @() foreach ($objprop in $objprops) { $diff = Compare-Object $ReferenceObject $DifferenceObject -Property $objprop if ($diff) { $diffprops = @{ PropertyName=$objprop RefValue=($diff | ? {$_.SideIndicator -eq '<='} | % $($objprop)) DiffValue=($diff | ? {$_.SideIndicator -eq '=>'} | % $($objprop)) } $diffs += New-Object PSObject -Property $diffprops } } if ($diffs) { ($diffs | Select PropertyName,RefValue,DiffValue)} } function Write-EMBackUp() { [cmdletbinding()] Param ( [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Object ) Process { Start-EMCheckPaths $timestamp = ("{0:yyyyMMddHHmmss}" -f (get-date)).tostring() if ($object) { try { $objectdn = $object.distinguishedname; $objectdn = $objectdn.tolower() $objectdn = ($objectdn.substring($objectdn.indexof('dc=')+3) -replace (',dc=','.')).toupper() $objectout = $null; $objectout = $Script:ModuleEMBackupPath + $($object.objectguid) + "_" + $objectdn + "_" + $timestamp + ".json" $object | select samaccountname,objectclass,mailnickname,objectguid,legacyexchangedn,distinguishedname,displayname,mail,proxyaddresses,targetaddress,msExchRemoteRecipientType,msExchRecipientDisplayType,msExchRecipientTypeDetails,mDBStorageQuota,mDBOverQuotaLimit,mDBOverHardQuotaLimit,mDBUseDefaults | convertto-json | out-file $objectout -encoding utf8 if ($object.samaccountname) { write-Slog "$($object.samaccountname)" "LOG" "Writing backup GUID: $($object.objectguid.guid); DN: $($object.distinguishedname)" } else { if ($object.mailnickname) { write-Slog "$($object.mailnickname)" "LOG" "Writing backup GUID: $($object.objectguid.guid); DN: $($object.distinguishedname)" } } } catch { throw "Issue writing backup object." } } } } function Read-EMBackUp() { [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$GUID ) Process { try { $backups = $null; $backups = gci "$($Script:ModuleEMBackupPath)$GUID*" | sort lastwritetime $backups | % { $backup = $null; $backup = $_ | gc | convertfrom-json $backup | Add-Member -type NoteProperty -name "filename" -value $($_.versioninfo.filename) -Force return $backup } } catch { throw "Issue reading backup objects" } } } function Get-EMSecondaryDistinguishedNames() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Attribute, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$PrimaryCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$PrimaryPDC, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)]$PrimaryDNs, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SecondaryCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SecondaryPDC ) Process { if ($primaryDNs) { # get sams $psams = $null; $psams = @(); $primaryDNs | % { $dn = $null; $dn = $_ try { $adobject = $null; $adobject = get-adobject $dn -properties mailnickname,samaccountname -server $primarypdc -credential $primarycred if (($adobject | measure).count -eq 1) { $psams += $adobject } if (($adobject | measure).count -gt 1) { write-Slog "$identity" "WARN" "'$dn' $Attribute multiple user objects returned from primary domain and will be excluded" } } catch { write-Slog "$identity" "WARN" "'$dn' Issue getting $Attribute in primary domain and will be excluded" } } $allsams = $null; $allsams = @() $psams | % {$allsams += $_} $allsams = $allsams | sort | get-unique # get distinguishednames from secondary if ($allsams) { $sdns = $null; $sdns = @(); $allsams | % { $sam = $null; $sam = $_.samaccountname $mailnickname = $null; $mailnickname = $_.mailnickname try { if ($sam) { $adobject = $null; $adobject = get-adobject -filter {samaccountname -eq $sam} -properties mailnickname,samaccountname -server $secondarypdc -credential $secondarycred } if (!($sam) -and $mailnickname) { $adobject = $null; $adobject = get-adobject -filter {mailnickname -eq $mailnickname} -properties mailnickname,samaccountname -server $secondarypdc -credential $secondarycred } if (($adobject | measure).count -eq 1) { $sdns += $adobject.distinguishedname } if (($adobject | measure).count -eq 0) { write-Slog "$identity" "WARN" "'$sam' $Attribute no object found in secondary domain and will be excluded" } if (($adobject | measure).count -gt 1) { write-Slog "$identity" "WARN" "'$sam' $Attribute multiple objects found in secondary domain and will be excluded" } } catch { write-Slog "$identity" "ERR" "Samccountname: '$sam' or Mailnickname: '$mailnickname' Issue getting $Attribute in secondary domain. $($_.exception.message)" } } $sdns = $sdns | sort | get-unique | % {$_.tostring()} return $sdns } else { return $null } } else { return $null } } } function Get-EMSecondaryGUIDs() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Attribute, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$PrimaryCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$PrimaryPDC, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)]$PrimaryDNs, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SecondaryCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SecondaryPDC ) Process { if ($primaryDNs) { # get sams $psams = $null; $psams = @(); $primaryDNs | % { $dn = $null; $dn = $_ try { $adobject = $null; $adobject = get-adobject $dn -properties mailnickname,samaccountname -server $primarypdc -credential $primarycred if (($adobject | measure).count -eq 1) { $psams += $adobject.samaccountname } if (($adobject | measure).count -gt 1) { write-Slog "$identity" "WARN" "'$dn' $Attribute multiple user objects returned from primary domain and will be excluded" } } catch { write-Slog "$identity" "WARN" "'$dn' Issue getting $Attribute in primary domain and will be excluded" } } $allsams = $null; $allsams = @() $psams | % {$allsams += $_} $allsams = $allsams | sort | get-unique # get GUIDs from secondary if ($allsams) { $sGUIDs = $null; $sGUIDs = @(); $allsams | % { $sam = $null; $sam = $_ try { $adobject = $null; $adobject = get-adobject -filter {samaccountname -eq $sam} -properties objectguid,samaccountname -server $secondarypdc -credential $secondarycred if (($adobject | measure).count -eq 1) { $sGUIDs += $adobject.objectguid.guid } if (($adobject | measure).count -eq 0) { write-Slog "$identity" "WARN" "'$sam' $Attribute no object found in secondary domain and will be excluded" } if (($adobject | measure).count -gt 1) { write-Slog "$identity" "WARN" "'$sam' $Attribute multiple objects found in secondary domain and will be excluded" } } catch { write-Slog "$identity" "ERR" "'$sam' Issue getting $Attribute in secondary domain. $($_.exception.message)" } } $sGUIDs = $sGUIDs | sort | get-unique | % {$_.tostring()} return $sGUIDs } else { return $null } } else { return $null } } } function Convertto-DistinguishedName() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true)][string]$identity, [Parameter(mandatory=$true)][string]$CanonicalName ) Process { try { $DN = $null; $DN = $CanonicalName -split "/" $DNout = $null; $count = $($DN | measure).count - 1 for ($i = $count; $i -ge 0; $i--) { if ($i -eq $count) { $DNout = "CN=" + $DN[$i] } if ($i -lt $count -and $i -ne 0) { $DNout += ",OU=" + $DN[$i] } if ($i -eq 0) { $DNtemp = $null; $DNtemp = ",DC=" + $DN[$i]; $DNtemp = $DNtemp -replace "\.",",DC=" $DNout += $DNtemp } } return $DNout } catch { write-Slog "$identity" "WARN" "'$Canonicalname' issue converting canonicalname to distringuishedname and will be excluded" } } } function Invoke-EMExchangeCommand() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true)][string]$Endpoint, [Parameter(mandatory=$true)][string]$DomainController, [Parameter(mandatory=$true)][system.management.automation.pscredential]$Credential, [Parameter(mandatory=$true)][string]$Command ) Process { if ($Command -match '`') {$Command = $Command -replace '`','``'} if ($Command -match "$") {$Command = $Command -replace "\$",'`$'} #Correct specific replacements if ($command.contains('`$false')) {$command = $command -replace '`',''} if ($command.contains('`$true')) {$command = $command -replace '`',''} [scriptblock]$scriptblock = [scriptblock]::Create($command) Invoke-Command -ConnectionUri http://$Endpoint/powershell -credential $credential -ConfigurationName microsoft.exchange -scriptblock $scriptblock -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop } } function Set-EMSourceUniqueMailnickname() { [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][String]$GUID, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][Boolean]$Confirm = $true ) Process { try { $source = $null $source = get-adobject -Filter {objectguid -eq $guid} -properties mailnickname -server $Script:ModuleSourcePDC -Credential $Script:ModuleSourceCred -ea Stop if (!($source)) { throw "No object found" } $mailnickname = $null; $mailnickname = $source.mailnickname if ($mailnickname) { $padding = $null; $padding = get-random -Maximum 999 $mailnickname += $padding if ($mailnickname.length -gt 64) { throw "Proposed mailnickname '$mailnickname' greater than 64 characters." } else { write-log "LOG" "Proposed mailnickname '$mailnickname'" } if ($confirm -eq $true) { write-host "Are you sure? " -nonewline; write-host "[Y] Yes" -fore yellow -nonewline; write-host ' [N] No (default is "Y")' -nonewline [ValidateSet('Yes','No','Y','N',$null)][string]$read = Read-Host -Prompt " " if ($read -match "^No$|^N$") { write-log "WARN" "Aborted" break } } $source.mailnickname = $mailnickname set-adobject -instance $source -server $Script:ModuleSourcePDC -Credential $Script:ModuleSourceCred -ea stop write-log "OK" "Set mailnickname to '$mailnickname' in '$Script:ModuleSourceDomain'" } } catch { write-log "ERR" "Issue granting unique mailnickname. $($_.exception.message)" } } } function Get-EMCleanForLDAPQuery($String) { if ($string){ $String = $String -replace "\\","\5C" $String = $String -replace "\*","\2A" $String = $String -replace "\(","\28" $String = $String -replace "\)","\29" $String = $String -replace "Nul","\00" return $string } } function Get-EMConflict() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$sourcepdc, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$targetpdc, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$sourcedomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$targetdomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$targetendpoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred ) Process { $smtps = $($source.proxyaddresses | ? {$_ -match "^smtp:"}) + $("smtp:" + $(Get-EMCleanForLDAPQuery($source.mail))) | % {$_.tolower()} | sort | get-unique $smtps += "smtp:" + $(Get-EMCleanForLDAPQuery($($source.mailnickname))) + "@mail.on" + $targetdomain if (!($smtps)) { write-Slog "$identity" "ERR" "Issues getting SMTPs in source domain '$($sourcedomain)'. $($_.exception.message)" } $x500 = $null; $x500 = "$($source.legacyexchangedn)" if (!($x500)) { write-Slog "$identity" "ERR" "Issue getting X500 / legacyexchangedn in source domain '$($sourcedomain)'. $($_.exception.message)" } $x500 = "X500:" + $(Get-EMCleanForLDAPQuery($x500)) $smtps += $x500 $ldapquery = $null; $($smtps | % {$ldapquery += "(proxyaddresses=" + $_ + ")"}) $ldapquery += "(mailnickname=" + $(Get-EMCleanForLDAPQuery($source.mailnickname)) + ")" $ldapquery = "(|" + $ldapquery + ")" try { $cresult = $null; $cresult = Get-ADObject -LDAPFilter "$ldapquery" -properties samaccountname,proxyaddresses,mailnickname -server $($targetpdc) -Credential $TargetCred if ($cresult) { $cresult | % { if ($source.objectclass -eq "contact"){ if ($_.mailnickname -ne $identity) { $_ } } else { if ($_.samaccountname -ne $identity){ $_ } } } } } catch { write-Slog "$identity" "ERR" "Issue running command '$command'. $($_.exception.message)" } } } function Get-EMRecipientTypeDetails() { #=============================================================================================================== Param ( [Parameter(mandatory=$false)][string]$type ) $data = $type if($type -eq 1) {$data = "UserMailbox"} if($type -eq 2) {$data = "LinkedMailbox"} if($type -eq 4) {$data = "SharedMailbox"} if($type -eq 16) {$data = "RoomMailbox"} if($type -eq 32) {$data = "EquipmentMailbox"} if($type -eq 128) {$data = "MailUser"} if($type -eq 33554432) {$data = "LinkedUser"} if($type -eq 2147483648) {$data = "RemoteUserMailbox"} if($type -eq 8589934592) {$data = "RemoteRoomMailbox"} if($type -eq 17179869184) {$data = "RemoteEquipmentMailbox"} if($type -eq 34359738368) {$data = "RemoteSharedMailbox"} if ($type -eq 268435456) {$data = "roomlist"} if ($type -eq 1073741824) {$data = "rolegroup"} if (!($type)) {$data = "NotMailEnabled"} $data } function Get-EMRecipientDisplayType() { #=============================================================================================================== Param ( [Parameter(mandatory=$true,valuefrompipeline=$true)][string]$type ) $data = $type if($type -eq -2147483642) {$data = "RemoteUserMailbox"} if($type -eq -2147481850) {$data = "RemoteRoomMailbox"} if($type -eq -2147481594) {$data = "RemoteEquipmentMailbox"} if($type -eq 0) {$data = "SharedMailbox"} if($type -eq 1) {$data = "MailUniversalDistributionGroup"} if($type -eq 6) {$data = "MailContact"} if($type -eq 7) {$data = "RoomMailbox"} if($type -eq 8) {$data = "EquipmentMailbox"} if($type -eq 1073741824) {$data = "UserMailbox"} if($type -eq 1073741833) {$data = "MailUniversalSecurityGroup"} if (!($type)) {$data = "NotMailEnabled"} return $data } function Get-EMGroupType() { #=============================================================================================================== Param ( [Parameter(mandatory=$true,valuefrompipeline=$true)][int]$grouptype ) $data = @() if($grouptype -band 0x00000001) {$data += "System"} if($grouptype -band 0x00000002) {$data += "Global"} if($grouptype -band 0x00000004) {$data += "Local"} if($grouptype -band 0x00000008) {$data += "Universal"} if($grouptype -band 0x00000010) {$data += "APP_BASIC"} if($grouptype -band 0x00000020) {$data += "APP_QUERY"} if($grouptype -band 0x80000000) {$data += "Security"} if ($data -notcontains "Security"){$data += "Distribution"} return $data } function Update-EMGroupType() { #=============================================================================================================== Param ( [Parameter(mandatory=$true,valuefrompipeline=$true)][int]$grouptype ) if($grouptype -band 0x00000002) {$grouptype = $grouptype - 0x00000002 + 0x00000008} if($grouptype -band 0x00000004) {$grouptype = $grouptype - 0x00000004 + 0x00000008} return $grouptype } function Get-EMTargetOU() { Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDN, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDN ) Process { try { if ($activity -eq "migrate") { $dn = $null; $dn = $source.distinguishedname; $dn = $dn.tolower() $ou = $null; $ou = $dn.substring($dn.indexof("ou="),$dn.length - $dn.indexof("ou=")) $ou = $ou -replace $sourcedn,$targetdn if (!($(try{Get-ADObject $ou -Server $($TargetDomain) -Credential $($TargetCred)}catch{}))){ write-Slog "$identity" "WARN" "'$ou' not found." -Console $false $ou = "cn=users," + $targetdn } } if ($activity -eq "galsync") { $ou = $Script:ModuleTargetGALSyncOU } $ou = $ou.tolower() return $ou } catch { $ou = "cn=users," + $targetdn $ou = $ou.tolower() write-Slog "$identity" "WARN" "Issue calculating target OU. Using '$ou'" -Console $false return $ou } } } function Start-EMCleanActiveDirectoryObject() { <# .SYNOPSIS Cleans an Active Directory object's Exchange attributes .DESCRIPTION You can use this cmdlet to clean the Exchange attributes of any Active Directory object. .PARAMETER Samaccountname The samaccountname of the Active Directory object. .PARAMETER SourceOrTarget Specify whether the action should be applied to the source or target domain. .PARAMETER SourceDomain Specify the source domain. .PARAMETER SourceCred Specify the source credentials for the source domain. .PARAMETER TargetDomain Specify the target domain. .PARAMETER TargetCred Specify the target credentials for the target domain. .PARAMETER Confirm Specify whether you want to be asked for confirmation. The default is TRUE. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Samaccountname, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Source','Target')][string]$SourceOrTarget, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Confirm = $true ) Process { #Setup if ($SourceOrTarget -eq "Source") { $ScopedDomain = $SourceDomain.toupper() $ScopedCred = $SourceCred } if ($SourceOrTarget -eq "Target") { $ScopedDomain = $TargetDomain.toupper() $ScopedCred = $TargetCred } write-Slog "$samaccountname" "LOG" "Cleaning AD object in domain '$ScopedDomain'" # collect info try { $Scopedpdc = $null; get-addomain -Server $Scopeddomain -credential $Scopedcred -ea stop | % { $Scopedpdc = $_.pdcemulator } } catch { write-Slog "$samaccountname" "ERR" "Issue getting domain information for domain '$Scopeddomain'. $($_.exception.message)" } $ADobj = $null; $ADobj = Get-ADObject -server $Scopedpdc -filter {samaccountname -eq $samaccountname} -properties * -credential $Scopedcred -ea stop if (($ADobj | measure).count -gt 1) { write-Slog "$samaccountname" "ERR" "Multiple user objects returned from domain '$Scopeddomain'. Unable to continue. $($_.exception.message)" } if (($ADobj | measure).count -eq 0) { write-Slog "$samaccountname" "ERR" "No user objects returned from domain '$Scopeddomain'. Unable to continue. $($_.exception.message)" } if ($confirm -eq $true) { write-host "This will remove all Exchange attributes! " -nonewline write-host "Are you sure? " -nonewline; write-host "[Y] Yes" -fore yellow -nonewline; write-host ' [N] No (default is "Y")' -nonewline [ValidateSet('Yes','No','Y','N',$null)][string]$read = Read-Host -Prompt " " if ($read -match "^No$|^N$") { write-Slog "$samaccountname" "WARN" "Cleaning AD object in domain '$ScopedDomain' aborted" break } } try { if ($ADobj.mail) {$ADobj.mail = $null} if ($ADobj.HomeMDB) {$ADobj.HomeMDB = $null} if ($ADobj.HomeMTA) {$ADobj.HomeMTA = $null} if ($ADobj.legacyExchangeDN) {$ADobj.legacyExchangeDN = $null} if ($ADobj.msExchMailboxAuditEnable) {$ADobj.msExchMailboxAuditEnable = $null} if ($ADobj.msExchAddressBookFlags) {$ADobj.msExchAddressBookFlags = $null} if ($ADobj.msExchArchiveQuota) {$ADobj.msExchArchiveQuota = $null} if ($ADobj.msExchArchiveWarnQuota) {$ADobj.msExchArchiveWarnQuota = $null} if ($ADobj.msExchBypassAudit) {$ADobj.msExchBypassAudit = $null} if ($ADobj.msExchDumpsterQuota) {$ADobj.msExchDumpsterQuota = $null} if ($ADobj.msExchDumpsterWarningQuota) {$ADobj.msExchDumpsterWarningQuota = $null} if ($ADobj.msExchHomeServerName) {$ADobj.msExchHomeServerName = $null} if ($ADobj.msExchMailboxAuditEnable) {$ADobj.msExchMailboxAuditEnable = $null} if ($ADobj.msExchMailboxAuditLogAgeLimit) {$ADobj.msExchMailboxAuditLogAgeLimit = $null} if ($ADobj.msExchMailboxGuid) {$ADobj.msExchMailboxGuid = $null} if ($ADobj.msExchMDBRulesQuota) {$ADobj.msExchMDBRulesQuota = $null} if ($ADobj.msExchModerationFlags) {$ADobj.msExchModerationFlags = $null} if ($ADobj.msExchPoliciesIncluded) {$ADobj.msExchPoliciesIncluded = $null} if ($ADobj.msExchProvisioningFlags) {$ADobj.msExchProvisioningFlags = $null} if ($ADobj.msExchRBACPolicyLink) {$ADobj.msExchRBACPolicyLink = $null} if ($ADobj.msExchRecipientDisplayType) {$ADobj.msExchRecipientDisplayType = $null} if ($ADobj.msExchRecipientTypeDetails) {$ADobj.msExchRecipientTypeDetails = $null} if ($ADobj.msExchADCGlobalNames) {$ADobj.msExchADCGlobalNames = $null} if ($ADobj.msExchALObjectVersion) {$ADobj.msExchALObjectVersion = $null} if ($ADobj.msExchRemoteRecipientType) {$ADobj.msExchRemoteRecipientType = $null} if ($ADobj.msExchSafeSendersHash) {$ADobj.msExchSafeSendersHash = $null} if ($ADobj.msExchUserHoldPolicies) {$ADobj.msExchUserHoldPolicies = $null} if ($ADobj.msExchWhenMailboxCreated) {$ADobj.msExchWhenMailboxCreated = $null} if ($ADobj.msExchTransportRecipientSettingsFlags) {$ADobj.msExchTransportRecipientSettingsFlags = $null} if ($ADobj.msExchRecipientSoftDeletedStatus) {$ADobj.msExchRecipientSoftDeletedStatus = $null} if ($ADobj.msExchUMDtmfMap) {$ADobj.msExchUMDtmfMap = $null} if ($ADobj.msExchUMEnabledFlags2) {$ADobj.msExchUMEnabledFlags2 = $null} if ($ADobj.msExchUserAccountControl) {$ADobj.msExchUserAccountControl = $null} if ($ADobj.msExchVersion) {$ADobj.msExchVersion = $null} if ($ADobj.proxyAddresses) {$ADobj.proxyAddresses = $null} if ($ADobj.showInAddressBook) {$ADobj.showInAddressBook = $null} if ($ADobj.mailNickname) {$ADobj.mailNickname = $null} Set-ADObject -Instance $ADobj -server $Scopedpdc -Credential $ScopedCred -ea stop write-Slog "$samaccountname" "OK" "Cleaned object" } catch { write-Slog "$samaccountname" "ERR" "Issue cleaning object. Unable to continue. $($_.exception.message)" } write-Slog "$samaccountname" "LOG" "Ready" } } function Get-EMRecipientChanges{ <# .SYNOPSIS Gets a list of recipients from the source Exchange Organization between specific dates and times. .DESCRIPTION Gets a list of recipients from the source Exchange Organization between specific dates and times. .PARAMETER SourceDomain Specify the source domain. .PARAMETER SourceEndPoint Specify the source end point. .PARAMETER SourceCred Specify the source credentials. .PARAMETER Start Specify the datetime to start from. .PARAMETER End Specify the datetime to end with. #> [cmdletbinding()] Param ( [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][datetime]$Start, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][datetime]$End ) Process { try { if (!($end)) {$end = get-date} if ($end -le $start) { throw "Start cannot be newer than or equal to the End datetime." } $sourcepdc = $Script:ModuleSourcePDC $sb = $null if ($start -and $end) {$sb = [scriptblock]::create("whenchanged -gt '$start' -and whenchanged -lt '$end'")} if (!($start) -and $end) {$sb = [scriptblock]::create("whenchanged -lt '$end'")} $recipients = $null; $recipients = Invoke-Command -ConnectionUri http://$SourceEndPoint/powershell -credential $SourceCred -ConfigurationName microsoft.exchange -scriptblock {get-recipient -resultsize unlimited -filter $using:sb -domaincontroller "$($using:sourcepdc)" -ea stop} -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop | select displayname,samaccountname,alias,recipienttype,recipienttypedetails,whenchanged $recipients = $recipients | sort whenchanged -Descending $recipients } catch { throw "Issue getting recipient changes from source. Unable to continue. $($_.exception.message)" } } } # Batch handling ################################################################################################################ function Start-EMProcessMailboxBatch() { <# .SYNOPSIS Processes mailboxes in batches .DESCRIPTION This cmdlet runs the Start-EMProcessMailbox cmdlet against many mailboxes in a batch. It will also provide a batch log and has options to send a notification email when is it done. .PARAMETER Samaccountnames This is an array of samaccountname attributes you want the cmdlet to batch process. .PARAMETER SourceCred Specify the source credentials of the source domain. .PARAMETER TargetCred Specify the target credentials of the target domain. .PARAMETER SourceDomain Specify the source domain. .PARAMETER TargetDomain Specify the target domain. .PARAMETER Activity Specify whether you want to MIGRATE or GALSYNC. .PARAMETER Mode Specify whether you want to PREPARE or LOGONLY. .PARAMETER MoveMailbox Specify what move request operation you would like to perform. It is possible to select SUSPEND which will copy up to 95% of the mail data but will not complete. .PARAMETER SourceEndPoint Specify the source end point for the source Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER TargetEndPoint Specify the target end point for the target Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER Link Specified whether to link the primary object, i.e. the mailbox, to the secondary object, i.e. the user object in the opposite Active Directory forest. .PARAMETER Separate Specifies whether to break the cross-forest relationship and apply limitations to the secondary object. .PARAMETER Threads Specify how many threads you would like to create for parallel execution of Start-EMProcessMailbox .PARAMETER wait For mailbox move request operations you can specify whether to wait for the move request to complete. On full mailbox moves this will result in the required post migration tasks being applied. If you don't wait then Start-EMProcessMailbox will need to be run manually after the move request has completed. .PARAMETER ReportSMTP The email address you would like a notification sent to when the batch completes. .PARAMETER SMTPServer The SMTP relay server to use when sending an email. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][array]$Samaccountnames = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity = $Script:ModuleActivity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode = $Script:ModuleMode, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Yes','No','Suspend','Rollback')]$MoveMailbox = $Script:ModuleMoveMailbox, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint = $Script:ModuleTargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet("",$true,$false)]$Link = $Script:ModuleLink, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][int]$Threads=$Script:ModuleThreads, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$wait = $Script:ModuleWait, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$ReportSMTP = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SMTPServer= $null ) Process { $samaccountnames = $samaccountnames | sort | get-unique $total = $null; $total = ($samaccountnames | measure).count $warnings = 0 $throws = 0 $sleep = 5 $timestamp = ("{0:yyyyMMddHHmmss}" -f (get-date)).tostring() $jobname = $null; $jobname = "EMProcessMailboxBatch$($timestamp)" $EMPath = (get-module exchangemigration).path $errorcount = 0 if ($reportsmtp) { if ($reportsmtp -notmatch "^[a-zA-Z0-9.!£#$%&'^_`{}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$") { write-Slog "$jobname" "ERR" "'$reportsmtp' is not a valid SMTP address. Unable to continue" $false } if ($reportsmtp -and !($smtpserver)) { write-Slog "$jobname" "ERR" "Must provide SMTPServer if using ReportSMTP. Unable to continue" $false } } #Starting write-host "" write-host "Job:`t`t$($jobname)" $start = $(get-date) write-host "Started:`t$($start)" $n = 0 foreach ($samaccountname in $samaccountnames) { $n++ write-progress -activity "$jobname" -status "Processing $n of $($total): '$samaccountname'" -percentcomplete (($n) / $total*100) start-job -name $jobname -ScriptBlock { #sleep -s $(get-random -Minimum 1 -Maximum 10); try{import-module "$using:EMPath"}catch{throw}; try{import-module activedirectory}catch{throw}; #sleep -s $(get-random -Minimum 1 -Maximum 10); write-Slog "$using:samaccountname" "LOG" "'$using:jobname' started" $false | out-null Start-EMProcessMailbox -Samaccountname $using:samaccountname ` -SourceCred $using:sourcecred ` -TargetCred $using:targetcred ` -SourceDomain $using:sourcedomain ` -TargetDomain $using:targetdomain ` -Activity $using:activity ` -Mode $using:mode ` -MoveMailbox $using:movemailbox ` -SourceEndPoint $using:sourceendpoint ` -TargetEndPoint $using:targetendpoint ` -Link $using:link ` -Wait $using:wait ` -Separate $using:separate | out-null; write-Slog "$using:samaccountname" "LOG" "'$using:jobname' ended" $false | out-null } | out-null write-Slog "$jobname" "LOG" "Samaccountname: $samaccountname; SourceDomain: $sourcedomain; TargetDomain: $targetdomain; Activity: $Activity; Mode: $Mode; MoveMailbox: $MoveMailbox; SourceEndPoint: $SourceEndPoint; TargetEndPoint: $TargetEndPoint; Link: $Link; Separate: $Separate;" $false| out-null sleep -s 1 while($(get-job -name $jobname | ? {$_.state -eq 'running'}).Count -ge $threads) { sleep -s $sleep } #tidy get-job -name $jobname | ? {$_.state -ne 'running'} | remove-job -Force -Confirm:$false } sleep -s 1; [System.GC]::Collect() #Completing write-host "Completing:`t$(get-date)" while($(get-job -name $jobname | ? {$_.state -eq 'running'})) { sleep -s $sleep } #Finishing foreach ($samaccountname in $samaccountnames) { try { try { $emlog = $null; $emlog = Read-EMLogs -identity $samaccountname } catch { write-Slog "$jobname" "ERR" "$($samaccountname): No log found" $false | out-null } if ($($emlog | ? {$_.comment -match $jobname} | measure).count -ne 2) { write-Slog "$jobname" "WARN" "$($samaccountname): Issue with job" $false | out-null } $readout = $false foreach ($line in $emlog) { if ($line.comment -match $jobname -and $line.comment -match "ended") {$readout = $false} if ($readout) { if ($line.type -eq "ERR") { write-Slog "$jobname" "ERR" "$($samaccountname): $($line.comment)" $false | out-null } } if ($line.comment -match $jobname -and $line.comment -match "started") {$readout = $true} } } catch { $errorcount += 1 } } Read-EMLogs "$jobname" -Type ERR | select ref,timestamp,comment $summary = $null; $summary = "Total $total ERR $errorcount" write-Slog "$jobname" "LOG" "$summary" $false | out-null get-job -name $jobname | remove-job -Force -Confirm:$false $end = $(get-date) $output = $null; $output = "Completed:`t$($end) `nDuration:`t$([math]::round((new-timespan -Start $start -End (get-date)).totalminutes)) minutes `nSummary:`t$summary" write-host $output write-host if ($reportsmtp) { try { Send-MailMessage -To $ReportSMTP -From $("EM@" + $targetdomain) -Subject $jobname -SmtpServer $smtpserver -body $output } catch { write-Slog "$jobname" "ERR" "Issue sending SMTP report to '$ReportSMTP' using SMTPServer: '$SMTPServer'" $false } } } } function Start-EMProcessDistributionGroupBatch() { <# .SYNOPSIS Processes distribution groups in batches .DESCRIPTION This cmdlet runs the Start-EMProcessDistributionGroup cmdlet against many groups in a batch. It will also provide a batch log and has options to send a notification email when is it done. .PARAMETER Samaccountnames This is an array of samaccountname attributes you want the cmdlet to batch process. .PARAMETER SourceCred Specify the source credentials of the source domain. .PARAMETER TargetCred Specify the target credentials of the target domain. .PARAMETER SourceDomain Specify the source domain. .PARAMETER TargetDomain Specify the target domain. .PARAMETER Activity Specify whether you want to MIGRATE or GALSYNC. .PARAMETER Mode Specify whether you want to PREPARE or LOGONLY. .PARAMETER SourceEndPoint Specify the source end point for the source Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER TargetEndPoint Specify the target end point for the target Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER Separate Specifies whether to break the cross-forest relationship and apply limitations to the secondary object. .PARAMETER Threads Specify how many threads you would like to create for parallel execution of Start-EMProcessMailbox .PARAMETER ReportSMTP The email address you would like a notification sent to when the batch completes. .PARAMETER SMTPServer The SMTP relay server to use when sending an email. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][array]$Samaccountnames = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity = $Script:ModuleActivity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode = $Script:ModuleMode, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint = $Script:ModuleTargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][int]$Threads=$Script:ModuleThreads, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$ReportSMTP = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SMTPServer= $null ) Process { $samaccountnames = $samaccountnames | sort | get-unique $total = $null; $total = ($samaccountnames | measure).count $warnings = 0 $throws = 0 $sleep = 5 $timestamp = ("{0:yyyyMMddHHmmss}" -f (get-date)).tostring() $jobname = $null; $jobname = "EMProcessDistributionGroupBatch$($timestamp)" $EMPath = (get-module exchangemigration).path $errorcount = 0 if ($reportsmtp) { if ($reportsmtp -notmatch "^[a-zA-Z0-9.!£#$%&'^_`{}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$") { write-Slog "$jobname" "ERR" "'$reportsmtp' is not a valid SMTP address. Unable to continue" $false } if ($reportsmtp -and !($smtpserver)) { write-Slog "$jobname" "ERR" "Must provide SMTPServer if using ReportSMTP. Unable to continue" $false } } #Starting write-host "" write-host "Job:`t`t$($jobname)" $start = $(get-date) write-host "Started:`t$($start)" $n = 0 foreach ($samaccountname in $samaccountnames) { $n++ write-progress -activity "$jobname" -status "Processing $n of $($total): '$samaccountname'" -percentcomplete (($n) / $total*100) start-job -name $jobname -ScriptBlock { #sleep -s $(get-random -Minimum 1 -Maximum 10); try{import-module "$using:EMPath"}catch{throw}; try{import-module activedirectory}catch{throw}; #sleep -s $(get-random -Minimum 1 -Maximum 10); write-Slog "$using:samaccountname" "LOG" "'$using:jobname' started" $false | out-null Start-EMProcessDistributionGroup -Samaccountname $using:samaccountname ` -SourceCred $using:sourcecred ` -TargetCred $using:targetcred ` -SourceDomain $using:sourcedomain ` -TargetDomain $using:targetdomain ` -Activity $using:activity ` -Mode $using:mode ` -SourceEndPoint $using:sourceendpoint ` -TargetEndPoint $using:targetendpoint ` -Separate $using:separate | out-null; write-Slog "$using:samaccountname" "LOG" "'$using:jobname' ended" $false | out-null } | out-null write-Slog "$jobname" "LOG" "Samaccountname: $samaccountname; SourceDomain: $sourcedomain; TargetDomain: $targetdomain; Activity: $Activity; Mode: $Mode; SourceEndPoint: $SourceEndPoint; TargetEndPoint: $TargetEndPoint; Separate: $Separate;" $false | out-null sleep -s 1 while($(get-job -name $jobname | ? {$_.state -eq 'running'}).Count -ge $threads) { sleep -s $sleep } #tidy get-job -name $jobname | ? {$_.state -ne 'running'} | remove-job -Force -Confirm:$false } sleep -s 1; [System.GC]::Collect() #Completing write-host "Completing:`t$(get-date)" while($(get-job -name $jobname | ? {$_.state -eq 'running'})) { sleep -s $sleep } #Finishing foreach ($samaccountname in $samaccountnames) { try { try { $emlog = $null; $emlog = Read-EMLogs -identity $samaccountname } catch { write-Slog "$jobname" "ERR" "$($samaccountname): No log found" $false | out-null } if ($($emlog | ? {$_.comment -match $jobname} | measure).count -ne 2) { write-Slog "$jobname" "WARN" "$($samaccountname): Issue with job" $false | out-null } $readout = $false foreach ($line in $emlog) { if ($line.comment -match $jobname -and $line.comment -match "ended") {$readout = $false} if ($readout) { if ($line.type -eq "ERR") { write-Slog "$jobname" "ERR" "$($samaccountname): $($line.comment)" $false | out-null } } if ($line.comment -match $jobname -and $line.comment -match "started") {$readout = $true} } } catch { $errorcount += 1 } } Read-EMLogs "$jobname" -Type ERR | select ref,timestamp,comment $summary = $null; $summary = "Total $total ERR $errorcount" write-Slog "$jobname" "LOG" "$summary" $false | out-null get-job -name $jobname | remove-job -Force -Confirm:$false $end = $(get-date) $output = $null; $output = "Completed:`t$($end) `nDuration:`t$([math]::round((new-timespan -Start $start -End (get-date)).totalminutes)) minutes `nSummary:`t$summary" write-host $output write-host if ($reportsmtp) { try { Send-MailMessage -To $ReportSMTP -From $("EM@" + $targetdomain) -Subject $jobname -SmtpServer $smtpserver -body $output } catch { write-Slog "$jobname" "ERR" "Issue sending SMTP report to '$ReportSMTP' using SMTPServer: '$SMTPServer'" $false } } } } function Start-EMProcessContactBatch() { <# .SYNOPSIS Processes contacts in batches. .DESCRIPTION This cmdlet runs the Start-EMProcesscontact cmdlet against many contacts in a batch. It will also provide a batch log and has options to send a notification email when is it done. .PARAMETER Aliases This is an array of mailnickname attributes you want the cmdlet to batch process. .PARAMETER SourceCred Specify the source credentials of the source domain. .PARAMETER TargetCred Specify the target credentials of the target domain. .PARAMETER SourceDomain Specify the source domain. .PARAMETER TargetDomain Specify the target domain. .PARAMETER Activity Specify whether you want to MIGRATE or GALSYNC. .PARAMETER Mode Specify whether you want to PREPARE or LOGONLY. .PARAMETER SourceEndPoint Specify the source end point for the source Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER TargetEndPoint Specify the target end point for the target Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER Separate Specifies whether to break the cross-forest relationship and apply limitations to the secondary object. .PARAMETER Threads Specify how many threads you would like to create for parallel execution of Start-EMProcessMailbox .PARAMETER ReportSMTP The email address you would like a notification sent to when the batch completes. .PARAMETER SMTPServer The SMTP relay server to use when sending an email. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][array]$Aliases = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity = $Script:ModuleActivity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode = $Script:ModuleMode, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint = $Script:ModuleTargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][int]$Threads=$Script:ModuleThreads, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$ReportSMTP = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SMTPServer= $null ) Process { $aliases = $aliases | sort | get-unique $total = $null; $total = ($aliases | measure).count $warnings = 0 $throws = 0 $sleep = 5 $timestamp = ("{0:yyyyMMddHHmmss}" -f (get-date)).tostring() $jobname = $null; $jobname = "EMProcessContactBatch$($timestamp)" $EMPath = (get-module exchangemigration).path $errorcount = 0 if ($reportsmtp) { if ($reportsmtp -notmatch "^[a-zA-Z0-9.!£#$%&'^_`{}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$") { write-Slog "$jobname" "ERR" "'$reportsmtp' is not a valid SMTP address. Unable to continue" $false } if ($reportsmtp -and !($smtpserver)) { write-Slog "$jobname" "ERR" "Must provide SMTPServer if using ReportSMTP. Unable to continue" $false } } #Starting write-host "" write-host "Job:`t`t$($jobname)" $start = $(get-date) write-host "Started:`t$($start)" $n = 0 foreach ($alias in $aliases) { $n++ write-progress -activity "$jobname" -status "Processing $n of $($total): '$alias'" -percentcomplete (($n) / $total*100) start-job -name $jobname -ScriptBlock { #sleep -s $(get-random -Minimum 1 -Maximum 10); try{import-module "$using:EMPath"}catch{throw}; try{import-module activedirectory}catch{throw}; #sleep -s $(get-random -Minimum 1 -Maximum 10); write-Slog "$using:alias" "LOG" "'$using:jobname' started" $false | out-null Start-EMProcessContact -Alias $using:alias ` -SourceCred $using:sourcecred ` -TargetCred $using:targetcred ` -SourceDomain $using:sourcedomain ` -TargetDomain $using:targetdomain ` -Activity $using:activity ` -Mode $using:mode ` -SourceEndPoint $using:sourceendpoint ` -TargetEndPoint $using:targetendpoint ` -Separate $using:separate | out-null; write-Slog "$using:alias" "LOG" "'$using:jobname' ended" $false | out-null } | out-null write-Slog "$jobname" "LOG" "Alias: $Alias; SourceDomain: $sourcedomain; TargetDomain: $targetdomain; Activity: $Activity; Mode: $Mode; SourceEndPoint: $SourceEndPoint; TargetEndPoint: $TargetEndPoint; Separate: $Separate;" $false | out-null sleep -s 1 while($(get-job -name $jobname | ? {$_.state -eq 'running'}).Count -ge $threads) { sleep -s $sleep } #tidy get-job -name $jobname | ? {$_.state -ne 'running'} | remove-job -Force -Confirm:$false } sleep -s 1; [System.GC]::Collect() #Completing write-host "Completing:`t$(get-date)" while($(get-job -name $jobname | ? {$_.state -eq 'running'})) { sleep -s $sleep } #Finishing foreach ($alias in $aliases) { try { try { $emlog = $null; $emlog = Read-EMLogs -identity $alias } catch { write-Slog "$jobname" "ERR" "$($alias): No log found" $false | out-null } if ($($emlog | ? {$_.comment -match $jobname} | measure).count -ne 2) { write-Slog "$jobname" "WARN" "$($alias): Issue with job" $false | out-null } $readout = $false foreach ($line in $emlog) { if ($line.comment -match $jobname -and $line.comment -match "ended") {$readout = $false} if ($readout) { if ($line.type -eq "ERR") { write-Slog "$jobname" "ERR" "$($alias): $($line.comment)" $false | out-null } } if ($line.comment -match $jobname -and $line.comment -match "started") {$readout = $true} } } catch { $errorcount += 1 } } Read-EMLogs "$jobname" -Type ERR | select ref,timestamp,comment $summary = $null; $summary = "Total $total ERR $errorcount" write-Slog "$jobname" "LOG" "$summary" $false | out-null get-job -name $jobname | remove-job -Force -Confirm:$false $end = $(get-date) $output = $null; $output = "Completed:`t$($end) `nDuration:`t$([math]::round((new-timespan -Start $start -End (get-date)).totalminutes)) minutes `nSummary:`t$summary" write-host $output write-host if ($reportsmtp) { try { Send-MailMessage -To $ReportSMTP -From $("EM@" + $targetdomain) -Subject $jobname -SmtpServer $smtpserver -body $output } catch { write-Slog "$jobname" "ERR" "Issue sending SMTP report to '$ReportSMTP' using SMTPServer: '$SMTPServer'" $false } } } } # Mailboxes ################################################################################################################ function Start-EMProcessMailbox() { <# .SYNOPSIS Processes a mailbox for migration .DESCRIPTION This cmdlet is used to prepare and migrate a mailbox from the source to the target Exchange Organization. .PARAMETER Samaccountname This is the samaccountname attribute of the mailbox you want the cmdlet to process. .PARAMETER SourceCred Specify the source credentials of the source domain. .PARAMETER TargetCred Specify the target credentials of the target domain. .PARAMETER SourceDomain Specify the source domain. .PARAMETER TargetDomain Specify the target domain. .PARAMETER Activity Specify whether you want to MIGRATE or GALSYNC. .PARAMETER Mode Specify whether you want to PREPARE or LOGONLY. .PARAMETER MoveMailbox Specify what move request operation you would like to perform. It is possible to select SUSPEND which will copy up to 95% of the mail data but will not complete. .PARAMETER SourceEndPoint Specify the source end point for the source Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER TargetEndPoint Specify the target end point for the target Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER Link Specified whether to link the primary object, i.e. the mailbox, to the secondary object, i.e. the user object in the opposite Active Directory forest. .PARAMETER Separate Specifies whether to break the cross-forest relationship and apply limitations to the secondary object. .PARAMETER wait For mailbox move request operations you can specify whether to wait for the move request to complete. On full mailbox moves this will result in the required post migration tasks being applied. If you don't wait then Start-EMProcessMailbox will need to be run manually after the move request has completed. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Samaccountname = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity = $Script:ModuleActivity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode = $Script:ModuleMode, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Yes','No','Suspend','Rollback')]$MoveMailbox = $Script:ModuleMoveMailbox, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint = $Script:ModuleTargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet("",$true,$false)]$Link = $Script:ModuleLink, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate = $Script:ModuleSeparate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Wait = $Script:ModuleWait ) Process { #formatting $sourcedomain = $sourcedomain.toupper() $targetdomain = $targetdomain.toupper() $sourceendpoint = $sourceendpoint.toupper() $targetendpoint = $targetendpoint.toupper() $activity = $activity.toupper() $Mode = $Mode.toupper() $MoveMailbox = $MoveMailbox.toupper() write-Slog "$samaccountname" "GO" "$activity mailbox" write-Slog "$samaccountname" "LOG" "SourceCred: $($Sourcecred.username); TargetCred: $($TargetCred.username)" write-Slog "$samaccountname" "LOG" "SourceDomain: $sourcedomain; TargetDomain: $targetdomain; Activity: $Activity; Mode: $Mode; MoveMailbox: $MoveMailbox; SourceEndPoint: $SourceEndPoint; TargetEndPoint: $TargetEndPoint; Link: $Link; Separate: $Separate;" # start checks if ($MoveMailbox -match "Yes|Suspend|Rollback" -and ($activity -eq 'GALSync')) { write-Slog "$samaccountname" "ERR" "Unable to move mailboxes for the activity GALSync. $($_.exception.message)" } if ($MoveMailbox -match "Yes|Suspend|Rollback" -and !($SourceEndPoint)) { write-Slog "$samaccountname" "ERR" "SourceEndPoint required when moving mailboxes. $($_.exception.message)" } if ($movemailbox -match "Yes|Suspend|Rollback" -and $Mode -ne 'Prepare') { write-Slog "$samaccountname" "WARN" "Unable to move mailbox when mode is $Mode. Forcing MoveMailbox to 'No'" $MoveMailbox = "No" } if ($link -eq $true -and ($mode -ne 'Prepare' -or $separate -eq $true -or $activity -eq "galsync")) { write-Slog "$samaccountname" "ERR" "Unable to link mailbox when Mode is not '$Mode', Separate is '$Separate', or Activity is '$activity'." } if (($separate -eq $true -or $activity -eq "galsync" -or $movemailbox -eq "rollback") -and $link -ne $false -and $mode -eq "prepare") { write-Slog "$samaccountname" "WARN" "Mode, Activity, or Movemailbox parameters require a primary usermailbox. Forcing Link to $false" $link = $false } #get source data try { try { $sourcepdc = $Script:ModuleSourcePDC $sourcedomainsid = $Script:ModuleSourceDomainSID $sourcenbdomain = $Script:ModuleSourceNBDomain $sourcedn = $script:ModuleSourceDN } catch { write-Slog "$samaccountname" "ERR" "Issue getting domain information for source domain '$sourcedomain'. $($_.exception.message)" } $smeu = $null; $smeu = get-adobject -server $sourcepdc -filter {mailnickname -like "*" -and samaccountname -eq $samaccountname} -properties * -credential $sourcecred -ea stop if (($smeu | measure).count -gt 1) { write-Slog "$samaccountname" "ERR" "Multiple user objects returned from source domain '$sourcedomain'. Unable to continue. $($_.exception.message)" } if (($smeu | measure).count -eq 1) { if (!(Read-EMBackUp $($smeu.objectguid.guid))) { Write-EMBackUp $smeu } } } catch { write-Slog "$samaccountname" "ERR" "Issue getting mail enabled user from source domain '$sourcedomain'. $($_.exception.message)" } if (!($smeu)) { write-Slog "$samaccountname" "ERR" "No mail enabled user object found in source domain '$sourcedomain'. $($_.exception.message)" } else { try { $SourceType = $null; $SourceType = invoke-emexchangecommand -endpoint $sourceendpoint -domaincontroller $sourcepdc -credential $sourcecred -command "get-user -identity ""$($smeu.objectguid.guid)"" -domaincontroller ""$($sourcepdc)"" -ea stop" | select -expandproperty recipienttypedetails } catch { write-Slog "$samaccountname" "ERR" "Issue getting source type from '$sourcedomain'. $($_.exception.message)" } } #get target data try { try { $targetpdc = $null; $targetdomainsid = $null; $targetNBdomain = $null get-addomain -Server $targetdomain -credential $targetcred -ea stop | % { $targetpdc = $_.pdcemulator $targetdomainsid = $_.domainsid.value $targetnbdomain = $_.netbiosname.tostring() } } catch { write-Slog "$samaccountname" "ERR" "Issue getting domain information for target domain '$targetdomain'. $($_.exception.message)" } $tmeu = $null; $tmeu = get-adobject -server $targetpdc -filter {samaccountname -eq $samaccountname} -properties * -credential $targetcred -ea stop if (($tmeu | measure).count -gt 1) { write-Slog "$samaccountname" "ERR" "Multiple user objects returned from target domain '$targetdomain'. Unable to continue. $($_.exception.message)" } if (($tmeu | measure).count -eq 1) { if (!(Read-EMBackUp $($tmeu.objectguid.guid))) { Write-EMBackUp $tmeu } } } catch { write-Slog "$samaccountname" "ERR" "Issues getting user from target domain '$targetdomain'. $($_.exception.message)" } if (!($tmeu) -and $activity -eq "migrate") { write-Slog "$samaccountname" "WARN" "Target not found in target domain '$targetdomain'" } if ($tmeu) { try { $TargetType = $null; $TargetType = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-user -identity ""$($tmeu.objectguid.guid)"" -domaincontroller ""$($targetpdc)"" -ea stop" | select -expandproperty recipienttypedetails } catch { write-Slog "$samaccountname" "ERR" "Issue getting target type from '$targetdomain'. $($_.exception.message)" } } try { $detected = $null; $detected = Get-EMConflict -identity $samaccountname -Source $smeu -sourcepdc $sourcepdc -targetpdc $targetpdc -sourcedomain $sourcedomain -targetdomain $targetdomain -sourcecred $sourcecred -targetcred $targetcred -targetendpoint $targetendpoint } catch { write-Slog "$samaccountname" "ERR" "Issue detecting conflict in target domain '$($targetdomain)'. $($_.exception.message)" } if ($detected) { $detected | select samaccountname,distinguishedname,mailnickname,proxyaddresses | % { write-slog "$samaccountname" "WARN" "Conflict $($_ | convertto-json -compress)" } write-Slog "$samaccountname" "ERR" "SMTP, X500, or Alias conflict detected in target domain '$($targetdomain)'. $($_.exception.message)" } $meu = [pscustomobject]@{ SamAccountName = $samaccountname Activity = $Activity Source = $smeu SourceType = $SourceType SourceDomain = $SourceDomain.toupper() SourceNBDomain = $sourcenbdomain SourcePDC = $SourcePDC.toupper() SourceDomainSID = $sourcedomainsid Target = $tmeu TargetType = $TargetType TargetDomain = $TargetDomain TargetNBDomain = $targetnbdomain TargetPDC = $TargetPDC.toupper() TargetDomainSID = $targetdomainsid TargetCred = $TargetCred SourceCred = $SourceCred Mode = $Mode MoveMailbox = $MoveMailbox SourceEndPoint = $SourceEndPoint TargetEndPoint = $TargetEndPoint Link = $Link Separate = $Separate Wait = $Wait } write-Slog "$samaccountname" "LOG" "SourceType: $($sourcetype); SourcePDC: $($meu.sourcepdc); TargetType: $($targettype); TargetPDC: $($targetpdc)" $meu | Start-EMMailboxPrep } } function Start-EMMailboxPrep() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountName, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourcePDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomainSID, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Yes','No','Suspend','Rollback')]$MoveMailbox, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet("",$true,$false)]$Link, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][boolean]$Separate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Wait ) Process { #determine action $next = $null $primary = $null if ($source -eq $null) { write-Slog "$samaccountname" "ERR" "Not found in source domain '$sourcedomain'. Unable to continue. $($_.exception.message)" $next = "Stop" } else { #determine supportability if (($SourceType -eq $TargetType) -and ($SourceType -notmatch "remote" -and $TargetType -notmatch "remote")) { write-Slog "$samaccountname" "ERR" "Source and target recipients are of the same unsupported types. Unable to continue. $($_.exception.message)" $next = "Stop" } if (($SourceType -eq $TargetType) -and ($SourceType -match "remote" -and $TargetType -match "remote")) { if ((($source.targetaddress) -and $source.targetaddress -notmatch "@mail`.on$targetdomain$") -and (($target.targetaddress) -and $target.targetaddress -notmatch "@mail`.on$sourcedomain$")) { write-Slog "$samaccountname" "ERR" "Remote recipient target addresses in unsupported configuration. Source: $($source.targetaddress). Target: $($target.targetaddress). Unable to continue. $($_.exception.message)" $next = "Stop" } if ((($source.targetaddress) -and $source.targetaddress -match "@mail`.on$targetdomain$") -and (($target.targetaddress) -and $target.targetaddress -match "@mail`.on$sourcedomain$")) { write-Slog "$samaccountname" "ERR" "Remote recipient target addresses in unsupported configuration. Source: $($source.targetaddress). Target: $($target.targetaddress). Unable to continue. $($_.exception.message)" $next = "Stop" } if ($link -eq $true) { write-Slog "$samaccountname" "WARN" "Not possible to link from remote to remote recipient types. Forcing Link to 'FALSE'" $Link = $false } if ($movemailbox -match "Yes|Suspend") { write-Slog "$samaccountname" "WARN" "Not possible to create move request for remote to remote recipient types. Forcing MoveMailbox to 'No'" $MoveMailbox = "No" } } if (($SourceType -ne $TargetType) -and ($SourceType -match "remote" -and $TargetType -match "remote")) { write-Slog "$samaccountname" "ERR" "Remote recipient types are in an unsupported configuration. Source: $SourceType. Target: $TargetType. Unable to continue. $($_.exception.message)" $next = "Stop" } if ($SourceType -eq "DiscoveryMailbox" -or $TargetType -eq "DiscoveryMailbox") { write-Slog "$samaccountname" "ERR" "Source and / or Target type unsupported. Source: $SourceType. Target: $TargetType. Unable to continue. $($_.exception.message)" $next = "Stop" } if ($SourceType -match "^usermailbox$|^linkedmailbox$|^sharedmailbox$|^roommailbox$|^equipmentmailbox$" -and $TargetType -match "^usermailbox$|^linkedmailbox$|^sharedmailbox$|^roommailbox$|^equipmentmailbox$") { write-Slog "$samaccountname" "ERR" "Source and / or Target type unsupported. Source: $SourceType. Target: $TargetType. Unable to continue. $($_.exception.message)" $next = "Stop" } #Migration activity handling if ($Activity -eq "migrate" -and !($target)) { write-Slog "$samaccountname" "ERR" "MIGRATE requires a target user object. Unable to continue. $($_.exception.message)" $next = "Stop" } if ($Activity -eq "migrate" -and $target) { if ($target.distinguishedname -match $Script:ModuleTargetGALSyncOU) { write-Slog "$samaccountname" "WARN" "MIGRATE target user object located in GALSync OU '$Script:ModuleTargetGALSyncOU'" } } #GALsync activity handling if ($activity -eq "galsync" -and !($target)) { write-Slog "$samaccountname" "LOG" "Primary: SOURCE" write-Slog "$samaccountname" "AR" "Target user object to be created in '$Script:ModuleTargetGALSyncOU'" $primary = "Source" $next = "CreateTargetGALUser" } if ($activity -eq "galsync" -and $target) { if ($target.distinguishedname -notmatch $Script:ModuleTargetGALSyncOU) { write-Slog "$samaccountname" "WARN" "GALSync target user object not located in GALSync OU '$Script:ModuleTargetGALSyncOU'" } } #other if (!($next) -AND ($SourceType -match "^usermailbox$|^linkedmailbox$|^sharedmailbox$|^roommailbox$|^equipmentmailbox$" -and $TargetType -match "^User$|^DisabledUser$")) { write-Slog "$samaccountname" "AR" "Source is primary. Target to be mail enabled" $primary = "Source" $next = "MailEnableTargetUser" } if (!($next) -AND ($SourceType -match "^usermailbox$|^linkedmailbox$|^sharedmailbox$|^roommailbox$|^equipmentmailbox$" -and $TargetType -match "mailbox$|^mailuser$")) { write-Slog "$samaccountname" "LOG" "Primary: SOURCE" $primary= "Source" $next = "PrepareSourceAndTarget" } if (!($next) -AND ($SourceType -match "^mailuser$|^linkeduser$|^remoteusermailbox$|^remoteroommailbox$|^remoteequipmentmailbox$|^remotesharedmailbox$" -and $TargetType -match "^usermailbox$|^linkedmailbox$|^sharedmailbox$|^roommailbox$|^equipmentmailbox$")) { write-Slog "$samaccountname" "LOG" "Primary: TARGET" $primary= "Target" $next = "PrepareSourceAndTarget" } if (!($next) -and (($source.targetaddress) -and $source.targetaddress -notmatch "@mail`.on$targetdomain$") -and $TargetType -match "^User$|^DisabledUser$") { write-Slog "$samaccountname" "LOG" "Primary: SOURCE" write-Slog "$samaccountname" "WARN" "External $($source.targetaddress)" $primary = "Source" $next = "MailEnableTargetUser" } if (!($next) -and (($source.targetaddress) -and $source.targetaddress -notmatch "@mail`.on$targetdomain$") -and ($TargetType)) { write-Slog "$samaccountname" "LOG" "Primary: SOURCE" write-Slog "$samaccountname" "WARN" "External $($source.targetaddress)" $primary = "Source" $next = "PrepareSourceAndTarget" } if (!($next) -and (($target.targetaddress) -and $target.targetaddress -notmatch "@mail`.on$sourcedomain$") -and $SourceType -match "^User$|^DisabledUser$") { write-Slog "$samaccountname" "LOG" "Primary: TARGET" write-Slog "$samaccountname" "WARN" "External $($target.targetaddress)" $primary = "Target" $next = "MailEnableTargetUser" } if (!($next) -and (($target.targetaddress) -and $target.targetaddress -notmatch "@mail`.on$sourcedomain$") -and ($SourceType)) { write-Slog "$samaccountname" "LOG" "Primary: TARGET" write-Slog "$samaccountname" "WARN" "External $($target.targetaddress)" $primary = "Target" $next = "PrepareSourceAndTarget" } if (!($next)) { write-Slog "$samaccountname" "ERR" "Unable to determine action. Unable to continue. $($_.exception.message)" } } if ($mode -eq "logonly"){ $next = "Stop" } if ($next -ne "stop") { #calculate source SMTP addresses $sourcePrimarySMTP = $null; $sourcePrimarySMTP = ($source.proxyaddresses | ? {$_ -cmatch "^SMTP:"}) -replace "SMTP:","" if (($sourcePrimarySMTP | measure).count -gt 1) { write-Slog "$samaccountname" "ERR" "source has multiple primary SMTP addresses. Unable to continue. $($_.exception.message)" $next = "Stop" } if (($sourcePrimarySMTP | measure).count -eq 0) { write-Slog "$samaccountname" "ERR" "Source has no primary SMTP address. Unable to continue. $($_.exception.message)" $next = "Stop" } $SourceRoutingSMTP = $null; $SourceRoutingSMTP = $("$($Source.mailnickname)@mail.on$($sourcedomain)").tolower() #prepare mode object $Modeobj = [pscustomobject]@{ Samaccountname = $samaccountname Source = $source SourceDomain = $SourceDomain SourceNBDomain = $SourceNBDomain SourcePDC = $SourcePDC Target = $target TargetDomain = $TargetDomain TargetNBDomain = $TargetNBDomain TargetPDC = $TargetPDC Mode = $Mode SourcePrimarySMTP = $SourcePrimarySMTP SourceRoutingSMTP = $SourceRoutingSMTP TargetCred = $TargetCred SourceCred = $SourceCred Primary = $Primary Activity = $Activity MoveMailbox = $MoveMailbox SourceEndPoint = $SourceEndPoint TargetEndPoint = $TargetEndPoint Sourcetype = $sourcetype Targettype = $targettype SourceDomainSID = $SourceDomainSID TargetDomainSID = $TargetDomainSID Link = $Link Separate = $Separate Wait = $Wait } #apply action if ($next -eq "MailEnableTargetUser") { if ($mode -eq "prepare") { $Modeobj | Start-EMMailEnableTargetUser } else { write-Slog "$samaccountname" "LOG" "Not mail enabling target user due to mode" $next = "Stop" } } if ($next -eq "CreateTargetGALUser") { if ($mode -eq "prepare") { $Modeobj | New-EMTargetGALUser } else { write-Slog "$samaccountname" "LOG" "Not creating target GAL user due to mode" $next = "Stop" } } if ($next -eq "PrepareSourceAndTarget") { $Modeobj | Start-EMPrepareUserObjects } } if ($next -eq "Stop") { write-Slog "$samaccountname" "LOG" "Ready" } } } function Start-EMMailEnableTargetUser() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountName, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceRoutingSMTP, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourcePrimarySMTP, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Yes','No','Suspend','Rollback')]$MoveMailbox, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet("",$true,$false)]$Link, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][boolean]$Separate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Wait ) Process { $completed = $null $secondsmax = $null; $secondsmax = 300 $secondsinc = $null; $secondsinc = 30 $start = $null; $start = get-date try { invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "enable-mailuser -identity ""$($target.objectguid.guid)"" -externalemailaddress ""$($SourceRoutingSMTP)"" -primarysmtpaddress ""$($SourcePrimarySMTP)"" -domaincontroller ""$($targetpdc)"" -ea stop" | out-null } catch { write-Slog "$samaccountname" "ERR" "Problem mail enabling in target domain '$targetdomain'. $($_.exception.message)" } write-Slog "$samaccountname" "LOG" "Waiting for mail enabled object to be ready in target domain '$targetdomain'. Waiting up to $secondsmax seconds" Do { $invresult = $null; try { $invresult = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-mailuser -identity ""$($target.objectguid.guid)"" -domaincontroller ""$($targetpdc)"" -ea stop" #| out-null $completed = $true } catch{sleep -s $secondsinc} if ($($invresult | measure).count -gt 1) {write-Slog "$samaccountname" "ERR" "Multiple objects found in target domain '$targetdomain'. Unable to continue. $($_.exception.message)"} if ((new-timespan -Start $start -End (get-date)).seconds -ge $secondsmax) { write-Slog "$samaccountname" "ERR" "Timeout mail enabling. Unable to continue. $($_.exception.message)" } write-Slog "$samaccountname" "LOG" "Waited $([math]::round((new-timespan -Start $start -End (get-date)).totalseconds)) seconds" } while (!($completed)) if ($completed) { write-Slog "$samaccountname" "OK" "mail enabled OK in target domain '$targetdomain'" Start-EMProcessMailbox -SourceDomain $sourcedomain -TargetDomain $targetdomain -Samaccountname $samaccountname -SourceCred $SourceCred -TargetCred $TargetCred -Mode $Mode -Activity $activity -MoveMailbox $MoveMailbox -SourceEndPoint $SourceEndPoint -TargetEndPoint $TargetEndPoint -Link $Link -Separate $Separate -Wait $Wait } } } function New-EMTargetGALUser() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountName, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Yes','No','Suspend','Rollback')]$MoveMailbox, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet("",$true,$false)]$Link, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][boolean]$Separate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Wait ) Process { try { $t = 300 $path = $Script:ModuleTargetGALSyncOU New-ADUser -UserPrincipalName ("$samaccountname" + "@" + "$($targetdomain.tolower())") -SamAccountName "$samaccountname" -Path "$path" -Name "$samaccountname" -Server "$targetpdc" -credential $targetcred -ea stop write-Slog "$samaccountname" "OK" "GAL user object created in target domain '$targetdomain'" write-Slog "$samaccountname" "LOG" "Waiting for user object to be ready in target domain '$targetdomain'. Waiting up to $t seconds" $n = 0 while ($n -lt $t) { $esid = $null; $esid = $(try{(get-adobject -server $targetpdc -filter {samaccountname -eq $samaccountname} -credential $targetcred).objectguid.guid}catch{}) if ($($esid | measure).count -gt 1) {write-Slog "$samaccountname" "WARN" "Multiple objects found in target domain '$targetdomain'";throw} if ($esid) { if ($(try {invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-user $esid -domaincontroller $targetpdc"}catch{})) { Start-EMProcessMailbox -SourceDomain $sourcedomain -TargetDomain $targetdomain -Samaccountname $samaccountname -SourceCred $SourceCred -TargetCred $TargetCred -Mode $Mode -Activity $activity -MoveMailbox $MoveMailbox -SourceEndPoint $SourceEndPoint -TargetEndPoint $TargetEndPoint -Link $Link -Separate $Separate -Wait $Wait break } else { sleep -s 1; $n++ } } else { sleep -s 1; $n++ } } if ($n -ge $t) { throw } } catch { write-Slog "$samaccountname" "ERR" "Problem creating GAL user object in target domain '$targetdomain'. $($_.exception.message)" } } } function Start-EMPrepareUserObjects() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountName, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Primary, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceRoutingSMTP, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Yes','No','Suspend','Rollback')]$MoveMailbox, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet("",$true,$false)]$Link, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][boolean]$Separate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Wait ) Process { #calcs #calculate target routing SMTP address try { $targetRoutingSMTP = $null; $targetRoutingSMTP = $("$($Source.mailnickname)@mail.on$($targetdomain)").tolower() } catch { write-Slog "$samaccountname" "ERR" "Problem preparing target routing SMTP address. $($_.exception.message)" } #calculate source routing X500 address try { $sourceRoutingX500 = $null; $sourceRoutingX500 = $("X500:" + $source.legacyexchangedn) } catch { write-Slog "$samaccountname" "ERR" "Problem preparing source routing X500 address. $($_.exception.message)" } #calculate target routing X500 address try { $targetRoutingX500 = $null; $targetRoutingX500 = $("X500:" + $target.legacyexchangedn) } catch { write-Slog "$samaccountname" "ERR" "Problem preparing target routing X500 address. $($_.exception.message)" } #direction if ($primary -eq "source") { $primaryobj = $source $secondaryobj = $target $primarynbdomain = $null; $primarynbdomain = $sourcenbdomain $secondarynbdomain = $null; $secondarynbdomain = $targetnbdomain $primarydomain = $null; $primarydomain = $sourcedomain $secondarydomain = $null; $secondarydomain = $targetdomain $primaryendpoint = $null; $primaryendpoint = $sourceendpoint $secondaryendpoint = $null; $secondaryendpoint = $targetendpoint $primarypdc = $null; $primarypdc = $sourcepdc $secondarypdc = $null; $secondarypdc = $targetpdc $primarycred = $null; $primarycred = $sourcecred $secondarycred = $null; $secondarycred = $targetcred $primaryroutingsmtp = $null; $primaryroutingsmtp = $sourceroutingsmtp $secondaryroutingsmtp = $null; $secondaryroutingsmtp = $targetroutingsmtp $primaryroutingx500 = $null; $primaryroutingx500 = $sourceRoutingX500 $secondaryroutingx500 = $null; $secondaryroutingx500 = $targetRoutingX500 $primarytype = $null; $primarytype = $sourcetype $secondarytype = $null; $secondarytype = $targettype $primarydomainsid = $null; $primarydomainsid = $sourcedomainsid $secondarydomainsid = $null; $secondarydomainsid = $targetdomainsid $PrimaryGALSyncOU = $null; $PrimaryGALSyncOU = $Script:ModuleSourceGALSyncOU $SecondaryGALSyncOU = $null; $SecondaryGALSyncOU = $Script:ModuleTargetGALSyncOU } if ($primary -eq "target") { $primaryobj = $target $secondaryobj = $source $primarynbdomain = $null; $primarynbdomain = $targetnbdomain $secondarynbdomain = $null; $secondarynbdomain = $sourcenbdomain $primarydomain = $null; $primarydomain = $targetdomain $secondarydomain = $null; $secondarydomain = $sourcedomain $primaryendpoint = $null; $primaryendpoint = $targetendpoint $secondaryendpoint = $null; $secondaryendpoint = $sourceendpoint $primarypdc = $null; $primarypdc = $targetpdc $secondarypdc = $null; $secondarypdc = $sourcepdc $primarycred = $null; $primarycred = $targetcred $secondarycred = $null; $secondarycred = $sourcecred $primaryroutingsmtp = $null; $primaryroutingsmtp = $targetroutingsmtp $secondaryroutingsmtp = $null; $secondaryroutingsmtp = $sourceroutingsmtp $primaryroutingx500 = $null; $primaryroutingx500 = $targetRoutingX500 $secondaryroutingx500 = $null; $secondaryroutingx500 = $sourceRoutingX500 $primarytype = $null; $primarytype = $targettype $secondarytype = $null; $secondarytype = $sourcetype $primarydomainsid = $null; $primarydomainsid = $targetdomainsid $secondarydomainsid = $null; $secondarydomainsid = $sourcedomainsid $PrimaryGALSyncOU = $null; $PrimaryGALSyncOU = $Script:ModuleTargetGALSyncOU $SecondaryGALSyncOU = $null; $SecondaryGALSyncOU = $Script:ModuleSourceGALSyncOU } $pupdate = $false $supdate = $false $primaryobjbefore = $primaryobj $secondaryobjbefore = $secondaryobj #displayname if ($($primaryobj.displayname) -ne $($secondaryobj.displayname)) { write-Slog "$samaccountname" "AR" "Secondary displayname attr update required: $($primaryobj.displayname)" $secondaryobj.displayname = $primaryobj.displayname $supdate = $true } #givenName if ($($primaryobj.givenName) -ne $($secondaryobj.givenName)) { write-Slog "$samaccountname" "AR" "Secondary givenName attr update required: $($primaryobj.givenName)" $secondaryobj.givenName = $primaryobj.givenName $supdate = $true } #Sn if ($($primaryobj.Sn) -ne $($secondaryobj.Sn)) { write-Slog "$samaccountname" "AR" "Secondary Sn attr update required: $($primaryobj.Sn)" $secondaryobj.Sn = $primaryobj.Sn $supdate = $true } if ($($primaryobj.mail) -ne $($secondaryobj.mail)) { write-Slog "$samaccountname" "AR" "Secondary mail attr update required: $($primaryobj.mail)" $secondaryobj.mail = $primaryobj.mail $supdate = $true } #mailnickname if ($($primaryobj.mailnickname) -ne $($secondaryobj.mailnickname)) { write-Slog "$samaccountname" "AR" "Secondary mailnickname attr update required: $($primaryobj.mailnickname)" $secondaryobj.mailnickname = $primaryobj.mailnickname $supdate = $true } #msexchmailboxguid try {$pguid = $null; $pguid = (new-object guid @(,$primaryobj.msExchMailboxGUID)).guid}catch{} try {$sguid = $null; $sguid = (new-object guid @(,$secondaryobj.msExchMailboxGUID)).guid}catch{} if (!($pguid)) { write-Slog "$samaccountname" "ERR" "Primary msExchMailboxGuid attribute cannot be null. $($_.exception.message)" } if ($pguid -ne $sguid) { write-Slog "$samaccountname" "AR" "Secondary msExchMailboxGuid attr update required" $secondaryobj.msExchMailboxGuid = $primaryobj.msExchMailboxGuid $supdate = $true $sguidupdate = $true } #msexcharchiveguid try {$paguid = $null; $paguid = (new-object guid @(,$primaryobj.msExchArchiveGUID)).guid}catch{} try {$saguid = $null; $saguid = (new-object guid @(,$secondaryobj.msExchArchiveGUID)).guid}catch{} if ($paguid -ne $saguid) { write-Slog "$samaccountname" "AR" "Secondary msExchArchiveGuid attr update required" $secondaryobj.msExchArchiveGuid = $primaryobj.msExchArchiveGuid $supdate = $true } #msExchUserCulture if ($($primaryobj.msExchUserCulture) -ne $($secondaryobj.msExchUserCulture)) { write-Slog "$samaccountname" "AR" "Secondary msExchUserCulture attr update required: $($primaryobj.msExchUserCulture)" $secondaryobj.msExchUserCulture = $primaryobj.msExchUserCulture $supdate = $true } #countryCode if ($($primaryobj.countryCode) -ne $($secondaryobj.countryCode)) { write-Slog "$samaccountname" "AR" "Secondary countryCode attr update required: $($primaryobj.countryCode)" $secondaryobj.countryCode = $primaryobj.countryCode $supdate = $true } #msExchELCMailboxFlags if ($($primaryobj.msExchELCMailboxFlags) -ne $($secondaryobj.msExchELCMailboxFlags)) { write-Slog "$samaccountname" "AR" "Secondary msExchELCMailboxFlags attr update required: $($primaryobj.msExchELCMailboxFlags)" $secondaryobj.msExchELCMailboxFlags = $primaryobj.msExchELCMailboxFlags $supdate = $true } #textEncodedORAddress if ($($primaryobj.textEncodedORAddress) -ne $($secondaryobj.textEncodedORAddress)) { write-Slog "$samaccountname" "AR" "Secondary textEncodedORAddress attr update required" $secondaryobj.textEncodedORAddress = $primaryobj.textEncodedORAddress $supdate = $true } #extensionAttribute1 if ($($primaryobj.extensionAttribute1) -ne $($secondaryobj.extensionAttribute1)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute1 attr update required" $secondaryobj.extensionAttribute1 = $primaryobj.extensionAttribute1 $supdate = $true } #extensionAttribute2 if ($($primaryobj.extensionAttribute2) -ne $($secondaryobj.extensionAttribute2)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute2 attr update required" $secondaryobj.extensionAttribute2 = $primaryobj.extensionAttribute2 $supdate = $true } #extensionAttribute3 if ($($primaryobj.extensionAttribute3) -ne $($secondaryobj.extensionAttribute3)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute3 attr update required" $secondaryobj.extensionAttribute3 = $primaryobj.extensionAttribute3 $supdate = $true } #extensionAttribute4 if ($($primaryobj.extensionAttribute4) -ne $($secondaryobj.extensionAttribute4)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute4 attr update required" $secondaryobj.extensionAttribute4 = $primaryobj.extensionAttribute4 $supdate = $true } #extensionAttribute5 if ($($primaryobj.extensionAttribute5) -ne $($secondaryobj.extensionAttribute5)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute5 attr update required" $secondaryobj.extensionAttribute5 = $primaryobj.extensionAttribute5 $supdate = $true } #extensionAttribute6 if ($($primaryobj.extensionAttribute6) -ne $($secondaryobj.extensionAttribute6)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute6 attr update required" $secondaryobj.extensionAttribute6 = $primaryobj.extensionAttribute6 $supdate = $true } #extensionAttribute7 if ($($primaryobj.extensionAttribute7) -ne $($secondaryobj.extensionAttribute7)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute7 attr update required" $secondaryobj.extensionAttribute7 = $primaryobj.extensionAttribute7 $supdate = $true } #extensionAttribute8 if ($($primaryobj.extensionAttribute8) -ne $($secondaryobj.extensionAttribute8)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute8 attr update required" $secondaryobj.extensionAttribute8 = $primaryobj.extensionAttribute8 $supdate = $true } #extensionAttribute9 if ($($primaryobj.extensionAttribute9) -ne $($secondaryobj.extensionAttribute9)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute9 attr update required" $secondaryobj.extensionAttribute9 = $primaryobj.extensionAttribute9 $supdate = $true } #extensionAttribute10 if ($($primaryobj.extensionAttribute10) -ne $($secondaryobj.extensionAttribute10)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute10 attr update required" $secondaryobj.extensionAttribute10 = $primaryobj.extensionAttribute10 $supdate = $true } #extensionAttribute11 if ($($primaryobj.extensionAttribute11) -ne $($secondaryobj.extensionAttribute11)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute11 attr update required" $secondaryobj.extensionAttribute11 = $primaryobj.extensionAttribute11 $supdate = $true } #extensionAttribute12 if ($($primaryobj.extensionAttribute12) -ne $($secondaryobj.extensionAttribute12)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute12 attr update required" $secondaryobj.extensionAttribute12 = $primaryobj.extensionAttribute12 $supdate = $true } #extensionAttribute13 if ($($primaryobj.extensionAttribute13) -ne $($secondaryobj.extensionAttribute13)) { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute13 attr update required" $secondaryobj.extensionAttribute13 = $primaryobj.extensionAttribute13 $supdate = $true } #extensionAttribute14 if ($($primaryobj.extensionAttribute14) -ne $($secondaryobj.extensionAttribute14)) { if ($secondaryobj.extensionAttribute14 -cmatch "^ID.*/CF:$" -and $secondaryobj.extensionAttribute15 -cmatch "^\w{32}$") { write-Slog "$samaccountname" "WARN" "Secondary extensionAttribute14 used by QMM and will be ignored" } else { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute14 attr update required" $secondaryobj.extensionAttribute14 = $primaryobj.extensionAttribute14 $supdate = $true } } #extensionAttribute15 if ($($primaryobj.extensionAttribute15) -ne $($secondaryobj.extensionAttribute15)) { if ($secondaryobj.extensionAttribute14 -cmatch "^ID.*/CF:$" -and $secondaryobj.extensionAttribute15 -cmatch "^\w{32}$") { write-Slog "$samaccountname" "WARN" "Secondary extensionAttribute15 used by QMM and will be ignored" } else { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute15 attr update required" $secondaryobj.extensionAttribute15 = $primaryobj.extensionAttribute15 $supdate = $true } } #authOrig (users allowed to send to the recipient) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute authOrig -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.authOrig -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.authOrig) $($sdns)}catch{})) -or ($($secondaryobj.authOrig) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary authOrig attr update required" $secondaryobj.authOrig = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary authOrig attr. $($_.exception.message)" } } #unauthOrig (users not allowed to send to the recipient) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute unauthOrig -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.unauthOrig -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.unauthOrig) $($sdns)}catch{})) -or ($($secondaryobj.unauthOrig) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary unauthOrig attr update required" $secondaryobj.unauthOrig = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary unauthOrig attr. $($_.exception.message)" } } #dLMemSubmitPerms (groups allowed to send to the recipient) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute dLMemSubmitPerms -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.dLMemSubmitPerms -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.dLMemSubmitPerms) $($sdns)}catch{})) -or ($($secondaryobj.dLMemSubmitPerms) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary dLMemSubmitPerms attr update required" $secondaryobj.dLMemSubmitPerms = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary dLMemSubmitPerms attr. $($_.exception.message)" } } #dLMemRejectPerms (groups not allowed to send to the recipient) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute dLMemRejectPerms -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.dLMemRejectPerms -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.dLMemRejectPerms) $($sdns)}catch{})) -or ($($secondaryobj.dLMemRejectPerms) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary dLMemRejectPerms attr update required" $secondaryobj.dLMemRejectPerms = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary dLMemRejectPerms attr. $($_.exception.message)" } } #msExchHideFromAddressLists / msExchSenderHintTranslations (mailtips) if ($separate -eq $false -and $activity -eq "migrate") { if ($($primaryobj.msExchHideFromAddressLists) -ne $($secondaryobj.msExchHideFromAddressLists)) { write-Slog "$samaccountname" "AR" "Secondary msExchHideFromAddressLists attr update required" $secondaryobj.msExchHideFromAddressLists = $primaryobj.msExchHideFromAddressLists $supdate = $true } if ($($primaryobj.msExchSenderHintTranslations) -ne $($secondaryobj.msExchSenderHintTranslations)) { write-Slog "$samaccountname" "AR" "Secondary msExchSenderHintTranslations attr update required" $secondaryobj.msExchSenderHintTranslations = $primaryobj.msExchSenderHintTranslations $supdate = $true } } if ($separate -eq $true -or $activity -eq "GALSync") { if ($($secondaryobj.msExchHideFromAddressLists) -ne $true) { write-Slog "$samaccountname" "AR" "Secondary msExchHideFromAddressLists attr update required" $secondaryobj.msExchHideFromAddressLists = $true $supdate = $true } $tip = $null; $tip = "default:<html>`n<body>`nPlease be aware this is an external recipient.`n</body>`n</html>`n" if ($($secondaryobj.msExchSenderHintTranslations) -ne $tip -or (!($secondaryobj.msExchSenderHintTranslations))) { write-Slog "$samaccountname" "AR" "Secondary msExchSenderHintTranslations attr update required" $secondaryobj.msExchSenderHintTranslations = $tip $supdate = $true } } #disable sender auth requirement if ($($primaryobj.msExchRequireAuthToSendTo) -eq $true) { write-Slog "$samaccountname" "AR" "Primary msExchRequireAuthToSendTo attr update required" $primaryobj.msExchRequireAuthToSendTo = $false $pupdate = $true } if ($($secondaryobj.msExchRequireAuthToSendTo) -eq $true) { write-Slog "$samaccountname" "AR" "Secondary msExchRequireAuthToSendTo attr update required" $secondaryobj.msExchRequireAuthToSendTo = $false $supdate = $true } #targetaddress if ($($primaryobj.targetaddress) -match "@mail`.on$SecondaryDomain$") { try { write-Slog "$samaccountname" "AR" "Primary targetaddress attr update required" $primaryobj.targetaddress = $null $pupdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing primary targetaddress. $($_.exception.message)" } } if ($separate -eq $false) { if ($($secondaryobj.targetaddress) -cne $("SMTP:" + $primaryRoutingSMTP)) { try { write-Slog "$samaccountname" "AR" "Secondary targetaddress attr update required" $secondaryobj.targetaddress = $("SMTP:" + $primaryRoutingSMTP) $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary targetaddress. $($_.exception.message)" } } } if ($separate -eq $true) { $emailaddr = $null; $emailaddr = $secondaryobj | select @{label="email";expression={$secondaryobj.proxyaddresses | ? {$_ -cmatch "^SMTP:"}}} | % {$_.email -replace "SMTP:",""} if (($emailaddr | measure).count -ne 1) { write-Slog "$samaccountname" "ERR" "Problem calculating secondary targetaddress for separation. $($_.exception.message)" } $emailaddr = "SMTP:" + $emailaddr if ($($secondaryobj.targetaddress) -cne $emailaddr) { try { write-Slog "$samaccountname" "AR" "Secondary targetaddress attr update required" $secondaryobj.targetaddress = $emailaddr $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary targetaddress. $($_.exception.message)" } } } #quotas if ($($primaryobj.mDBUseDefaults) -ne $($secondaryobj.mDBUseDefaults)) { write-Slog "$samaccountname" "AR" "Secondary mDBUseDefaults attr update required" $secondaryobj.mDBUseDefaults = $primaryobj.mDBUseDefaults $supdate = $true } if ($($primaryobj.mDBUseDefaults) -eq $true) { if ($($secondaryobj.mDBOverHardQuotaLimit) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary mDBOverHardQuotaLimit attr update required" $secondaryobj.mDBOverHardQuotaLimit = $null $supdate = $true } if ($($secondaryobj.mDBOverQuotaLimit) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary mDBOverQuotaLimit attr update required" $secondaryobj.mDBOverQuotaLimit = $null $supdate = $true } if ($($secondaryobj.mDBStorageQuota) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary mDBStorageQuota attr update required" $secondaryobj.mDBStorageQuota = $null $supdate = $true } } if ($($primaryobj.mDBUseDefaults) -ne $true) { if ($($primaryobj.mDBOverHardQuotaLimit) -eq $null) { if ($($secondaryobj.mDBOverHardQuotaLimit) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary mDBOverHardQuotaLimit attr update required" $secondaryobj.mDBOverHardQuotaLimit = $null $supdate = $true } } else { $secondaryquota = $null; if ($primary -eq "source") { try { $secondaryquota = [math]::Round($($primaryobj.mDBOverHardQuotaLimit * 1.35)) } catch { write-Slog "$samaccountname" "WARN" "Problem calculating secondary mDBOverHardQuotaLimit. $($_.exception.message)" write-Slog "$samaccountname" "WARN" "Using mDBOverHardQuotaLimit from primary" $secondaryquota = $primaryobj.mDBOverHardQuotaLimit } } else { $secondaryquota = $primaryobj.mDBOverHardQuotaLimit } if ($($secondaryobj.mDBOverHardQuotaLimit) -ne $secondaryquota) { write-Slog "$samaccountname" "AR" "Secondary mDBOverHardQuotaLimit attr update required" $secondaryobj.mDBOverHardQuotaLimit = $secondaryquota $supdate = $true } } if ($($primaryobj.mDBOverQuotaLimit) -eq $null) { if ($($secondaryobj.mDBOverQuotaLimit) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary mDBOverQuotaLimit attr update required" $secondaryobj.mDBOverQuotaLimit = $null $supdate = $true } } else { $secondaryquota = $null; if ($primary -eq "source") { try { $secondaryquota = [math]::Round($($primaryobj.mDBOverQuotaLimit * 1.35)) } catch { write-Slog "$samaccountname" "WARN" "Problem calculating secondary mDBOverQuotaLimit. $($_.exception.message)" write-Slog "$samaccountname" "WARN" "Using mDBOverQuotaLimit from primary" $secondaryquota = $primaryobj.mDBOverQuotaLimit } } else { $secondaryquota = $primaryobj.mDBOverQuotaLimit } if ($($secondaryobj.mDBOverQuotaLimit) -ne $secondaryquota) { write-Slog "$samaccountname" "AR" "Secondary mDBOverQuotaLimit attr update required" $secondaryobj.mDBOverQuotaLimit = $secondaryquota $supdate = $true } } if ($($primaryobj.mDBStorageQuota) -eq $null) { if ($($secondaryobj.mDBStorageQuota) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary mDBStorageQuota attr update required" $secondaryobj.mDBStorageQuota = $null $supdate = $true } } else { $secondaryquota = $null; if ($primary -eq "source") { try { $secondaryquota = [math]::Round($($primaryobj.mDBStorageQuota * 1.35)) } catch { write-Slog "$samaccountname" "WARN" "Problem calculating secondary mDBStorageQuota. $($_.exception.message)" write-Slog "$samaccountname" "WARN" "Using mDBStorageQuota from primary" $secondaryquota = $primaryobj.mDBStorageQuota } } else { $secondaryquota = $primaryobj.mDBStorageQuota } if ($($secondaryobj.mDBStorageQuota) -ne $secondaryquota) { write-Slog "$samaccountname" "AR" "Secondary mDBStorageQuota attr update required" $secondaryobj.mDBStorageQuota = $secondaryquota $supdate = $true } } } #maximum size restrictions if ($($primaryobj.delivContLength) -ne $($secondaryobj.delivContLength)) { try { write-Slog "$samaccountname" "AR" "Secondary delivContLength attr update required" $secondaryobj.delivContLength = $primaryobj.delivContLength $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary delivContLength attr. $($_.exception.message)" } } if ($($primaryobj.submissionContLength) -ne $($secondaryobj.submissionContLength)) { try { write-Slog "$samaccountname" "AR" "Secondary submissionContLength attr update required" $secondaryobj.submissionContLength = $primaryobj.submissionContLength $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary submissionContLength attr. $($_.exception.message)" } } #forwarding $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute altRecipient -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.altRecipient -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($sdns | measure).count -gt 1) {write-Slog "$samaccountname" "ERR" "Secondary altRecipient too many objects returned. Unable to continue. $($_.exception.message)"} if (($(try{compare-object $($secondaryobj.altRecipient) $($sdns)}catch{})) -or ($($secondaryobj.altRecipient) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary altRecipient attr update required" $secondaryobj.altRecipient = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary altRecipient attr. $($_.exception.message)" } } if ($secondaryobj.altRecipient){ if (($secondaryobj.deliverAndRedirect) -ne $($primaryobj.deliverAndRedirect)) { try { write-Slog "$samaccountname" "AR" "Secondary deliverAndRedirect attr update required" $secondaryobj.deliverAndRedirect = $($primaryobj.deliverAndRedirect) $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary deliverAndRedirect. $($_.exception.message)" } } } else { if ($($secondaryobj.deliverAndRedirect) -ne $null) { try { write-Slog "$samaccountname" "AR" "Secondary deliverAndRedirect attr update required" $secondaryobj.deliverAndRedirect = $null $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary deliverAndRedirect. $($_.exception.message)" } } } #recipient limits if ($($primaryobj.msExchRecipLimit) -ne $($secondaryobj.msExchRecipLimit)) { try { write-Slog "$samaccountname" "AR" "Secondary msExchRecipLimit attr update required" $secondaryobj.msExchRecipLimit = $primaryobj.msExchRecipLimit $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchRecipLimit attr. $($_.exception.message)" } } #enabled if ($Activity -eq "migrate" -and $Separate -eq $false) { if ($secondaryobj.enabled -ne $primaryobj.enabled) { write-Slog "$samaccountname" "WARN" "Secondary enabled attr does not match primary" } } if ($Activity -eq "galsync" -or $Separate -eq $true) { if ($secondaryobj.enabled -eq $true) { write-Slog "$samaccountname" "AR" "Secondary user object to be disabled due to Activity '$Activity' or Separate '$Separate'" $secondaryobj.enabled = $false $supdate = $true } } if ($primarytype -match "shared") { if ($primaryobj.enabled -eq $true) { write-Slog "$samaccountname" "AR" "Primarytype is '$primarytype'. Primary user object to be disabled" $primaryobj.enabled = $false $pupdate = $true } if ($secondaryobj.enabled -eq $true) { write-Slog "$samaccountname" "AR" "Primarytype is '$primarytype'. Secondary user object to be disabled" $secondaryobj.enabled = $false $supdate = $true } } #PROXYADDRESSES $pproxsticky = $null; $pproxsticky = $primaryobj.proxyaddresses -notmatch "^smtp:|^x500:" $sproxsticky = $null; $sproxsticky = $secondaryobj.proxyaddresses -notmatch "^smtp:|^x500" $x500sticky = $null; $x500sticky = $primaryobj.proxyaddresses -match "^x500:"; $x500sticky += $secondaryobj.proxyaddresses -match "^x500:" $x500sticky = $x500sticky | sort -Unique $pprox = $primaryobj.proxyaddresses -match "^smtp:" $sprox = $pprox #primary #smtp if ($pprox -notcontains $("smtp:" + $primaryRoutingSMTP)) { $pprox += $("smtp:" + $primaryRoutingSMTP) } #nonsmtp $pproxsticky | % {$pprox += $_} $x500sticky | % {$pprox += $_} if ($pprox -notcontains $secondaryRoutingX500) { $pprox += $secondaryRoutingX500 } #remove unwanted $pprox = $pprox -notmatch "mail\.on$($secondarydomain)$" $pprox = $pprox -notmatch [regex]::escape($primaryRoutingX500) #formatting $pprox = $pprox | sort -Unique $pproxarray = $null; $pproxarray = @(); $pprox | % {$pproxarray += ($_.tostring())} if ($(try{compare-object $($primaryobj.proxyaddresses) $($pproxarray)}catch{}) -or ($($primaryobj.proxyaddresses) -xor $($pproxarray))) { try { write-Slog "$samaccountname" "AR" "Primary proxyaddresses attr update required" $primaryobj.proxyaddresses = $pproxarray $pupdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing primary proxyaddresses attr. $($_.exception.message)" } } #secondary #smtp if ($sprox -notcontains $("smtp:" + $secondaryRoutingSMTP)) { $sprox += $("smtp:" + $secondaryRoutingSMTP) } #nonsmtp $sproxsticky | % {$sprox += $_} $x500sticky | % {$sprox += $_} if ($sprox -notcontains $primaryRoutingX500) { $sprox += $primaryRoutingX500 } #remove unwanted $sprox = $sprox -notmatch "mail\.on$($primarydomain)$" $sprox = $sprox -notmatch [regex]::escape($secondaryRoutingX500) #formatting $sprox = $sprox | sort -Unique $sproxarray = $null; $sproxarray = @(); $sprox | % {$sproxarray += ($_.tostring())} if ($(try{compare-object $($secondaryobj.proxyaddresses) $($sproxarray)}catch{}) -or $($($secondaryobj.proxyaddresses) -xor $($sproxarray))) { try { write-Slog "$samaccountname" "AR" "Secondary proxyaddresses attr update required" $secondaryobj.proxyaddresses = $sproxarray $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary proxyaddresses attr. $($_.exception.message)" } } #remote mailbox settings if ($primarytype -match "^usermailbox$|^linkedmailbox$" -and $secondarytype -ne "remoteusermailbox") { try { write-Slog "$samaccountname" "AR" "Converting secondary to remote user mailbox" $secondaryobj.msExchRecipientDisplayType = "-2147483642" $secondaryobj.msExchRecipientTypeDetails = "2147483648" $secondaryobj.msExchRemoteRecipientType = "1" $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem converting secondary to remote user mailbox. Unable to continue. $($_.exception.message)" } } if ($primarytype -eq "sharedmailbox" -and $secondarytype -ne "remotesharedmailbox") { try { write-Slog "$samaccountname" "AR" "Converting secondary to remote shared mailbox" $secondaryobj.msExchRecipientDisplayType = "-2147483642" $secondaryobj.msExchRecipientTypeDetails = "34359738368" $secondaryobj.msExchRemoteRecipientType = "1" $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem converting secondary to remote shared mailbox. Unable to continue. $($_.exception.message)" } } if ($primarytype -match "shared" -and $primaryobj.enabled -eq $true) { try { write-Slog "$samaccountname" "AR" "Disabling user object for primary '$primarytype'" $primaryobj.enabled = $false $pupdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem disabling user object for primary '$primarytype'. Unable to continue. $($_.exception.message)" } } if ($primarytype -match "shared" -and $secondaryobj.enabled -eq $true) { try { write-Slog "$samaccountname" "AR" "Disabling user object for secondary '$secondarytype'" $secondaryobj.enabled = $false $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem disabling user object for secondary '$secondarytype'. Unable to continue. $($_.exception.message)" } } if ($primarytype -eq "roommailbox" -and $secondarytype -ne "remoteroommailbox") { try { write-Slog "$samaccountname" "AR" "Converting secondary to remote room mailbox" $secondaryobj.msExchRecipientDisplayType = "-2147481850" $secondaryobj.msExchRecipientTypeDetails = "8589934592" $secondaryobj.msExchRemoteRecipientType = "1" $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem converting secondary to remote room mailbox. Unable to continue. $($_.exception.message)" } } if ($primarytype -eq "equipmentmailbox" -and $secondarytype -ne "remoteequipmentmailbox") { try { write-Slog "$samaccountname" "AR" "Converting secondary to remote equipment mailbox" $secondaryobj.msExchRecipientDisplayType = "-2147481594" $secondaryobj.msExchRecipientTypeDetails = "17179869184" $secondaryobj.msExchRemoteRecipientType = "1" $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem converting secondary to remote equipment mailbox. Unable to continue. $($_.exception.message)" } } if ($primarytype -eq "roommailbox" -or $primarytype -eq "equipmentmailbox") { if ($($primaryobj.msExchResourceCapacity) -ne $($secondaryobj.msExchResourceCapacity)) { try { write-Slog "$samaccountname" "AR" "Secondary msExchResourceCapacity attr update required" $secondaryobj.msExchResourceCapacity = $primaryobj.msExchResourceCapacity $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchResourceCapacity attr. $($_.exception.message)" } } if ($($primaryobj.msExchResourceDisplay) -ne $($secondaryobj.msExchResourceDisplay)) { try { write-Slog "$samaccountname" "AR" "Secondary msExchResourceDisplay attr update required" $secondaryobj.msExchResourceDisplay = $primaryobj.msExchResourceDisplay $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchResourceDisplay attr. $($_.exception.message)" } } if ($(try{compare-object $($primaryobj.msExchResourceMetaData) $($secondaryobj.msExchResourceMetaData)}catch{}) -or ($($primaryobj.msExchResourceMetaData) -xor $($secondaryobj.msExchResourceMetaData))) { try { write-Slog "$samaccountname" "AR" "Secondary msExchResourceMetaData attr update required" $secondaryobj.msExchResourceMetaData = $primaryobj.msExchResourceMetaData $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchResourceMetaData attr. $($_.exception.message)" } } if ($(try{compare-object $($primaryobj.msExchResourceSearchProperties) $($secondaryobj.msExchResourceSearchProperties)}catch{}) -or ($($primaryobj.msExchResourceSearchProperties) -xor $($secondaryobj.msExchResourceSearchProperties))) { try { write-Slog "$samaccountname" "AR" "Secondary msExchResourceSearchProperties attr update required" $secondaryobj.msExchResourceSearchProperties = $primaryobj.msExchResourceSearchProperties $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchResourceSearchProperties attr. $($_.exception.message)" } } } if (($primaryobj.targetaddress) -and $primaryobj.targetaddress -notmatch "@mail`.on$targetdomain$" -and $primarytype -ne $secondarytype) { write-Slog "$samaccountname" "AR" "Converting secondary to $primarytype" $secondaryobj.msExchRecipientDisplayType = $primaryobj.msExchRecipientDisplayType $secondaryobj.msExchRecipientTypeDetails = $primaryobj.msExchRecipientTypeDetails $secondaryobj.msExchRemoteRecipientType = $primaryobj.msExchRemoteRecipientType $supdate = $true } #room and equipment external meeting processing if ($primarytype -match "^roommailbox$|^equipmentmailbox$") { try { $calproc = $null; $calproc = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-calendarprocessing -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" $BookinPolicy = $null; $BookinPolicy = $calproc | select -ExpandProperty bookinpolicy | % {Convertto-DistinguishedName -identity $samaccountname -CanonicalName $_} $RequestInPolicy = $null; $RequestInPolicy = $calproc | select -ExpandProperty RequestInPolicy | % {Convertto-DistinguishedName -identity $samaccountname -CanonicalName $_} $RequestOutOfPolicy = $null; $RequestOutOfPolicy = $calproc | select -ExpandProperty RequestOutOfPolicy | % {Convertto-DistinguishedName -identity $samaccountname -CanonicalName $_} # need to apply after mailbox migration if ($primary -eq "source") { $BookinPolicyGUIDs = $null; $BookinPolicyGUIDs = @(); $BookinPolicyGUIDs = Get-EMSecondaryGUIDs -Identity $samaccountname -Attribute BookInPolicy -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $BookinPolicy -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc Write-EMData -Identity $samaccountname -Type BookInPolicy -Data $BookinPolicyGUIDs $RequestInPolicyGUIDs = $null; $RequestInPolicyGUIDs = @(); $RequestInPolicyGUIDs = Get-EMSecondaryGUIDs -Identity $samaccountname -Attribute RequestInPolicy -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $RequestInPolicy -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc Write-EMData -Identity $samaccountname -Type RequestInPolicy -Data $RequestinPolicyGUIDs $RequestOutOfPolicyGUIDs = $null; $RequestOutOfPolicyGUIDs = @(); $RequestOutOfPolicyGUIDs = Get-EMSecondaryGUIDs -Identity $samaccountname -Attribute RequestOutOfPolicy -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $RequestOutOfPolicy -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc Write-EMData -Identity $samaccountname -Type RequestOutOfPolicy -Data $RequestOutOfPolicyGUIDs } } catch { write-Slog "$samaccountname" "ERR" "Problem getting calendar processing from primary. Unable to continue. $($_.exception.message)" } if (!($calproc.ProcessExternalMeetingMessages)) { write-Slog "$samaccountname" "AR" "Setting ProcessExternalMeetingMessages to $true for primary" if ($mode -eq "prepare") { try { write-Slog "$samaccountname" "OK" "Set ProcessExternalMeetingMessages to $true for primary" invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "set-calendarprocessing -identity ""$($primaryobj.objectguid.guid)"" -ProcessExternalMeetingMessages 1 -domaincontroller ""$($primarypdc)"" -ea stop" } catch { write-Slog "$samaccountname" "ERR" "Problem setting calendar processing from primary. Unable to continue. $($_.exception.message)" } } } } #Sent items configuration if ($primary -eq "source") { if ($primarytype -match "^usermailbox$|^linkedmailbox$|^sharedmailbox$|^roommailbox$|^equipmentmailbox$") { try { $sentconfig = $null; $sentconfig = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "Get-MailboxSentItemsConfiguration -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" Write-EMData -Identity $samaccountname -Type SendAsItemsCopiedTo -Data $($sentconfig.SendAsItemsCopiedTo) Write-EMData -Identity $samaccountname -Type SendOnBehalfOfItemsCopiedTo -Data $($sentconfig.SendOnBehalfOfItemsCopiedTo) } catch { write-Slog "$samaccountname" "ERR" "Problem getting sent items configuration from primary. Unable to continue. $($_.exception.message)" } } } #msExchEnableModeration if ($($primaryobj.msExchEnableModeration) -ne $($secondaryobj.msExchEnableModeration)) { write-Slog "$samaccountname" "AR" "Secondary msExchEnableModeration attr update required" $secondaryobj.msExchEnableModeration = $primaryobj.msExchEnableModeration $supdate = $true } #msExchModerationFlags if ($($primaryobj.msExchModerationFlags) -ne $($secondaryobj.msExchModerationFlags)) { write-Slog "$samaccountname" "AR" "Secondary msExchModerationFlags attr update required" $secondaryobj.msExchModerationFlags = $primaryobj.msExchModerationFlags $supdate = $true } #msExchModeratedByLink $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute msExchModeratedByLink -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.msExchModeratedByLink -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.msExchModeratedByLink) $($sdns)}catch{})) -or ($($secondaryobj.msExchModeratedByLink) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary msExchModeratedByLink attr update required" $secondaryobj.msExchModeratedByLink = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary msExchModeratedByLink attr. $($_.exception.message)" } } #msExchBypassModerationLink $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute msExchBypassModerationLink -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.msExchBypassModerationLink -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.msExchBypassModerationLink) $($sdns)}catch{})) -or ($($secondaryobj.msExchBypassModerationLink) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary msExchBypassModerationLink attr update required" $secondaryobj.msExchBypassModerationLink = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary msExchBypassModerationLink attr. $($_.exception.message)" } } #publicdelegates $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute publicdelegates -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.publicdelegates -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.publicdelegates) $($sdns)}catch{})) -or ($($secondaryobj.publicdelegates) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary publicdelegates attr update required" $secondaryobj.publicdelegates = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary publicdelegates attr. $($_.exception.message)" } } #msExchDelegateListLink (automapping) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute msExchDelegateListLink -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.msExchDelegateListLink -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.msExchDelegateListLink) $($sdns)}catch{})) -or ($($secondaryobj.msExchDelegateListLink) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary msExchDelegateListLink attr update required" $secondaryobj.msExchDelegateListLink = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary $Attribute attr. $($_.exception.message)" } } #msExchMailboxTemplateLink (retention policies) if ($primaryobj.msExchMailboxTemplateLink) { try { $primarypol = $null; $primarypol = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-retentionpolicy ""$($primaryobj.msExchMailboxTemplateLink)"" -domaincontroller ""$($primarypdc)""" if (($primarypol | measure).count -gt 1) {write-Slog "$samaccountname" "WARN" "Multiple retention policies for primary for '$($primaryobj.msExchMailboxTemplateLink)'";throw} } catch { write-Slog "$samaccountname" "ERR" "Problem getting retention policy data from primary for '$($primaryobj.msExchMailboxTemplateLink)'. Unable to continue. $($_.exception.message)" } try { $allsecpols = $null; $allsecpols = invoke-emexchangecommand -endpoint $secondaryendpoint -domaincontroller $secondarypdc -credential $secondarycred -command "get-retentionpolicy -domaincontroller ""$($secondarypdc)""" } catch { write-Slog "$samaccountname" "ERR" "Problem getting retention policy data from secondary for '$($primarypol.retentionid)'. Unable to continue. $($_.exception.message)" } $secondarypol = $null if ($allsecpols) { $secondarypol = $allsecpols | ? {$_.retentionid -eq $($primarypol.retentionid)} } else { write-Slog "$samaccountname" "ERR" "Secondary retention policies not found. Unable to continue. $($_.exception.message)" } if ($secondarypol) { if ($secondaryobj.msExchMailboxTemplateLink -ne $($secondarypol.distinguishedname)) { write-Slog "$samaccountname" "AR" "Secondary msExchMailboxTemplateLink attr update required" $secondaryobj.msExchMailboxTemplateLink = $($secondarypol.distinguishedname) $supdate = $true } } else { write-Slog "$samaccountname" "WARN" "Retention policy with retentionid '$($primarypol.retentionid)' does not exist in secondary" $defpol = $null; $defpol = $allsecpols | ? {$_.name -match "^Default MRM Policy$|^Default Archive and Retention Policy$"} if ($($defpol | measure).count -eq 0) { write-Slog "$samaccountname" "ERR" "No default retention policy found in secondary. Unable to continue. $($_.exception.message)" } if ($($defpol | measure).count -gt 1) { write-Slog "$samaccountname" "ERR" "Multiple default retention policies found in secondary. Unable to continue. $($_.exception.message)" } if ($($defpol | measure).count -eq 1) { write-Slog "$samaccountname" "AR" "Assigning default retention policy '$($defpol.name)' to secondary" $secondaryobj.msExchMailboxTemplateLink = $($defpol.distinguishedname) $supdate = $true } } } #msExchPoliciesExcluded msExchPoliciesIncluded (do not apply email address policies) if ($mode -eq "prepare") { if ($primaryobj.msExchPoliciesExcluded -notcontains '{26491cfc-9e50-4857-861b-0cb8df22b5d7}') { write-Slog "$samaccountname" "AR" "Primary msExchPoliciesExcluded attr update required" $primaryobj.msExchPoliciesExcluded = '{26491cfc-9e50-4857-861b-0cb8df22b5d7}' $pupdate = $true } if ($primaryobj.msExchPoliciesIncluded -ne $null) { write-Slog "$samaccountname" "AR" "Primary msExchPoliciesIncluded attr update required" $primaryobj.msExchPoliciesIncluded = $null $pupdate = $true } if ($secondaryobj.msExchPoliciesExcluded -notcontains '{26491cfc-9e50-4857-861b-0cb8df22b5d7}') { write-Slog "$samaccountname" "AR" "Secondary msExchPoliciesExcluded attr update required" $secondaryobj.msExchPoliciesExcluded = '{26491cfc-9e50-4857-861b-0cb8df22b5d7}' $supdate = $true } if ($secondaryobj.msExchPoliciesIncluded -ne $null) { write-Slog "$samaccountname" "AR" "Secondary msExchPoliciesIncluded attr update required" $secondaryobj.msExchPoliciesIncluded = $null $supdate = $true } } # single item recovery if ($($primaryobj.deletedItemFlags) -ne $($secondaryobj.deletedItemFlags)) { write-Slog "$samaccountname" "AR" "Secondary deletedItemFlags attr update required" $secondaryobj.deletedItemFlags = $primaryobj.deletedItemFlags $supdate = $true } if ($($primaryobj.garbageCollPeriod) -ne $($secondaryobj.garbageCollPeriod)) { write-Slog "$samaccountname" "AR" "Secondary garbageCollPeriod attr update required" $secondaryobj.garbageCollPeriod = $primaryobj.garbageCollPeriod $supdate = $true } #commit changes if ($Mode -eq "Prepare") { try { if ($pupdate -eq $true) { set-adobject -instance $primaryobj -server $primarypdc -Credential $primaryCred -ea stop write-Slog "$samaccountname" "OK" "Primary user prepared in domain '$primarydomain'" } } catch { write-Slog "$samaccountname" "ERR" "Problem preparing primary user in domain '$primarydomain'. $($_.exception.message)" } try { if ($supdate -eq $true) { set-adobject -instance $secondaryobj -server $secondarypdc -Credential $secondaryCred -ea stop write-Slog "$samaccountname" "OK" "Secondary user prepared in domain '$secondarydomain'" } } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary user in domain '$secondarydomain'. $($_.exception.message)" } if ($supdate -eq $true -and $sguidupdate -eq $true) { $completed = $null $secondsmax = $null; $secondsmax = 300 $secondsinc = $null; $secondsinc = 30 $start = $null; $start = get-date if (!($pguid)) {write-Slog "$samaccountname" "ERR" "Primary guid missing. Unable to continue. $($_.exception.message)"} write-Slog "$samaccountname" "LOG" "Waiting for secondary AD changes to be ready. Waiting up to $secondsmax seconds" Do { try { invoke-emexchangecommand -endpoint $secondaryendpoint -domaincontroller $secondarypdc -credential $secondarycred -command "get-user ""$pguid"" -domaincontroller ""$($secondarypdc)"" -ea stop" | out-null $completed = $true } catch{sleep -s $secondsinc} if ((new-timespan -Start $start -End (get-date)).seconds -ge $secondsmax) { write-Slog "$samaccountname" "ERR" "AD preparation timeout. Unable to continue. $($_.exception.message)" } write-Slog "$samaccountname" "LOG" "Waited $([math]::round((new-timespan -Start $start -End (get-date)).totalseconds)) seconds" } while (!($completed)) } } #move mailbox if req if ($activity -eq "migrate") { #movehistory if ($movemailbox -match "^Yes$|^Suspend$" -and $primary -eq "target") { write-Slog "$samaccountname" "ERR" "MoveMailbox parameter set to '$movemailbox'. Mailbox already migrated. Unable to continue" } $movehistory = $null; try {$movehistory = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-moverequeststatistics -identity ""$($target.objectguid.guid)"" -domaincontroller ""$($targetpdc)"""} catch {} if (($movehistory | measure).count -gt 1) {write-Slog "$samaccountname" "ERR" "Multiple move requests found. Unable to continue. $($_.exception.message)"} $movehistoryfound = $false $sourcedatabase = $null if ($movehistory) { $movehistoryfound = $true write-Slog "$samaccountname" "Log" "Move history found" $workload = $null; $workload = $movehistory.workloadtype.value $sourcedatabase = $movehistory.remotedatabasename #$sourcedatabase #return if (!($workload)) {write-Slog "$samaccountname" "ERR" "Unable to determine move request workload type. Unable to continue"} $movehistory = $movehistory.status.tostring() if ($movemailbox -match "^Yes$|^Suspend$" -and $workload -eq "offboarding") { write-Slog "$samaccountname" "AR" "Removing move request history from target Exchange Organisation" try { invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command $("remove-moverequest -identity ""$($target.objectguid.guid)"" -domaincontroller ""$($targetpdc)""" + ' -confirm:$false') write-Slog "$samaccountname" "OK" "Move request history removed" $movehistory = $null sleep -s 5 } catch { write-Slog "$samaccountname" "ERR" "Issue removing move request history. Unable to continue. $($_.exception.message)" } } } if (!($movehistory) -and $primary -eq "source") { write-Slog "$samaccountname" "Log" "No move history found and primary located in source Exchange Organisation" } if (!($movehistory) -and $primary -eq "target") { write-Slog "$samaccountname" "Log" "No move history found but primary located in target Exchange Organisation" $movehistory = "Completed" } if ($MoveMailbox -eq "rollback") { if ($sourcedatabase -eq $null) { $sourcedatabase = $Script:ModuleDefaultRollbackDatabase write-Slog "$samaccountname" "WARN" "Unable to determine source mailbox database from move history. Using default rollback database from configuration '$sourcedatabase'" } else { write-Slog "$samaccountname" "Log" "Using source mailbox database from move history '$sourcedatabase'" } } $moveobj = $null; $moveobj = [pscustomobject]@{ Source = $Source Target = $Target SourceDomain = $SourceDomain TargetDomain = $TargetDomain SamAccountname = $Samaccountname SourceCred = $sourcecred TargetCred = $targetcred Activity = $Activity SourceEndPoint = $SourceEndPoint TargetEndPoint = $TargetEndPoint TargetRoutingSMTP = $TargetRoutingSMTP SourcePDC = $SourcePDC TargetPDC = $targetPDC Mode = $Mode MoveMailbox = $MoveMailbox MoveHistory = $MoveHistory Link = $Link Separate = $Separate Wait = $Wait SourceDatabase = $Sourcedatabase } if ($movehistory) { switch -Regex ($($movehistory)) { 'Completed|CompletedWithWarning' { #write-Slog "$samaccountname" "LOG" "Move request state: $($movehistory)" if ($mode -eq "prepare" -and $movemailbox -match "^yes$|^no$") { # process the Data directory to apply post migration actions $actions = $null; try { $actions = $(Read-EMData -Identity $samaccountname) } catch { write-Slog "$samaccountname" "WARN" "Issue reading post migration actions. Unable to continue" } if ($actions -and $primary -eq "target") { write-Slog "$samaccountname" "LOG" "Post migration actions detected" $actions | % { $action = $null; $action = $_ $actiontype = $null; $actiontype = $_.type $actiondata = $null; $actiondata = $_.data if (!($actiondata)) {$actiondata = "`$null"} switch ($actiontype) { "BookInPolicy" { try { write-Slog "$samaccountname" "AR" "'BookInPolicy' attr update required" invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "set-calendarprocessing -identity ""$($primaryobj.objectguid.guid)"" -BookInPolicy ""$($actiondata -join ",")"" -domaincontroller ""$($primarypdc)"" -ea stop" write-Slog "$samaccountname" "OK" "'BookInPolicy' attr updated" $action | Update-EMData -Identity $samaccountname } catch { write-Slog "$samaccountname" "WARN" "'BookInPolicy' issue updating. $($_.exception.message)" } } "RequestInPolicy" { try { write-Slog "$samaccountname" "AR" "'RequestInPolicy' attr update required" invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "set-calendarprocessing -identity ""$($primaryobj.objectguid.guid)"" -RequestInPolicy ""$($actiondata -join ",")"" -domaincontroller ""$($primarypdc)"" -ea stop" write-Slog "$samaccountname" "OK" "'RequestInPolicy' attr updated" $action | Update-EMData -Identity $samaccountname } catch { write-Slog "$samaccountname" "WARN" "'RequestInPolicy' issue updating. $($_.exception.message)" } } "RequestOutOfPolicy" { try { write-Slog "$samaccountname" "AR" "'RequestOutOfPolicy' attr update required" invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "set-calendarprocessing -identity ""$($primaryobj.objectguid.guid)"" -RequestOutOfPolicy ""$($actiondata -join ",")"" -domaincontroller ""$($primarypdc)"" -ea stop" write-Slog "$samaccountname" "OK" "'RequestOutOfPolicy' attr updated" $action | Update-EMData -Identity $samaccountname } catch { write-Slog "$samaccountname" "WARN" "'RequestOutOfPolicy' issue updating. $($_.exception.message)" } } "SendAsItemsCopiedTo" { try { $mailbox = $null; $mailbox = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-mailbox -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" if (($actiondata -eq "sender" -and $($mailbox.MessageCopyForSentAsEnabled) -eq $true) -OR ($actiondata -eq "senderandfrom" -and $mailbox.MessageCopyForSentAsEnabled -eq $false)) { #Adding controls for linked mailboxes that is needed in advance if ($link -eq $false) { if ($primarytype -eq "linkedmailbox") { write-Slog "$samaccountname" "AR" "Converting primary to user mailbox" try { Invoke-Command -ConnectionUri http://$PrimaryEndPoint/powershell -credential $PrimaryCred -ConfigurationName microsoft.exchange -scriptblock {set-user -identity "$(($using:primaryobj).objectguid.guid)" -linkedmasteraccount $null -domaincontroller "$($using:primarypdc)" -ea stop} -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop | out-null write-Slog "$samaccountname" "OK" "Primary converted to user mailbox" $primarytype = "usermailbox" } catch { write-Slog "$samaccountname" "ERR" "Problem converting to user mailbox. Unable to continue. $($_.exception.message)" } } } write-Slog "$samaccountname" "AR" "'SendAsItemsCopiedTo' attr update required" if ($actiondata -eq "sender") { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command $("set-mailbox -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)""" + ' -MessageCopyForSentAsEnabled $false') | out-null } if ($actiondata -eq "senderandfrom") { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command $("set-mailbox -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)""" + ' -MessageCopyForSentAsEnabled $true') | out-null } write-Slog "$samaccountname" "OK" "'SendAsItemsCopiedTo' attr updated" } $action | Update-EMData -Identity $samaccountname } catch { write-Slog "$samaccountname" "WARN" "'SendAsItemsCopiedTo' issue updating. $($_.exception.message)" } } "SendOnBehalfOfItemsCopiedTo" { try { $mailbox = $null; $mailbox = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-mailbox -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" if (($actiondata -eq "sender" -and $($mailbox.MessageCopyForSendOnBehalfEnabled) -eq $true) -OR ($actiondata -eq "senderandfrom" -and $mailbox.MessageCopyForSendOnBehalfEnabled -eq $false)) { #Adding controls for linked mailboxes that is needed in advance if ($link -eq $false) { if ($primarytype -eq "linkedmailbox") { write-Slog "$samaccountname" "AR" "Converting primary to user mailbox" try { Invoke-Command -ConnectionUri http://$PrimaryEndPoint/powershell -credential $PrimaryCred -ConfigurationName microsoft.exchange -scriptblock {set-user -identity "$(($using:primaryobj).objectguid.guid)" -linkedmasteraccount $null -domaincontroller "$($using:primarypdc)" -ea stop} -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop | out-null write-Slog "$samaccountname" "OK" "Primary converted to user mailbox" $primarytype = "usermailbox" } catch { write-Slog "$samaccountname" "ERR" "Problem converting to user mailbox. Unable to continue. $($_.exception.message)" } } } write-Slog "$samaccountname" "AR" "'SendOnBehalfOfItemsCopiedTo' attr update required" if ($actiondata -eq "sender") { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command $("set-mailbox -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)""" + ' -MessageCopyForSendOnBehalfEnabled $false') | out-null } if ($actiondata -eq "senderandfrom") { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command $("set-mailbox -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)""" + ' -MessageCopyForSendOnBehalfEnabled $true') | out-null } write-Slog "$samaccountname" "OK" "'SendOnBehalfOfItemsCopiedTo' attr updated" } $action | Update-EMData -Identity $samaccountname } catch { write-Slog "$samaccountname" "WARN" "'SendOnBehalfOfItemsCopiedTo' issue updating. $($_.exception.message)" } } default {write-Slog "$samaccountname" "WARN" "Type '$($actiontype)' unsupported and will be ignored"} } } } } #ROLLBACK if ($mode -eq "prepare" -and $movemailbox -eq "rollback") { if ($primary -eq "source") { write-Slog "$samaccountname" "ERR" "Mailbox is already hosted on the source Exchange Organisation" } write-Slog "$samaccountname" "AR" "Rolling back mailbox to source Exchange Organisation" if ($movehistoryfound) { write-Slog "$samaccountname" "AR" "Removing move request history from target Exchange Organisation" try { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command $("remove-moverequest -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)""" + ' -confirm:$false') write-Slog "$samaccountname" "OK" "Move request history removed" } catch { write-Slog "$samaccountname" "ERR" "Issue removing move request history. Unable to continue. $($_.exception.message)" } } else { write-Slog "$samaccountname" "LOG" "No move history found" } $moveobj | Start-EMMigrateMailbox; return } } 'AutoSuspended' { if ($mode -eq "prepare" -and $movemailbox -match "^yes$|^suspend$") { write-Slog "$samaccountname" "AR" "Resuming move request. Move request state: $($movehistory)" $moveobj | Start-EMMigrateMailbox; return } } default { write-Slog "$samaccountname" "ERR" "Move request in unsupported state: $($movehistory). Unable to continue. $($_.exception.message)" } } } if (!($movehistory) -and $primary -eq "Source") { if ($mode -eq "prepare" -and $movemailbox -match "^yes$|^suspend$") { write-Slog "$samaccountname" "AR" "Creating move request. Move request state: None" $moveobj | Start-EMMigrateMailbox; return } } $checkperms = $false if (($movehistory -match "^completed|^autosuspended$" -or (!($movehistory))) -and (!($primaryobj.targetaddress)) -and $primarytype -notmatch "remote") { $checkperms = $true } #permissions if ($checkperms -and $separate -eq $false -and $activity -eq "migrate") { #full access write-Slog "$samaccountname" "LOG" "Checking full access permissions on primary" try { if(!($($primaryobj.objectguid.guid))){write-Slog "$samaccountname" "WARN" "Primary missing guid";throw} $mperms = $null; $mperms = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-mailboxpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" | ? {$_.isinherited -eq $false -and $_.user -ne 'NT AUTHORITY\SELF' -and $_.accessrights -eq 'fullaccess' -and $_.deny -eq $false} } catch { write-Slog "$samaccountname" "ERR" "Issue checking full access permissions on primary. $($_.exception.message)" } if ($mperms) { foreach ($perm in $mperms) { if ($perm.user -match "^($primarynbdomain)\\") { $sam = $null; $sam = $perm.user -replace ($primarynbdomain + "\\"),"" if (!($mperms | ? {$_.user -match "^$secondarynbdomain\\$sam$"})) { write-Slog "$samaccountname" "AR" "'$("$secondarynbdomain\$sam")' full access missing" if ($Mode -eq "prepare") { try { if (get-adobject -filter {samaccountname -eq $sam} -server $secondarypdc -Credential $secondarycred -ea stop) { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "add-mailboxpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -user ""$("$secondarynbdomain\$sam")"" -accessrights fullaccess -automapping 0" | out-null write-Slog "$samaccountname" "OK" "'$("$secondarynbdomain\$sam")' full access added" } else { write-Slog "$samaccountname" "WARN" "'$("$secondarynbdomain\$sam")' does not exist in domain '$secondarydomain'" } } catch { write-Slog "$samaccountname" "WARN" "'$("$secondarynbdomain\$sam")' issue adding full access permission and will be excluded" } } else { write-Slog "$samaccountname" "WARN" "No full access changes committed due to mode" } } } if ($perm.user -match "^($secondarynbdomain)\\") { $sam = $null; $sam = $perm.user -replace ($secondarynbdomain + "\\"),"" if (!($mperms | ? {$_.user -match "^$primarynbdomain\\$sam$"}) -and $sam -ne $samaccountname) { write-Slog "$samaccountname" "AR" "'$("$primarynbdomain\$sam")' full access missing" if ($Mode -eq "prepare") { try { if (get-adobject -filter {samaccountname -eq $sam} -server $primarypdc -Credential $primarycred -ea stop) { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "add-mailboxpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -user ""$("$primarynbdomain\$sam")"" -accessrights fullaccess -automapping 0" | out-null write-Slog "$samaccountname" "OK" "'$("$primarynbdomain\$sam")' full access added" } else { write-Slog "$samaccountname" "WARN" "'$("$primarynbdomain\$sam")' does not exist in domain '$primarydomain'" } } catch { write-Slog "$samaccountname" "WARN" "'$("$primarynbdomain\$sam")' issue adding full access permission and will be excluded" } } else { write-Slog "$samaccountname" "WARN" "No full access changes committed due to mode" } } } } } #send-as write-Slog "$samaccountname" "LOG" "Checking send-as permissions on primary" try { $adperms = $null; $adperms = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" | ? {$_.isinherited -eq $false -and $_.user -ne 'NT AUTHORITY\SELF' -and $_.extendedrights -match "send-as" -and $_.deny -eq $false} } catch { write-Slog "$samaccountname" "ERR" "Issue checking send-as permissions on primary. $($_.exception.message)" } if ($adperms) { foreach ($perm in $adperms) { if ($perm.user -match "^($primarynbdomain)\\") { $sam = $null; $sam = $perm.user -replace ($primarynbdomain + "\\"),"" if (!($adperms | ? {$_.user -match "^$secondarynbdomain\\$sam$"})) { write-Slog "$samaccountname" "AR" "'$("$secondarynbdomain\$sam")' send-as missing" if ($Mode -eq "prepare") { try { if (get-adobject -filter {samaccountname -eq $sam} -server $secondarypdc -Credential $secondarycred -ea stop) { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "add-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -user ""$("$secondarynbdomain\$sam")"" -accessrights extendedright -extendedrights send-as" | out-null write-Slog "$samaccountname" "OK" "'$("$secondarynbdomain\$sam")' send-as added" } else { write-Slog "$samaccountname" "WARN" "'$("$secondarynbdomain\$sam")' does not exist in domain '$secondarydomain'" } } catch { write-Slog "$samaccountname" "WARN" "'$("$secondarynbdomain\$sam")' issue adding send-as permission and will be excluded. $($_.exception.message)" } } else { write-Slog "$samaccountname" "WARN" "No send-as changes committed due to mode" } } } if ($perm.user -match "^($secondarynbdomain)\\") { $sam = $null; $sam = $perm.user -replace ($secondarynbdomain + "\\"),"" if (!($adperms | ? {$_.user -match "^$primarynbdomain\\$sam$"}) -and $sam -ne $samaccountname) { write-Slog "$samaccountname" "AR" "'$("$primarynbdomain\$sam")' send-as missing" if ($Mode -eq "prepare") { try { if (get-adobject -filter {samaccountname -eq $sam} -server $primarypdc -Credential $primarycred -ea stop) { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "add-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -user ""$("$primarynbdomain\$sam")"" -accessrights extendedright -extendedrights send-as" | out-null write-Slog "$samaccountname" "OK" "'$("$primarynbdomain\$sam")' send-as added" } else { write-Slog "$samaccountname" "WARN" "'$("$primarynbdomain\$sam")' does not exist in domain '$primarydomain'" } } catch { write-Slog "$samaccountname" "WARN" "'$("$primarynbdomain\$sam")' issue adding send-as permission and will be excluded. $($_.exception.message)" } } else { write-Slog "$samaccountname" "WARN" "No send-as changes committed due to mode" } } } } } } } #permissions on separate or galsync if ($separate -eq $true -or $activity -eq "galsync") { #full access write-Slog "$samaccountname" "LOG" "Checking full access permissions on primary" try { if(!($($primaryobj.objectguid.guid))){write-Slog "$samaccountname" "WARN" "Primary missing guid";throw} $mperms = $null; $mperms = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-mailboxpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" | ? {$_.isinherited -eq $false -and $_.user -ne 'NT AUTHORITY\SELF' -and $_.accessrights -eq 'fullaccess' -and $_.deny -eq $false} } catch { write-Slog "$samaccountname" "ERR" "Issue checking full access permissions on primary. $($_.exception.message)" } if ($mperms) { foreach ($perm in $mperms) { if ($perm.user -match "^$($secondarynbdomain)\\") { write-Slog "$samaccountname" "AR" "'$($perm.user)' full access to be removed" try { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command $("remove-mailboxpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -user ""$($perm.user)"" -accessrights fullaccess" + ' -confirm:$false') | out-null write-Slog "$samaccountname" "OK" "'$($perm.user)' full access removed" } catch { write-Slog "$samaccountname" "WARN" "'$($perm.user)' issue removing full access permission and will be excluded" } } } } #send-as write-Slog "$samaccountname" "LOG" "Checking send-as permissions on primary" try { $adperms = $null; $adperms = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" | ? {$_.isinherited -eq $false -and $_.user -ne 'NT AUTHORITY\SELF' -and $_.extendedrights -match "send-as" -and $_.deny -eq $false} } catch { write-Slog "$samaccountname" "ERR" "Issue checking send-as permissions on primary. $($_.exception.message)" } if ($adperms) { foreach ($perm in $adperms) { if ($perm.user -match "^$($secondarynbdomain)\\") { write-Slog "$samaccountname" "AR" "'$($perm.user)' send-as to be removed" try { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command $("remove-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -user ""$($perm.user)"" -accessrights extendedright -extendedrights send-as" + ' -confirm:$false') | out-null write-Slog "$samaccountname" "OK" "'$($perm.user)' send-as removed" } catch { write-Slog "$samaccountname" "WARN" "'$($perm.user)' issue removing send-as permission and will be excluded" } } } } } #link if ($mode -eq "prepare" -and $activity -eq "migrate" -and $link -eq $true) { if ($primarytype -eq "usermailbox") { write-Slog "$samaccountname" "AR" "Converting primary to linked mailbox" try { $masteraccount = $null; $masteraccount = "$($secondarynbdomain)\$($samaccountname)" Invoke-Command -ConnectionUri http://$PrimaryEndPoint/powershell -credential $PrimaryCred -ConfigurationName microsoft.exchange -scriptblock {set-user -identity "$(($using:primaryobj).objectguid.guid)" -linkeddomaincontroller "$using:secondarypdc" -linkedmasteraccount "$using:masteraccount" -linkedcredential:$using:secondarycred -domaincontroller "$($using:primarypdc)" -ea stop} -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop | out-null write-Slog "$samaccountname" "OK" "Primary converted to linked mailbox" } catch { write-Slog "$samaccountname" "ERR" "Problem converting to linked mailbox. Unable to continue. $($_.exception.message)" } } else { if ($primarytype -ne "linkedmailbox") { write-Slog "$samaccountname" "WARN" "Unable to convert primary to linked mailbox due to unsupported recipient type '$($primarytype)'" } } } if (($mode -eq "prepare" -and $activity -eq "migrate" -and $link -eq $false) -or ($mode -eq "prepare" -and $activity -eq "galsync")) { if ($primarytype -eq "linkedmailbox") { write-Slog "$samaccountname" "AR" "Converting primary to user mailbox" try { Invoke-Command -ConnectionUri http://$PrimaryEndPoint/powershell -credential $PrimaryCred -ConfigurationName microsoft.exchange -scriptblock {set-user -identity "$(($using:primaryobj).objectguid.guid)" -linkedmasteraccount $null -domaincontroller "$($using:primarypdc)" -ea stop} -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop | out-null write-Slog "$samaccountname" "OK" "Primary converted to user mailbox" } catch { write-Slog "$samaccountname" "ERR" "Problem converting to user mailbox. Unable to continue. $($_.exception.message)" } } } #GALSync on separation handling if ($activity -eq "GalSync"){ if ($Secondaryobj.distinguishedname -notmatch $SecondaryGALSyncOU ) { write-Slog "$samaccountname" "AR" "Moving secondary to GALSync OU '$SecondaryGALSyncOU'" try { Move-ADObject -Identity $($Secondaryobj.objectguid.guid) -TargetPath $SecondaryGALSyncOU -Server $secondarypdc -Credential $secondarycred -ea Stop write-Slog "$samaccountname" "OK" "Moved secondary to GALSync OU '$SecondaryGALSyncOU'" } catch { write-Slog "$samaccountname" "ERR" "Problem moving secondary to GALSync OU '$SecondaryGALSyncOU'. $($_.exception.message)" } } } write-Slog "$samaccountname" "LOG" "Ready" } } function Start-EMMigrateMailbox() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourcePDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Sourcedatabase, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountname, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Activity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetRoutingSMTP, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Yes','No','Suspend','Rollback')]$MoveMailbox, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$MoveHistory, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet("",$true,$false)]$Link, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Wait = $Script:ModuleWait ) Process { try { $tddomain = $null; $tddomain = $tddomain = $source.proxyaddresses $tddomain = ($tddomain | ? {$_ -cmatch "^SMTP:"}) -replace "SMTP:","" $tddomain = $tddomain.substring($tddomain.indexof("@") + 1,$tddomain.length - $tddomain.indexof("@") -1) $tddomain = $tddomain.toupper() } catch { write-Slog "$samaccountname" "ERR" "Problem preparing target delivery domain for '$targetdomain'. $($_.exception.message)" } $completed = $null $secondsmax = $null; $secondsmax = 43200 $secondsinc = $null; $secondsinc = 120 $start = $null; $start = get-date if ($movehistory) { switch -regex ($movehistory) { 'Completed|CompletedWithWarning' { #write-Slog "$samaccountname" "LOG" "Move request state: $($movehistory)" #ROLLBACK if ($movemailbox -eq 'Rollback') { try { Invoke-Command -ConnectionUri http://$TargetEndpoint/powershell -credential $TargetCred -ConfigurationName microsoft.exchange -scriptblock {New-MoveRequest -Identity $($using:target.objectguid.guid) -Outbound -RemoteHostName "$using:SourceEndPoint" -BadItemLimit 10000 -AcceptLargeDataLoss -AllowLargeItems -domaincontroller "$using:targetpdc" -remotecredential $using:sourcecred -RemoteTargetDatabase $using:sourcedatabase -TargetDeliveryDomain $using:sourcedomain -ea stop -warningaction silentlycontinue} -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop | out-null if ($wait) { write-Slog "$samaccountname" "OK" "Rollback move request and set to complete. Waiting $secondsmax seconds to complete" } else { write-Slog "$samaccountname" "OK" "Rollback move request and set to complete." } } catch { write-Slog "$samaccountname" "ERR" "Problem creating rollback move request. $($_.exception.message)" } } } 'AutoSuspended' { if ($movemailbox -eq 'Yes') { try { invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "Resume-MoveRequest -identity ""$($target.objectguid.guid)"" -domaincontroller ""$($targetpdc)""" if ($wait) { write-Slog "$samaccountname" "OK" "Resumed move request and set to complete. Waiting $secondsmax seconds to complete" } else { write-Slog "$samaccountname" "OK" "Resumed move request and set to complete." } } catch { write-Slog "$samaccountname" "ERR" "Problem resuming move request. $($_.exception.message)" } } if ($movemailbox -eq 'Suspend') { try { invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "Resume-MoveRequest -identity ""$($target.objectguid.guid)"" -SuspendWhenReadyToComplete -domaincontroller ""$($targetpdc)""" if ($wait) { write-Slog "$samaccountname" "OK" "Resumed move request and set to suspend. Waiting $secondsmax seconds to suspend" } else { write-Slog "$samaccountname" "OK" "Resumed move request and set to suspend." } } catch { write-Slog "$samaccountname" "ERR" "Problem resuming move request. $($_.exception.message)" } } } default { write-Slog "$samaccountname" "ERR" "Move request in unsupported state: $($movehistory). Unable to continue. $($_.exception.message)" } } } if (!($movehistory)) { if ($movemailbox -eq "Yes") { try { Invoke-Command -ConnectionUri http://$TargetEndpoint/powershell -credential $TargetCred -ConfigurationName microsoft.exchange -scriptblock {New-MoveRequest -Identity "$using:TargetRoutingSMTP" -RemoteGlobalCatalog "$using:SourcePDC" -Remote -RemoteHostName "$using:SourceEndPoint" -BadItemLimit 10000 -AcceptLargeDataLoss -AllowLargeItems -TargetDeliveryDomain "$using:tddomain" -domaincontroller "$using:targetpdc" -remotecredential $using:sourcecred -ea stop -warningaction silentlycontinue} -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop | out-null if ($wait) { write-Slog "$samaccountname" "OK" "Move request created. Waiting $secondsmax seconds to complete" } else { write-Slog "$samaccountname" "OK" "Move request created." } } catch { write-Slog "$samaccountname" "ERR" "Problem creating move request. $($_.exception.message)" } } if ($movemailbox -eq "suspend") { try { Invoke-Command -ConnectionUri http://$TargetEndpoint/powershell -credential $TargetCred -ConfigurationName microsoft.exchange -scriptblock {New-MoveRequest -Identity "$using:TargetRoutingSMTP" -RemoteGlobalCatalog "$using:SourcePDC" -Remote -RemoteHostName "$using:SourceEndPoint" -BadItemLimit 10000 -AcceptLargeDataLoss -AllowLargeItems -SuspendWhenReadyToComplete -TargetDeliveryDomain "$using:tddomain" -domaincontroller "$using:targetpdc" -remotecredential $using:sourcecred -ea stop -warningaction silentlycontinue} -sessionoption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) -allowredirection -warningaction silentlycontinue -ea stop | out-null if ($wait) { write-Slog "$samaccountname" "OK" "Move request created and set to suspend. Waiting $secondsmax seconds to suspend" } else { write-Slog "$samaccountname" "OK" "Move request created and set to suspend." } } catch { write-Slog "$samaccountname" "ERR" "Problem creating move request. $($_.exception.message)" } } } if ($wait) { Do { sleep -s $secondsinc $movestatus = $null; try {$movestatus = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-moverequest ""$($target.objectguid.guid)"" -domaincontroller ""$($targetpdc)"""} catch {} if ($movestatus) {$movestatus = $movestatus.status.tostring()} write-Slog "$samaccountname" "LOG" "Waited $([math]::round((new-timespan -Start $start -End (get-date)).totalseconds)) seconds. State: $($movestatus)" if ((new-timespan -Start $start -End (get-date)).seconds -ge $secondsmax) { write-Slog "$samaccountname" "ERR" "Move request did not complete in $secondsmax seconds. Unable to continue. $($_.exception.message)" } if ($movestatus -match "warn") { write-Slog "$samaccountname" "WARN" "Move request warning detected" } if ($movestatus -match "^Failed") { write-Slog "$samaccountname" "ERR" "Move request failed. Unable to continue. $($_.exception.message)" } if ($movemailbox -match "^yes$|^rollback$" -and $movestatus -match "^Completed") { $completed = $true } if ($movemailbox -eq "Suspend" -and $movestatus -eq "AutoSuspended") { $completed = $true } } while (!($completed)) if ($completed) { write-Slog "$samaccountname" "OK" "Move mailbox request" Start-EMProcessMailbox -SourceDomain $sourcedomain -TargetDomain $targetdomain -Samaccountname $samaccountname -SourceCred $SourceCred -TargetCred $TargetCred -Mode $Mode -Activity $activity -MoveMailbox No -SourceEndPoint $SourceEndPoint -TargetEndPoint $TargetEndPoint -Link $Link -Separate $Separate -Wait $Wait } } else { if ($movemailbox -eq "Yes") { write-Slog "$samaccountname" "LOG" "Not waiting for move request to complete. Post migration actions will be required." } else { write-Slog "$samaccountname" "LOG" "Not waiting for move request to complete." } write-Slog "$samaccountname" "LOG" "Ready" } } } # Distribution Groups ################################################################################################################ function Start-EMProcessDistributionGroup() { <# .SYNOPSIS Process a distribution group. .DESCRIPTION This cmdlet is used to prepare and migrate a distribution group from the source to the target Exchange Organization. .PARAMETER Samaccountname This is the samaccountname attribute of the distribution group you want the cmdlet to process. .PARAMETER SourceCred Specify the source credentials of the source domain. .PARAMETER TargetCred Specify the target credentials of the target domain. .PARAMETER SourceDomain Specify the source domain. .PARAMETER TargetDomain Specify the target domain. .PARAMETER Activity Specify whether you want to MIGRATE or GALSYNC. .PARAMETER Mode Specify whether you want to PREPARE or LOGONLY. .PARAMETER SourceEndPoint Specify the source end point for the source Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER TargetEndPoint Specify the target end point for the target Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER Separate Specifies whether to break the cross-forest relationship and apply limitations to the secondary object. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Samaccountname = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity = $Script:ModuleActivity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode = $Script:ModuleMode, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint = $Script:ModuleTargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { #formatting $sourcedomain = $sourcedomain.toupper() $targetdomain = $targetdomain.toupper() $sourceendpoint = $sourceendpoint.toupper() $targetendpoint = $targetendpoint.toupper() $activity = $activity.toupper() $Mode = $Mode.toupper() write-Slog "$samaccountname" "GO" "$activity distribution group" write-Slog "$samaccountname" "LOG" "SourceCred: $($Sourcecred.username); TargetCred: $($TargetCred.username)" write-Slog "$samaccountname" "LOG" "SourceDomain: $sourcedomain; TargetDomain: $targetdomain; Activity: $Activity; Mode: $Mode; SourceEndPoint: $SourceEndPoint; TargetEndPoint: $TargetEndPoint; Separate: $Separate;" if ($separate) { write-Slog "$samaccountname" "WARN" "Separate parameter not in use and will be ignored" } #get source data try { try { $sourcepdc = $Script:ModuleSourcePDC $sourcedomainsid = $Script:ModuleSourceDomainSID $sourcenbdomain = $Script:ModuleSourceNBDomain $sourcedn = $script:ModuleSourceDN } catch { write-Slog "$samaccountname" "ERR" "Issue getting domain information for source domain '$sourcedomain'. $($_.exception.message)" } $smeg = $null; $smeg = get-adobject -server $sourcepdc -filter {mailnickname -like "*" -and samaccountname -eq $samaccountname} -properties * -credential $sourcecred -ea stop } catch { write-Slog "$samaccountname" "ERR" "Issue getting mail enabled group from '$sourcedomain'. $($_.exception.message)" } if (!($smeg)) { write-Slog "$samaccountname" "ERR" "No mail enabled group object found in source domain '$sourcedomain'. $($_.exception.message)" } else { try { $SourceType = $null; $SourceType = invoke-emexchangecommand -endpoint $sourceendpoint -domaincontroller $sourcepdc -credential $sourcecred -command "get-group -identity ""$($smeg.objectguid.guid)"" -domaincontroller ""$($sourcepdc)"" -ea stop" | select -expandproperty recipienttypedetails } catch { write-Slog "$samaccountname" "ERR" "Issue getting source type from '$sourcedomain'. $($_.exception.message)" } } if ((($smeg | measure).count) -gt 1) { write-Slog "$samaccountname" "ERR" "Duplicate samaccountnames detected in source domain '$($sourcedomain)'. $($_.exception.message)" } if (($smeg | measure).count -eq 1) { if (!(Read-EMBackUp $($smeg.objectguid.guid))) { Write-EMBackUp $smeg } } #get target data try { try { $targetpdc = $null; $targetdomainsid = $null; $targetNBdomain = $null get-addomain -Server $targetdomain -credential $targetcred -ea stop | % { $targetpdc = $_.pdcemulator $targetdomainsid = $_.domainsid.value $targetnbdomain = $_.netbiosname.tostring() } } catch { write-Slog "$samaccountname" "ERR" "Issue getting domain information for target domain '$targetdomain'. $($_.exception.message)" } $tmeg = $null; $tmeg = get-adobject -server $targetpdc -filter {samaccountname -eq $samaccountname} -properties * -credential $targetcred -ea stop } catch { write-Slog "$samaccountname" "ERR" "Issues getting group from target domain '$targetdomain'. $($_.exception.message)" } if ((($tmeg | measure).count) -gt 1) { write-Slog "$samaccountname" "ERR" "Duplicate samaccountnames detected in target domain '$($targetdomain)'. $($_.exception.message)" } if (($tmeg | measure).count -eq 1) { if (!(Read-EMBackUp $($tmeg.objectguid.guid))) { Write-EMBackUp $tmeg } } if ($tmeg) { try { $TargetType = $null; $TargetType = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-group -identity ""$($tmeg.objectguid.guid)"" -domaincontroller ""$($targetpdc)"" -ea stop" | select -expandproperty recipienttypedetails } catch { write-Slog "$samaccountname" "ERR" "Issue getting target type from '$targetdomain'. $($_.exception.message)" } } try { $detected = $null; $detected = Get-EMConflict -identity $samaccountname -Source $smeg -sourcepdc $sourcepdc -targetpdc $targetpdc -sourcedomain $sourcedomain -targetdomain $targetdomain -SourceCred $SourceCred -TargetCred $TargetCred -Targetendpoint $TargetEndPoint } catch { write-Slog "$samaccountname" "ERR" "Issue detecting conflict in target domain '$($targetdomain)'. $($_.exception.message)" } if ($detected) { $detected | select samaccountname,distinguishedname,mailnickname,proxyaddresses | % { write-slog "$samaccountname" "WARN" "Conflict $($_ | convertto-json -compress)" } write-Slog "$samaccountname" "ERR" "SMTP, X500, or Alias conflict detected in target domain '$($targetdomain)'. $($_.exception.message)" } $meg = [pscustomobject]@{ SamAccountName = $samaccountname Activity = $Activity Source = $smeg SourceType = $SourceType SourceDomain = $SourceDomain.toupper() SourceNBDomain = $sourcenbdomain SourcePDC = $SourcePDC.toupper() SourceDomainSID = $sourcedomainsid Target = $tmeg TargetType = $TargetType TargetDomain = $TargetDomain TargetNBDomain = $targetnbdomain TargetPDC = $TargetPDC.toupper() TargetDomainSID = $targetdomainsid TargetCred = $TargetCred SourceCred = $SourceCred Mode = $Mode SourceEndPoint = $SourceEndPoint TargetEndPoint = $TargetEndPoint Separate = $Separate } write-Slog "$samaccountname" "LOG" "SourceType: $($sourcetype); SourcePDC: $($meg.sourcepdc); TargetType: $($targettype); TargetPDC: $($targetpdc)" $meg | Start-EMDistributionGroupPrep } } function Start-EMDistributionGroupPrep() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountName, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourcePDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomainSID, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetType, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { #determine action $next = $null $primary = $null if ($source -eq $null) { write-Slog "$samaccountname" "ERR" "Not found in source domain '$sourcedomain'. Unable to continue. $($_.exception.message)" $next = "Stop" } else { #Migration activity handling if ($Activity -eq "migrate" -and !($target)) { write-Slog "$samaccountname" "ERR" "MIGRATE requires a target group object. Unable to continue. $($_.exception.message)" } if ($Activity -eq "migrate" -and $target) { if ($target.distinguishedname -match $Script:ModuleTargetGALSyncOU) { write-Slog "$samaccountname" "WARN" "MIGRATE target group object located in '$Script:ModuleTargetGALSyncOU'" } } #GALsync activity handling if ($activity -eq "galsync" -and !($target)) { write-Slog "$samaccountname" "AR" "Target group object to be created in '$Script:ModuleTargetGALSyncOU'" $next = "CreateTargetGALGroup" } if ($activity -eq "galsync" -and $target) { if ($target.distinguishedname -notmatch $Script:ModuleTargetGALSyncOU) { write-Slog "$samaccountname" "WARN" "GALSync target group object not located in '$Script:ModuleTargetGALSyncOU'" } } #other if (!($next) -AND ($SourceType -match "^Mail|^RoomList$" -and $TargetType -notmatch "^Mail|^RoomList$" )) { write-Slog "$samaccountname" "AR" "Target to be mail enabled" $next = "MailEnableTargetGroup" } if (!($next) -AND ($SourceType -match "^Mail|^RoomList$" -and $TargetType -match "^Mail|^RoomList$" )) { $next = "PrepareSourceAndTarget" } } #calculate source SMTP addresses $sourcePrimarySMTP = $null; $sourcePrimarySMTP = ($source.proxyaddresses | ? {$_ -cmatch "^SMTP:"}) -replace "SMTP:","" if (($sourcePrimarySMTP | measure).count -gt 1) { write-Slog "$samaccountname" "ERR" "Source has multiple primary SMTP addresses. Unable to continue. $($_.exception.message)" $next = "Stop" } if (($sourcePrimarySMTP | measure).count -eq 0) { write-Slog "$samaccountname" "ERR" "Source has no primary SMTP address. Unable to continue. $($_.exception.message)" $next = "Stop" } $SourceRoutingSMTP = $null; $SourceRoutingSMTP = $("$($Source.mailnickname)@mail.on$($sourcedomain)").tolower() #prepare mode object $Modeobj = [pscustomobject]@{ Samaccountname = $samaccountname Source = $source SourceDomain = $SourceDomain SourceNBDomain = $SourceNBDomain SourcePDC = $SourcePDC Target = $target TargetDomain = $TargetDomain TargetNBDomain = $TargetNBDomain TargetPDC = $TargetPDC Mode = $Mode SourcePrimarySMTP = $SourcePrimarySMTP SourceRoutingSMTP = $SourceRoutingSMTP TargetCred = $TargetCred SourceCred = $SourceCred Activity = $Activity SourceEndPoint = $SourceEndPoint TargetEndPoint = $TargetEndPoint Sourcetype = $sourcetype Targettype = $targettype SourceDomainSID = $SourceDomainSID TargetDomainSID = $TargetDomainSID Separate = $Separate } if ($mode -eq "logonly"){ $next = "Stop" } #apply action if ($next -eq "MailEnableTargetGroup") { if ($mode -eq "prepare") { $Modeobj | Start-EMMailEnableTargetGroup } else { write-Slog "$samaccountname" "LOG" "Not mail enabling target group due to mode" } } if ($next -eq "CreateTargetGALGroup") { if ($mode -eq "prepare") { $Modeobj | New-EMTargetGALGroup } else { write-Slog "$samaccountname" "LOG" "Not creating target GAL group due to mode" } } if ($next -eq "PrepareSourceAndTarget") { $Modeobj | Start-EMPrepareGroupObjects } if ($next -eq "stop") { write-Slog "$samaccountname" "LOG" "Ready" } } } function New-EMTargetGALGroup() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountName, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { try { $t = 300 $path = $Script:ModuleTargetGALSyncOU New-ADGroup -SamAccountName $samaccountname -Path $path -Name $samaccountname -groupcategory distribution -groupscope universal -Server $targetpdc -credential $targetcred -ea stop write-Slog "$samaccountname" "OK" "GAL group object created in target domain '$targetdomain'" write-Slog "$samaccountname" "LOG" "Waiting for group object to be ready in target domain '$targetdomain'. Waiting up to $t seconds" $n = 0 while ($n -lt $t) { $eguid = $null; $eguid = $(try{(get-adobject -server $targetpdc -filter {samaccountname -eq $samaccountname} -properties objectguid -credential $targetcred).objectguid.guid}catch{}) if ($eguid) { if ($($eguid | measure).count -gt 1) {write-Slog "$samaccountname" "WARN" "Multiple objects found in target domain '$targetdomain'";throw} if ($(try {invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-group ""$eguid"" -domaincontroller ""$targetpdc"""}catch{})) { Start-EMProcessDistributionGroup -SourceDomain $sourcedomain -TargetDomain $targetdomain -Samaccountname $samaccountname -SourceCred $SourceCred -TargetCred $TargetCred -Mode $Mode -Activity $activity -SourceEndPoint $SourceEndPoint -TargetEndPoint $TargetEndPoint -Separate $Separate break } else { sleep -s 1; $n++ } } else { sleep -s 1; $n++ } } if ($n -ge $t) { throw } } catch { write-Slog "$samaccountname" "ERR" "Problem creating GAL group object in target domain '$targetdomain'. $($_.exception.message)" } } } function Start-EMMailEnableTargetGroup() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountName, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceRoutingSMTP, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourcePrimarySMTP, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetType, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { #check group type if ($targettype -notmatch "^mailuniversal|^universal") { write-Slog "$samaccountname" "AR" "Group is not universal in target domain '$targetdomain'. Converting" try { $eguid = $null; $eguid = $target.objectguid.guid invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "set-group ""$eguid"" -universal -domaincontroller ""$($targetpdc)"" -ea stop" write-Slog "$samaccountname" "OK" "Converted group scope to universal" } catch { write-Slog "$samaccountname" "ERR" "Problem converting group scope to universal. $($_.exception.message)" } } $completed = $null $secondsmax = $null; $secondsmax = 300 $secondsinc = $null; $secondsinc = 30 $start = $null; $start = get-date write-Slog "$samaccountname" "LOG" "Waiting for object to be ready in target domain '$targetdomain'. Waiting up to $secondsmax seconds" Do { try { $eguid = $null; $eguid = $target.objectguid.guid $group = $null; $group = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-group ""$eguid"" -domaincontroller ""$($targetpdc)"" -ea stop" if ($($group | measure).count -gt 1) {write-Slog "$samaccountname" "WARN" "Multiple objects found in target domain '$targetdomain'. Unable to continue"} if ($group.grouptype -match "universal") { $completed = $true } } catch{sleep -s $secondsinc} if ((new-timespan -Start $start -End (get-date)).seconds -ge $secondsmax) { write-Slog "$samaccountname" "ERR" "Timeout. Unable to continue. $($_.exception.message)" } write-Slog "$samaccountname" "LOG" "Waited $([math]::round((new-timespan -Start $start -End (get-date)).totalseconds)) seconds" } while (!($completed)) #mail enable try { $eguid = $null; $eguid = $target.objectguid.guid if (($eguid | measure).count -eq 1) { invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "enable-distributiongroup -identity ""$eguid"" -domaincontroller ""$($targetpdc)"" -ea stop" | out-null } else { throw } } catch { write-Slog "$samaccountname" "ERR" "Problem mail enabling in target domain '$targetdomain'. $($_.exception.message)" } $completed = $null $start = $null; $start = get-date write-Slog "$samaccountname" "LOG" "Waiting for mail enabled object to be ready in target domain '$targetdomain'. Waiting up to $secondsmax seconds" Do { $invresult = $null try { $invresult = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-distributiongroup -identity ""$eguid"" -domaincontroller ""$($targetpdc)"" -ea stop" | out-null $completed = $true } catch{sleep -s $secondsinc} if ($($invresult | measure).count -gt 1) {write-Slog "$samaccountname" "ERR" "Multiple objects found in target domain '$targetdomain'. Unable to continue. $($_.exception.message)"} if ((new-timespan -Start $start -End (get-date)).seconds -ge $secondsmax) { write-Slog "$samaccountname" "ERR" "Timeout mail enabling. Unable to continue. $($_.exception.message)" } write-Slog "$samaccountname" "LOG" "Waited $([math]::round((new-timespan -Start $start -End (get-date)).totalseconds)) seconds" } while (!($completed)) if ($completed) { write-Slog "$samaccountname" "OK" "mail enabled OK in target domain '$targetdomain'" Start-EMProcessDistributionGroup -SourceDomain $sourcedomain -TargetDomain $targetdomain -Samaccountname $samaccountname -SourceCred $SourceCred -TargetCred $TargetCred -Mode $Mode -Activity $activity -SourceEndPoint $SourceEndPoint -TargetEndPoint $TargetEndPoint -Separate $Separate } } } function Start-EMPrepareGroupObjects() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SamAccountName, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceRoutingSMTP, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { #calculate target routing SMTP address try { $targetRoutingSMTP = $null; $targetRoutingSMTP = $("$($Source.mailnickname)@mail.on$($targetdomain)").tolower() } catch { write-Slog "$samaccountname" "ERR" "Problem preparing target routing SMTP address. $($_.exception.message)" } #calculate source routing X500 address try { $sourceRoutingX500 = $null; $sourceRoutingX500 = $("X500:" + $source.legacyexchangedn) } catch { write-Slog "$samaccountname" "ERR" "Problem preparing source routing X500 address. $($_.exception.message)" } #calculate target routing X500 address try { $targetRoutingX500 = $null; $targetRoutingX500 = $("X500:" + $target.legacyexchangedn) } catch { write-Slog "$samaccountname" "ERR" "Problem preparing target routing X500 address. $($_.exception.message)" } #direction $primaryobj = $source $secondaryobj = $target $primarynbdomain = $null; $primarynbdomain = $sourcenbdomain $secondarynbdomain = $null; $secondarynbdomain = $targetnbdomain $primarydomain = $null; $primarydomain = $sourcedomain $secondarydomain = $null; $secondarydomain = $targetdomain $primaryendpoint = $null; $primaryendpoint = $sourceendpoint $secondaryendpoint = $null; $secondaryendpoint = $targetendpoint $primarypdc = $null; $primarypdc = $sourcepdc $secondarypdc = $null; $secondarypdc = $targetpdc $primarycred = $null; $primarycred = $sourcecred $secondarycred = $null; $secondarycred = $targetcred $primaryroutingsmtp = $null; $primaryroutingsmtp = $sourceroutingsmtp $secondaryroutingsmtp = $null; $secondaryroutingsmtp = $targetroutingsmtp $primaryroutingx500 = $null; $primaryroutingx500 = $sourceRoutingX500 $secondaryroutingx500 = $null; $secondaryroutingx500 = $targetRoutingX500 $primarytype = $null; $primarytype = $sourcetype $secondarytype = $null; $secondarytype = $targettype $PrimaryGALSyncOU = $null; $PrimaryGALSyncOU = $Script:ModuleSourceGALSyncOU $SecondaryGALSyncOU = $null; $SecondaryGALSyncOU = $Script:ModuleTargetGALSyncOU $pupdate = $false $supdate = $false #displayname if ($($primaryobj.displayname) -ne $($secondaryobj.displayname)) { write-Slog "$samaccountname" "AR" "Secondary displayname attr update required: $($primaryobj.displayname)" $secondaryobj.displayname = $primaryobj.displayname $supdate = $true } if ($($primaryobj.mail) -ne $($secondaryobj.mail)) { write-Slog "$samaccountname" "AR" "Secondary mail attr update required: $($primaryobj.mail)" $secondaryobj.mail = $primaryobj.mail $supdate = $true } #mailnickname if ($($primaryobj.mailnickname) -ne $($secondaryobj.mailnickname)) { write-Slog "$samaccountname" "AR" "Secondary mailnickname attr update required: $($primaryobj.mailnickname)" $secondaryobj.mailnickname = $primaryobj.mailnickname $supdate = $true } #textEncodedORAddress if ($($primaryobj.textEncodedORAddress) -ne $($secondaryobj.textEncodedORAddress)) { write-Slog "$samaccountname" "AR" "Secondary textEncodedORAddress attr update required" $secondaryobj.textEncodedORAddress = $primaryobj.textEncodedORAddress $supdate = $true } #disable sender auth requirement if ($($primaryobj.msExchRequireAuthToSendTo) -eq $true) { write-Slog "$samaccountname" "AR" "Primary msExchRequireAuthToSendTo attr update required" $primaryobj.msExchRequireAuthToSendTo = $false $pupdate = $true } if ($($secondaryobj.msExchRequireAuthToSendTo) -eq $true) { write-Slog "$samaccountname" "AR" "Secondary msExchRequireAuthToSendTo attr update required" $secondaryobj.msExchRequireAuthToSendTo = $false $supdate = $true } #msExchHideFromAddressLists and msExchSenderHintTranslations if ($activity -eq "migrate") { if ($($primaryobj.msExchHideFromAddressLists) -ne $($secondaryobj.msExchHideFromAddressLists)) { write-Slog "$samaccountname" "AR" "Secondary msExchHideFromAddressLists attr update required" $secondaryobj.msExchHideFromAddressLists = $primaryobj.msExchHideFromAddressLists $supdate = $true } if ($($primaryobj.msExchSenderHintTranslations) -ne $($secondaryobj.msExchSenderHintTranslations)) { write-Slog "$samaccountname" "AR" "Secondary msExchSenderHintTranslations attr update required" $secondaryobj.msExchSenderHintTranslations = $primaryobj.msExchSenderHintTranslations $supdate = $true } } if ($activity -eq "galsync") { if ($($secondaryobj.msExchHideFromAddressLists) -ne $true) { write-Slog "$samaccountname" "AR" "Secondary msExchHideFromAddressLists attr update required" $secondaryobj.msExchHideFromAddressLists = $true $supdate = $true } $tip = $null; $tip = "default:<html>`n<body>`nPlease be aware this is a distribution group for external recipients.`n</body>`n</html>`n" if (($($secondaryobj.msExchSenderHintTranslations) -ne $tip) -or ($($secondaryobj.msExchSenderHintTranslations) -eq $null)) { write-Slog "$samaccountname" "AR" "Secondary msExchSenderHintTranslations attr update required" $secondaryobj.msExchSenderHintTranslations = $tip $supdate = $true } } #extensionAttribute1 if ($($primaryobj.extensionAttribute1) -ne $($secondaryobj.extensionAttribute1)) { write-Slog "$samaccountname" "AR" "extensionAttribute1 attr update required" $secondaryobj.extensionAttribute1 = $primaryobj.extensionAttribute1 $supdate = $true } #extensionAttribute2 if ($($primaryobj.extensionAttribute2) -ne $($secondaryobj.extensionAttribute2)) { write-Slog "$samaccountname" "AR" "extensionAttribute2 attr update required" $secondaryobj.extensionAttribute2 = $primaryobj.extensionAttribute2 $supdate = $true } #extensionAttribute3 if ($($primaryobj.extensionAttribute3) -ne $($secondaryobj.extensionAttribute3)) { write-Slog "$samaccountname" "AR" "extensionAttribute3 attr update required" $secondaryobj.extensionAttribute3 = $primaryobj.extensionAttribute3 $supdate = $true } #extensionAttribute4 if ($($primaryobj.extensionAttribute4) -ne $($secondaryobj.extensionAttribute4)) { write-Slog "$samaccountname" "AR" "extensionAttribute4 attr update required" $secondaryobj.extensionAttribute4 = $primaryobj.extensionAttribute4 $supdate = $true } #extensionAttribute5 if ($($primaryobj.extensionAttribute5) -ne $($secondaryobj.extensionAttribute5)) { write-Slog "$samaccountname" "AR" "extensionAttribute5 attr update required" $secondaryobj.extensionAttribute5 = $primaryobj.extensionAttribute5 $supdate = $true } #extensionAttribute6 if ($($primaryobj.extensionAttribute6) -ne $($secondaryobj.extensionAttribute6)) { write-Slog "$samaccountname" "AR" "extensionAttribute6 attr update required" $secondaryobj.extensionAttribute6 = $primaryobj.extensionAttribute6 $supdate = $true } #extensionAttribute7 if ($($primaryobj.extensionAttribute7) -ne $($secondaryobj.extensionAttribute7)) { write-Slog "$samaccountname" "AR" "extensionAttribute7 attr update required" $secondaryobj.extensionAttribute7 = $primaryobj.extensionAttribute7 $supdate = $true } #extensionAttribute8 if ($($primaryobj.extensionAttribute8) -ne $($secondaryobj.extensionAttribute8)) { write-Slog "$samaccountname" "AR" "extensionAttribute8 attr update required" $secondaryobj.extensionAttribute8 = $primaryobj.extensionAttribute8 $supdate = $true } #extensionAttribute9 if ($($primaryobj.extensionAttribute9) -ne $($secondaryobj.extensionAttribute9)) { write-Slog "$samaccountname" "AR" "extensionAttribute9 attr update required" $secondaryobj.extensionAttribute9 = $primaryobj.extensionAttribute9 $supdate = $true } #extensionAttribute10 if ($($primaryobj.extensionAttribute10) -ne $($secondaryobj.extensionAttribute10)) { write-Slog "$samaccountname" "AR" "extensionAttribute10 attr update required" $secondaryobj.extensionAttribute10 = $primaryobj.extensionAttribute10 $supdate = $true } #extensionAttribute11 if ($($primaryobj.extensionAttribute11) -ne $($secondaryobj.extensionAttribute11)) { write-Slog "$samaccountname" "AR" "extensionAttribute11 attr update required" $secondaryobj.extensionAttribute11 = $primaryobj.extensionAttribute11 $supdate = $true } #extensionAttribute12 if ($($primaryobj.extensionAttribute12) -ne $($secondaryobj.extensionAttribute12)) { write-Slog "$samaccountname" "AR" "extensionAttribute12 attr update required" $secondaryobj.extensionAttribute12 = $primaryobj.extensionAttribute12 $supdate = $true } #extensionAttribute13 if ($($primaryobj.extensionAttribute13) -ne $($secondaryobj.extensionAttribute13)) { write-Slog "$samaccountname" "AR" "extensionAttribute13 attr update required" $secondaryobj.extensionAttribute13 = $primaryobj.extensionAttribute13 $supdate = $true } #extensionAttribute14 if ($($primaryobj.extensionAttribute14) -ne $($secondaryobj.extensionAttribute14)) { if ($secondaryobj.extensionAttribute14 -cmatch "^ID.*/CF:$" -and $secondaryobj.extensionAttribute15 -cmatch "^\w{32}$") { write-Slog "$samaccountname" "WARN" "Secondary extensionAttribute14 used by QMM and will be ignored" } else { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute14 attr update required" $secondaryobj.extensionAttribute14 = $primaryobj.extensionAttribute14 $supdate = $true } } #extensionAttribute15 if ($($primaryobj.extensionAttribute15) -ne $($secondaryobj.extensionAttribute15)) { if ($secondaryobj.extensionAttribute14 -cmatch "^ID.*/CF:$" -and $secondaryobj.extensionAttribute15 -cmatch "^\w{32}$") { write-Slog "$samaccountname" "WARN" "Secondary extensionAttribute15 used by QMM and will be ignored" } else { write-Slog "$samaccountname" "AR" "Secondary extensionAttribute15 attr update required" $secondaryobj.extensionAttribute15 = $primaryobj.extensionAttribute15 $supdate = $true } } #authOrig (users allowed to send to distribution group) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute authOrig -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.authOrig -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.authOrig) $($sdns)}catch{})) -or ($($secondaryobj.authOrig) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary authOrig attr update required" $secondaryobj.authOrig = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary $Attribute attr. $($_.exception.message)" } } #unauthOrig (users not allowed to send to the distribution group) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute unauthOrig -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.unauthOrig -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.unauthOrig) $($sdns)}catch{})) -or ($($secondaryobj.unauthOrig) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary unauthOrig attr update required" $secondaryobj.unauthOrig = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary $Attribute attr. $($_.exception.message)" } } #dLMemSubmitPerms (groups allowed to send to distribution group) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute dLMemSubmitPerms -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.dLMemSubmitPerms -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.dLMemSubmitPerms) $($sdns)}catch{})) -or ($($secondaryobj.dLMemSubmitPerms) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary dLMemSubmitPerms attr update required" $secondaryobj.dLMemSubmitPerms = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary $Attribute attr. $($_.exception.message)" } } #dLMemRejectPerms (groups not allowed to send to distribution group) $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute dLMemRejectPerms -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.dLMemRejectPerms -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.dLMemRejectPerms) $($sdns)}catch{})) -or ($($secondaryobj.dLMemRejectPerms) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary dLMemRejectPerms attr update required" $secondaryobj.dLMemRejectPerms = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary $Attribute attr. $($_.exception.message)" } } #managedby and msExchCoManagedByLink $primaryallmanagers = @() $primaryobj.managedby | % {$primaryallmanagers += $_} $primaryobj.msExchCoManagedByLink | % {$primaryallmanagers += $_} $primaryallmanagers = $primaryallmanagers | sort | get-unique $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute 'managedBy msExchCoManagedByLink'-PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryallmanagers -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc try { if (($sdns | measure).count -eq 0) { if ($($secondaryobj.managedBy) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary managedBy attr update required" $secondaryobj.managedBy = $null $supdate = $true } if ($($secondaryobj.msExchCoManagedByLink) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary msExchCoManagedByLink attr update required" $secondaryobj.msExchCoManagedByLink = $null $supdate = $true } } if (($sdns | measure).count -eq 1) { if ($($secondaryobj.managedBy) -ne $sdns) { write-Slog "$samaccountname" "AR" "Secondary managedBy attr update required" $secondaryobj.managedBy = $sdns $supdate = $true } if ($($secondaryobj.msExchCoManagedByLink) -ne $null) { write-Slog "$samaccountname" "AR" "Secondary msExchCoManagedByLink attr update required" $secondaryobj.msExchCoManagedByLink = $null $supdate = $true } } if (($sdns | measure).count -gt 1) { if ($($secondaryobj.managedBy) -ne $sdns[0]) { write-Slog "$samaccountname" "AR" "Secondary managedBy attr update required" $secondaryobj.managedBy = $sdns[0] $supdate = $true } $sdns = $sdns | ? {$_ -ne $sdns[0]} | % {$_.tostring()} if (($(try{compare-object $($secondaryobj.msExchCoManagedByLink) $($sdns)}catch{})) -or ($($secondaryobj.msExchCoManagedByLink) -xor $($sdns))) { write-Slog "$samaccountname" "AR" "Secondary msExchCoManagedByLink attr update required" $secondaryobj.msExchCoManagedByLink = $sdns $supdate = $true } } } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary managedBy or msExchCoManagedByLink attr. $($_.exception.message)" } #msExchGroupJoinRestriction if ($($primaryobj.msExchGroupJoinRestriction) -ne $($secondaryobj.msExchGroupJoinRestriction)) { write-Slog "$samaccountname" "AR" "Secondary msExchGroupJoinRestriction attr update required" $secondaryobj.msExchGroupJoinRestriction = $primaryobj.msExchGroupJoinRestriction $supdate = $true } #msExchGroupDepartRestriction if ($($primaryobj.msExchGroupDepartRestriction) -ne $($secondaryobj.msExchGroupDepartRestriction)) { write-Slog "$samaccountname" "AR" "Secondary msExchGroupDepartRestriction attr update required" $secondaryobj.msExchGroupDepartRestriction = $primaryobj.msExchGroupDepartRestriction $supdate = $true } #msExchEnableModeration if ($($primaryobj.msExchEnableModeration) -ne $($secondaryobj.msExchEnableModeration)) { write-Slog "$samaccountname" "AR" "Secondary msExchEnableModeration attr update required" $secondaryobj.msExchEnableModeration = $primaryobj.msExchEnableModeration $supdate = $true } #msExchModerationFlags if ($($primaryobj.msExchModerationFlags) -ne $($secondaryobj.msExchModerationFlags)) { write-Slog "$samaccountname" "AR" "Secondary msExchModerationFlags attr update required" $secondaryobj.msExchModerationFlags = $primaryobj.msExchModerationFlags $supdate = $true } #msExchModeratedByLink $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute msExchModeratedByLink -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.msExchModeratedByLink -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.msExchModeratedByLink) $($sdns)}catch{})) -or ($($secondaryobj.msExchModeratedByLink) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary msExchModeratedByLink attr update required" $secondaryobj.msExchModeratedByLink = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary msExchModeratedByLink attr. $($_.exception.message)" } } #msExchBypassModerationLink $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute msExchBypassModerationLink -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.msExchBypassModerationLink -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.msExchBypassModerationLink) $($sdns)}catch{})) -or ($($secondaryobj.msExchBypassModerationLink) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary msExchBypassModerationLink attr update required" $secondaryobj.msExchBypassModerationLink = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary msExchBypassModerationLink attr. $($_.exception.message)" } } #publicdelegates $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $samaccountname -Attribute publicdelegates -PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryobj.publicdelegates -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc if (($(try{compare-object $($secondaryobj.publicdelegates) $($sdns)}catch{})) -or ($($secondaryobj.publicdelegates) -xor $($sdns))) { try { write-Slog "$samaccountname" "AR" "Secondary publicdelegates attr update required" $secondaryobj.publicdelegates = $sdns $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary publicdelegates attr. $($_.exception.message)" } } #PROXYADDRESSES $pproxsticky = $null; $pproxsticky = $primaryobj.proxyaddresses -notmatch "^smtp:|^x500:" $sproxsticky = $null; $sproxsticky = $secondaryobj.proxyaddresses -notmatch "^smtp:|^x500" $x500sticky = $null; $x500sticky = $primaryobj.proxyaddresses -match "^x500:"; $x500sticky += $secondaryobj.proxyaddresses -match "^x500:" $x500sticky = $x500sticky | sort -Unique $pprox = $primaryobj.proxyaddresses -match "^smtp:" $sprox = $pprox #primary #smtp if ($pprox -notcontains $("smtp:" + $primaryRoutingSMTP)) { $pprox += $("smtp:" + $primaryRoutingSMTP) } #nonsmtp $pproxsticky | % {$pprox += $_} $x500sticky | % {$pprox += $_} if ($pprox -notcontains $secondaryRoutingX500) { $pprox += $secondaryRoutingX500 } #remove unwanted $pprox = $pprox -notmatch "mail\.on$($secondarydomain)$" $pprox = $pprox -notmatch [regex]::escape($primaryRoutingX500) #formatting $pprox = $pprox | sort -Unique $pproxarray = $null; $pproxarray = @(); $pprox | % {$pproxarray += ($_.tostring())} if ($(try{compare-object $($primaryobj.proxyaddresses) $($pproxarray)}catch{}) -or ($($primaryobj.proxyaddresses) -xor $($pproxarray))) { try { write-Slog "$samaccountname" "AR" "Primary proxyaddresses attr update required" $primaryobj.proxyaddresses = $pproxarray $pupdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing primary proxyaddresses attr. $($_.exception.message)" } } #secondary #smtp if ($sprox -notcontains $("smtp:" + $secondaryRoutingSMTP)) { $sprox += $("smtp:" + $secondaryRoutingSMTP) } #nonsmtp $sproxsticky | % {$sprox += $_} $x500sticky | % {$sprox += $_} if ($sprox -notcontains $primaryRoutingX500) { $sprox += $primaryRoutingX500 } #remove unwanted $sprox = $sprox -notmatch "mail\.on$($primarydomain)$" $sprox = $sprox -notmatch [regex]::escape($secondaryRoutingX500) #formatting $sprox = $sprox | sort -Unique $sproxarray = $null; $sproxarray = @(); $sprox | % {$sproxarray += ($_.tostring())} if ($(try{compare-object $($secondaryobj.proxyaddresses) $($sproxarray)}catch{}) -or $($($secondaryobj.proxyaddresses) -xor $($sproxarray))) { try { write-Slog "$samaccountname" "AR" "Secondary proxyaddresses attr update required" $secondaryobj.proxyaddresses = $sproxarray $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary proxyaddresses attr. $($_.exception.message)" } } #groupType $groupType = $null; $groupType = Update-EMGroupType $($primaryobj.grouptype) if (!($grouptype)) { write-Slog "$samaccountname" "ERR" "groupType cannot be NULL. Unable to continue" } if ($($secondaryobj.grouptype) -ne $grouptype) { try { write-Slog "$samaccountname" "AR" "Secondary groupType attr update required" $secondaryobj.groupType = $grouptype $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary groupType attr. $($_.exception.message)" } } <# #groupscope if ($($secondaryobj.groupscope) -ne "universal") { try { write-Slog "$samaccountname" "AR" "Secondary groupscope attr update required" $secondaryobj.groupscope = "Universal" $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary group scope to universal. $($_.exception.message)" } } #groupcategory if ($($primaryobj.groupcategory) -ne $secondaryobj.groupcategory) { try { write-Slog "$samaccountname" "AR" "Secondary groupcategory attr update required" $secondaryobj.groupcategory = $primaryobj.groupcategory $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting group category. $($_.exception.message)" } } #> #types if ($($primaryobj.msexchrecipienttypedetails) -ne $($secondaryobj.msexchrecipienttypedetails)) { try { write-Slog "$samaccountname" "AR" "Secondary msExchRecipientTypeDetails attr update required" $secondaryobj.msexchrecipienttypedetails = $primaryobj.msexchrecipienttypedetails $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchRecipientTypeDetails attr. $($_.exception.message)" } } if ($($primaryobj.msexchrecipientdisplaytype) -ne $($secondaryobj.msexchrecipientdisplaytype)) { try { write-Slog "$samaccountname" "AR" "Secondary msExchRecipientDisplayType attr update required" $secondaryobj.msexchrecipientdisplaytype = $primaryobj.msexchrecipientdisplaytype $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchRecipientDisplayType attr. $($_.exception.message)" } } if ($($primaryobj.msExchRemoteRecipientType) -ne $($secondaryobj.msExchRemoteRecipientType)) { try { write-Slog "$samaccountname" "AR" "Secondary msExchRemoteRecipientType attr update required" $secondaryobj.msExchRemoteRecipientType = $primaryobj.msExchRemoteRecipientType $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchRemoteRecipientType attr. $($_.exception.message)" } } #maximum size restrictions if ($($primaryobj.delivContLength) -ne $($secondaryobj.delivContLength)) { try { write-Slog "$samaccountname" "AR" "Secondary delivContLength attr update required" $secondaryobj.delivContLength = $primaryobj.delivContLength $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary delivContLength attr. $($_.exception.message)" } } #membership approval if ($($primaryobj.msExchGroupJoinRestriction) -ne $($secondaryobj.msExchGroupJoinRestriction)) { try { write-Slog "$samaccountname" "AR" "Secondary msExchGroupJoinRestriction attr update required" $secondaryobj.msExchGroupJoinRestriction = $primaryobj.msExchGroupJoinRestriction $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchGroupJoinRestriction attr. $($_.exception.message)" } } if ($($primaryobj.msExchGroupDepartRestriction) -ne $($secondaryobj.msExchGroupDepartRestriction)) { try { write-Slog "$samaccountname" "AR" "Secondary msExchGroupDepartRestriction attr update required" $secondaryobj.msExchGroupDepartRestriction = $primaryobj.msExchGroupDepartRestriction $supdate = $true } catch { write-Slog "$samaccountname" "ERR" "Problem setting secondary msExchGroupDepartRestriction attr. $($_.exception.message)" } } #msExchPoliciesExcluded msExchPoliciesIncluded if ($mode -eq "prepare") { if ($primaryobj.msExchPoliciesExcluded -notcontains '{26491cfc-9e50-4857-861b-0cb8df22b5d7}') { write-Slog "$samaccountname" "AR" "Primary msExchPoliciesExcluded attr update required" $primaryobj.msExchPoliciesExcluded = '{26491cfc-9e50-4857-861b-0cb8df22b5d7}' $pupdate = $true } if ($primaryobj.msExchPoliciesIncluded -ne $null) { write-Slog "$samaccountname" "AR" "Primary msExchPoliciesIncluded attr update required" $primaryobj.msExchPoliciesIncluded = $null $pupdate = $true } if ($secondaryobj.msExchPoliciesExcluded -notcontains '{26491cfc-9e50-4857-861b-0cb8df22b5d7}') { write-Slog "$samaccountname" "AR" "Secondary msExchPoliciesExcluded attr update required" $secondaryobj.msExchPoliciesExcluded = '{26491cfc-9e50-4857-861b-0cb8df22b5d7}' $supdate = $true } if ($secondaryobj.msExchPoliciesIncluded -ne $null) { write-Slog "$samaccountname" "AR" "Secondary msExchPoliciesIncluded attr update required" $secondaryobj.msExchPoliciesIncluded = $null $supdate = $true } } #commit changes if ($Mode -eq "Prepare") { try { if ($pupdate -eq $true) { set-adobject -instance $primaryobj -server $primarypdc -Credential $primaryCred -ea stop write-Slog "$samaccountname" "OK" "Primary group prepared in domain '$primarydomain'" } } catch { write-Slog "$samaccountname" "ERR" "Problem preparing primary group in domain '$primarydomain'. $($_.exception.message)" } try { if ($supdate -eq $true) { set-adobject -instance $secondaryobj -server $secondarypdc -Credential $secondaryCred -ea stop write-Slog "$samaccountname" "OK" "Secondary group prepared in domain '$secondarydomain'" } } catch { write-Slog "$samaccountname" "ERR" "Problem preparing secondary group in domain '$secondarydomain'. $($_.exception.message)" } } #OU on GALSync if ($activity -eq "GalSync"){ if ($Secondaryobj.distinguishedname -notmatch $SecondaryGALSyncOU ) { write-Slog "$samaccountname" "AR" "Moving secondary to GALSync OU '$SecondaryGALSyncOU'" try { Move-ADObject -Identity $($Secondaryobj.objectguid.guid) -TargetPath $SecondaryGALSyncOU -Server $secondarypdc -Credential $secondarycred -ea Stop write-Slog "$samaccountname" "LOG" "Moved secondary to GALSync OU '$SecondaryGALSyncOU'" } catch { write-Slog "$samaccountname" "ERR" "Problem moving secondary to GALSync OU '$SecondaryGALSyncOU'. $($_.exception.message)" } } } #send-as permissions if ($activity -eq "migrate"){ write-Slog "$samaccountname" "LOG" "Checking send-as permissions on primary" try { $primaryperms = $null; $primaryperms = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" | ? {$_.isinherited -eq $false -and $_.user -ne 'NT AUTHORITY\SELF' -and $_.extendedrights -match "send-as" -and $_.deny -eq $false} } catch { write-Slog "$samaccountname" "ERR" "Issue checking send-as permissions on primary. $($_.exception.message)" } write-Slog "$samaccountname" "LOG" "Checking send-as permissions on secondary" try { $secondaryperms = $null; $secondaryperms = invoke-emexchangecommand -endpoint $secondaryendpoint -domaincontroller $secondarypdc -credential $secondarycred -command "get-adpermission -identity ""$($secondaryobj.objectguid.guid)"" -domaincontroller ""$($secondarypdc)"" -ea stop" | ? {$_.isinherited -eq $false -and $_.user -ne 'NT AUTHORITY\SELF' -and $_.extendedrights -match "send-as" -and $_.deny -eq $false} } catch { write-Slog "$samaccountname" "ERR" "Issue checking send-as permissions on secondary. $($_.exception.message)" } #formatting $primaryusers = $null; $primaryusers = @() $primaryperms | % {$primaryusers += $_.user.tostring()} $secondaryusers = $null; $secondaryusers = @() $secondaryperms | % {$secondaryusers += $_.user.tostring()} #hydrate $sendasperms = $null; $sendasperms = @() foreach ($user in $primaryusers) { if ($user -match "^($primarynbdomain)\\|^($secondarynbdomain)\\") { $sam = $null; $sam = $user -replace ($primarynbdomain + "\\"),"" $sam = $sam -replace ($secondarynbdomain + "\\"),"" $sendasperms += $("$primarynbdomain\$sam") $sendasperms += $("$secondarynbdomain\$sam") } } $sendasperms = $sendasperms | sort | Get-Unique #additions foreach ($perm in $sendasperms){ if ($primaryusers -notcontains $perm) { write-Slog "$samaccountname" "AR" "'$($perm)' send-as missing on primary" try { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "add-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -user ""$($perm)"" -accessrights extendedright -extendedrights send-as -WarningAction 0" | out-null write-Slog "$samaccountname" "OK" "'$($perm)' send-as added to primary" } catch { write-Slog "$samaccountname" "WARN" "'$($perm)' issue adding send-as permission to primary and will be excluded" } } if ($secondaryusers -notcontains $perm) { write-Slog "$samaccountname" "AR" "'$($perm)' send-as missing on secondary" try { invoke-emexchangecommand -endpoint $secondaryendpoint -domaincontroller $secondarypdc -credential $secondarycred -command "add-adpermission -identity ""$($secondaryobj.objectguid.guid)"" -domaincontroller ""$($secondarypdc)"" -user ""$($perm)"" -accessrights extendedright -extendedrights send-as -WarningAction 0" | out-null write-Slog "$samaccountname" "OK" "'$($perm)' send-as added to secondary" } catch { write-Slog "$samaccountname" "WARN" "'$($perm)' issue adding send-as permission to secondary and will be excluded" } } } } #send-as permissions on galsync if ($activity -eq "galsync") { write-Slog "$samaccountname" "LOG" "Checking send-as permissions on primary" try { $primaryperms = $null; $primaryperms = invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command "get-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -ea stop" | ? {$_.isinherited -eq $false -and $_.user -ne 'NT AUTHORITY\SELF' -and $_.extendedrights -match "send-as" -and $_.deny -eq $false} } catch { write-Slog "$samaccountname" "ERR" "Issue checking send-as permissions on primary. $($_.exception.message)" } write-Slog "$samaccountname" "LOG" "Checking send-as permissions on secondary" try { $secondaryperms = $null; $secondaryperms = invoke-emexchangecommand -endpoint $secondaryendpoint -domaincontroller $secondarypdc -credential $secondarycred -command "get-adpermission -identity ""$($secondaryobj.objectguid.guid)"" -domaincontroller ""$($secondarypdc)"" -ea stop" | ? {$_.isinherited -eq $false -and $_.user -ne 'NT AUTHORITY\SELF' -and $_.extendedrights -match "send-as" -and $_.deny -eq $false} } catch { write-Slog "$samaccountname" "ERR" "Issue checking send-as permissions on secondary. $($_.exception.message)" } #formatting $primaryusers = $null; $primaryusers = @() $primaryperms | % {$primaryusers += $_.user.tostring()} $primaryusers = $primaryusers | sort | get-unique $secondaryusers = $null; $secondaryusers = @() $secondaryperms | % {$secondaryusers += $_.user.tostring()} $secondaryusers = $secondaryusers | sort | get-unique #remove opposite permissions foreach($perm in $primaryusers) { if ($perm -match "^$($secondarynbdomain)\\") { write-Slog "$samaccountname" "AR" "'$($perm)' send-as removing from primary" try { invoke-emexchangecommand -endpoint $primaryendpoint -domaincontroller $primarypdc -credential $primarycred -command $("remove-adpermission -identity ""$($primaryobj.objectguid.guid)"" -domaincontroller ""$($primarypdc)"" -user ""$($perm)"" -accessrights extendedright -extendedrights send-as" + ' -confirm:$false') | out-null write-Slog "$samaccountname" "OK" "'$($perm)' send-as removed from primary" } catch { write-Slog "$samaccountname" "WARN" "'$($perm)' issue removing send-as permission from primary and will be excluded" } } } foreach($perm in $secondaryusers) { if ($perm -match "^$($primarynbdomain)\\") { write-Slog "$samaccountname" "AR" "'$($perm)' send-as removing from secondary" try { invoke-emexchangecommand -endpoint $secondaryendpoint -domaincontroller $secondarypdc -credential $secondarycred -command $("remove-adpermission -identity ""$($secondaryobj.objectguid.guid)"" -domaincontroller ""$($secondarypdc)"" -user ""$($perm)"" -accessrights extendedright -extendedrights send-as" + ' -confirm:$false') | out-null write-Slog "$samaccountname" "OK" "'$($perm)' send-as removed from secondary" } catch { write-Slog "$samaccountname" "WARN" "'$($perm)' issue removing send-as permission from secondary and will be excluded" } } } } write-Slog "$samaccountname" "LOG" "Ready" } } # Contacts ################################################################################################################ function Start-EMProcessContact() { <# .SYNOPSIS Process a contact. .DESCRIPTION This cmdlet is used to prepare and migrate a contact from the source to the target Exchange Organization. .PARAMETER Alias This is the mailnickname attribute of the contact you want the cmdlet to process. .PARAMETER SourceCred Specify the source credentials of the source domain. .PARAMETER TargetCred Specify the target credentials of the target domain. .PARAMETER SourceDomain Specify the source domain. .PARAMETER TargetDomain Specify the target domain. .PARAMETER Activity Specify whether you want to MIGRATE or GALSYNC. .PARAMETER Mode Specify whether you want to PREPARE or LOGONLY. .PARAMETER SourceEndPoint Specify the source end point for the source Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER TargetEndPoint Specify the target end point for the target Exchange Organization operations. This could be a specific Exchange server, client access array, or load balanced name. .PARAMETER Separate Specifies whether to break the cross-forest relationship and apply limitations to the secondary object. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Alias = $null, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity = $Script:ModuleActivity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode = $Script:ModuleMode, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint = $Script:ModuleTargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { #formatting $sourcedomain = $sourcedomain.toupper() $targetdomain = $targetdomain.toupper() $sourceendpoint = $sourceendpoint.toupper() $targetendpoint = $targetendpoint.toupper() $activity = $activity.toupper() $Mode = $Mode.toupper() write-Slog "$alias" "GO" "$activity mail contact" write-Slog "$alias" "LOG" "SourceCred: $($Sourcecred.username); TargetCred: $($TargetCred.username)" write-Slog "$alias" "LOG" "SourceDomain: $sourcedomain; TargetDomain: $targetdomain; Activity: $Activity; Mode: $Mode; SourceEndPoint: $SourceEndPoint; TargetEndPoint: $TargetEndPoint; Separate: $Separate;" if ($separate) { write-Slog "$alias" "WARN" "Separate parameter not in use and will be ignored" } #get source data try { try { $sourcepdc = $Script:ModuleSourcePDC $sourcedomainsid = $Script:ModuleSourceDomainSID $sourcenbdomain = $Script:ModuleSourceNBDomain $sourcedn = $script:ModuleSourceDN } catch { write-Slog "$alias" "ERR" "Issue getting domain information for source domain '$sourcedomain'. $($_.exception.message)" } $smec = $null; $smec = get-adobject -server $sourcepdc -filter {mailnickname -eq $alias -and objectclass -eq "contact"} -properties * -credential $sourcecred -ea stop } catch { write-Slog "$alias" "ERR" "Issue getting mail enabled contact from '$sourcedomain'. $($_.exception.message)" } if (!($smec)) { write-Slog "$alias" "ERR" "No mail enabled contact object found in source domain '$sourcedomain'. $($_.exception.message)" } else { try { $SourceType = $null; $SourceType = invoke-emexchangecommand -endpoint $sourceendpoint -domaincontroller $sourcepdc -credential $sourcecred -command "get-contact -identity ""$($smec.objectguid.guid)"" -domaincontroller ""$($sourcepdc)"" -ea stop" | select -expandproperty recipienttypedetails } catch { write-Slog "$alias" "ERR" "Issue getting source type from '$sourcedomain'. $($_.exception.message)" } } if ((($smec | measure).count) -gt 1) { write-Slog "$alias" "ERR" "Duplicate mailnicknames detected in source domain '$($sourcedomain)'. $($_.exception.message)" } if (($smec | measure).count -eq 1) { if (!(Read-EMBackUp $($smec.objectguid.guid))) { Write-EMBackUp $smec } } #get target data try { try { $targetpdc = $null; $targetdomainsid = $null; $targetNBdomain = $null get-addomain -Server $targetdomain -credential $targetcred -ea stop | % { $targetpdc = $_.pdcemulator $targetdomainsid = $_.domainsid.value $targetnbdomain = $_.netbiosname.tostring() $targetdn = $_.distinguishedname } } catch { write-Slog "$alias" "ERR" "Issue getting domain information for target domain '$targetdomain'. $($_.exception.message)" } $tmec = $null; $tmec = get-adobject -server $targetpdc -filter {mailnickname -eq $alias -and objectclass -eq "contact"} -properties * -credential $targetcred -ea stop } catch { write-Slog "$alias" "ERR" "Issues getting contact from target domain '$targetdomain'. $($_.exception.message)" } if ((($tmec | measure).count) -gt 1) { write-Slog "$alias" "ERR" "Duplicate mailnicknames detected in target domain '$($targetdomain)'. $($_.exception.message)" } if (($tmec | measure).count -eq 1) { if (!(Read-EMBackUp $($tmec.objectguid.guid))) { Write-EMBackUp $tmec } } if ($tmec) { try { $TargetType = $null; $TargetType = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-contact -identity ""$($tmec.objectguid.guid)"" -domaincontroller ""$($targetpdc)"" -ea stop" | select -expandproperty recipienttypedetails } catch { write-Slog "$alias" "ERR" "Issue getting target type from '$targetdomain'. $($_.exception.message)" } } try { $detected = $null; $detected = Get-EMConflict -identity $alias -Source $smec -sourcepdc $sourcepdc -targetpdc $targetpdc -sourcedomain $sourcedomain -targetdomain $targetdomain -SourceCred $SourceCred -TargetCred $TargetCred -Targetendpoint $TargetEndPoint } catch { write-Slog "$alias" "ERR" "Issue detecting conflict in target domain '$($targetdomain)'. $($_.exception.message)" } if ($detected) { $detected | select samaccountname,distinguishedname,mailnickname,proxyaddresses | % { write-slog "$alias" "WARN" "Conflict $($_ | convertto-json -compress)" } write-Slog "$alias" "ERR" "SMTP, X500, or Alias conflict detected in target domain '$($targetdomain)'. $($_.exception.message)" } $mec = [pscustomobject]@{ Alias = $alias Activity = $Activity Source = $smec SourceType = $SourceType SourceDomain = $SourceDomain.toupper() SourceNBDomain = $sourcenbdomain SourcePDC = $SourcePDC.toupper() SourceDomainSID = $sourcedomainsid SourceDN = $sourcedn Target = $tmec TargetType = $TargetType TargetDomain = $TargetDomain TargetNBDomain = $targetnbdomain TargetPDC = $TargetPDC.toupper() TargetDomainSID = $targetdomainsid TargetDN = $targetdn TargetCred = $TargetCred SourceCred = $SourceCred Mode = $Mode SourceEndPoint = $SourceEndPoint TargetEndPoint = $TargetEndPoint Separate = $Separate } write-Slog "$alias" "LOG" "SourceType: $($sourcetype); SourcePDC: $($mec.sourcepdc); TargetType: $($targettype); TargetPDC: $($targetpdc)" $mec | Start-EMContactPrep } } function Start-EMContactPrep() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Alias, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourcePDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDN, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetType, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDN, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { #determine action $next = $null $primary = $null if ($source -eq $null) { write-Slog "$alias" "ERR" "Not found in source domain '$sourcedomain'. Unable to continue. $($_.exception.message)" $next = "Stop" } else { #Migration activity handling if ($Activity -eq "migrate" -and !($target)) { write-Slog "$alias" "AR" "MIGRATE target contact object to be created" $next = "CreateTargetGALContact" } if ($Activity -eq "migrate" -and $target) { if ($target.distinguishedname -match $Script:ModuleTargetGALSyncOU) { write-Slog "$alias" "WARN" "MIGRATE contact object located in '$Script:ModuleTargetGALSyncOU'" } } #GALsync activity handling if ($activity -eq "galsync" -and !($target)) { write-Slog "$alias" "AR" "Target contact object to be created in '$Script:ModuleTargetGALSyncOU'" $next = "CreateTargetGALContact" } if ($activity -eq "galsync" -and $target) { if ($target.distinguishedname -notmatch $Script:ModuleTargetGALSyncOU) { write-Slog "$alias" "WARN" "GALSync target contact object not located in '$Script:ModuleTargetGALSyncOU'" } } #other if (!($next) -AND ($SourceType -match "^MailContact$" -and ($TargetType -match "^MailContact$"))) { $next = "PrepareSourceAndTarget" } } #calculate source SMTP addresses $sourcePrimarySMTP = $null; $sourcePrimarySMTP = ($source.proxyaddresses | ? {$_ -cmatch "^SMTP:"}) -replace "SMTP:","" if (($sourcePrimarySMTP | measure).count -gt 1) { write-Slog "$alias" "ERR" "Source has multiple primary SMTP addresses. Unable to continue. $($_.exception.message)" $next = "Stop" } if (($sourcePrimarySMTP | measure).count -eq 0) { write-Slog "$alias" "ERR" "Source has no primary SMTP address. Unable to continue. $($_.exception.message)" $next = "Stop" } $SourceRoutingSMTP = $null; $SourceRoutingSMTP = $("$($Source.mailnickname)@mail.on$($sourcedomain)").tolower() #prepare mode object $Modeobj = [pscustomobject]@{ Alias = $alias Source = $source SourceDomain = $SourceDomain SourceNBDomain = $SourceNBDomain SourceDN = $SourceDN SourcePDC = $SourcePDC Target = $target TargetDomain = $TargetDomain TargetNBDomain = $TargetNBDomain TargetDN = $TargetDN TargetPDC = $TargetPDC Mode = $Mode SourcePrimarySMTP = $SourcePrimarySMTP SourceRoutingSMTP = $SourceRoutingSMTP TargetCred = $TargetCred SourceCred = $SourceCred Activity = $Activity SourceEndPoint = $SourceEndPoint TargetEndPoint = $TargetEndPoint Sourcetype = $sourcetype Targettype = $targettype SourceDomainSID = $SourceDomainSID TargetDomainSID = $TargetDomainSID Separate = $Separate } if ($mode -eq "logonly"){ $next = "Stop" } #apply action if ($next -eq "CreateTargetGALContact") { if ($mode -eq "prepare") { $Modeobj | New-EMTargetGALContact } else { write-Slog "$alias" "LOG" "Not creating target GAL contact due to mode" } } if ($next -eq "PrepareSourceAndTarget") { $Modeobj | Start-EMPrepareContactObjects } if ($next -eq "stop") { write-Slog "$Alias" "LOG" "Ready" } } } function New-EMTargetGALContact() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Alias, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDN, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDN, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { try { $t = 300 $path = Get-EMTargetOU -Identity $Alias -Activity $Activity -Source $source -SourceDN $sourcedn -TargetCred $targetcred -TargetDomain $targetdomain -TargetDN $targetdn write-Slog "$alias" "LOG" "Using '$path'." invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "New-MailContact -Name ""$($source.cn)"" -ExternalEmailAddress ""$($alias + "@lvfs.net")"" -Alias ""$alias"" -OrganizationalUnit ""$path"" -domaincontroller ""$($targetpdc)""" | out-null write-Slog "$alias" "OK" "GAL contact object created in target domain '$targetdomain'" write-Slog "$alias" "LOG" "Waiting for contact object to be ready in target domain '$targetdomain'. Waiting up to $t seconds" $n = 0 while ($n -lt $t) { if ($(try {invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "Get-MailContact -identity ""$alias"" -domaincontroller ""$($targetpdc)"""}catch{})) { Start-EMProcessContact -SourceDomain $sourcedomain -TargetDomain $targetdomain -Alias $alias -SourceCred $SourceCred -TargetCred $TargetCred -Mode $Mode -Activity $activity -SourceEndPoint $SourceEndPoint -TargetEndPoint $TargetEndPoint -Separate $Separate break } else { sleep -s 1; $n++ } } if ($n -ge $t) { throw } } catch { write-Slog "$alias" "ERR" "Problem creating GAL contact object in target domain '$targetdomain'. $($_.exception.message)" } } } function Start-EMPrepareContactObjects() { #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Alias, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDN, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetType, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetNBDomain, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDN, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetDomainSID, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetPDC, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceRoutingSMTP, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Source, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][Microsoft.ActiveDirectory.Management.ADObject]$Target, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Prepare','LogOnly')][string]$Mode, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint, [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][ValidateSet('Migrate','GALSync')][string]$Activity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][boolean]$Separate= $Script:ModuleSeparate ) Process { #calcs #calculate target routing SMTP address try { $targetRoutingSMTP = $null; $targetRoutingSMTP = $("$($Source.mailnickname)@mail.on$($targetdomain)").tolower() } catch { write-Slog "$alias" "ERR" "Problem preparing target routing SMTP address. $($_.exception.message)" } #calculate source routing X500 address try { $sourceRoutingX500 = $null; $sourceRoutingX500 = $("X500:" + $source.legacyexchangedn) } catch { write-Slog "$alias" "ERR" "Problem preparing source routing X500 address. $($_.exception.message)" } #calculate target routing X500 address try { $targetRoutingX500 = $null; $targetRoutingX500 = $("X500:" + $target.legacyexchangedn) } catch { write-Slog "$alias" "ERR" "Problem preparing target routing X500 address. $($_.exception.message)" } #direction $primaryobj = $source $secondaryobj = $target $primarynbdomain = $null; $primarynbdomain = $sourcenbdomain $secondarynbdomain = $null; $secondarynbdomain = $targetnbdomain $primarydomain = $null; $primarydomain = $sourcedomain $secondarydomain = $null; $secondarydomain = $targetdomain $primaryendpoint = $null; $primaryendpoint = $sourceendpoint $secondaryendpoint = $null; $secondaryendpoint = $targetendpoint $primarypdc = $null; $primarypdc = $sourcepdc $secondarypdc = $null; $secondarypdc = $targetpdc $primarycred = $null; $primarycred = $sourcecred $secondarycred = $null; $secondarycred = $targetcred $primaryroutingsmtp = $null; $primaryroutingsmtp = $sourceroutingsmtp $secondaryroutingsmtp = $null; $secondaryroutingsmtp = $targetroutingsmtp $primaryroutingx500 = $null; $primaryroutingx500 = $sourceRoutingX500 $secondaryroutingx500 = $null; $secondaryroutingx500 = $targetRoutingX500 $primarytype = $null; $primarytype = $sourcetype $secondarytype = $null; $secondarytype = $targettype $PrimaryGALSyncOU = $null; $PrimaryGALSyncOU = $Script:ModuleSourceGALSyncOU $SecondaryGALSyncOU = $null; $SecondaryGALSyncOU = $Script:ModuleTargetGALSyncOU $PrimaryDN = $null; $PrimaryDN = $SourceDN $SecondaryDN = $null; $SecondaryDN = $TargetDN $pupdate = $false $supdate = $false #displayname if ($($primaryobj.displayname) -ne $($secondaryobj.displayname)) { write-Slog "$alias" "AR" "Secondary displayname attr update required: $($primaryobj.displayname)" $secondaryobj.displayname = $primaryobj.displayname $supdate = $true } if ($($primaryobj.mail) -ne $($secondaryobj.mail)) { write-Slog "$alias" "AR" "Secondary mail attr update required: $($primaryobj.mail)" $secondaryobj.mail = $primaryobj.mail $supdate = $true } #textEncodedORAddress if ($($primaryobj.textEncodedORAddress) -ne $($secondaryobj.textEncodedORAddress)) { write-Slog "$alias" "AR" "Secondary textEncodedORAddress attr update required" $secondaryobj.textEncodedORAddress = $primaryobj.textEncodedORAddress $supdate = $true } #disable sender auth requirement if ($($primaryobj.msExchRequireAuthToSendTo) -eq $true) { write-Slog "$alias" "AR" "Primary msExchRequireAuthToSendTo attr update required" $primaryobj.msExchRequireAuthToSendTo = $false $pupdate = $true } if ($($secondaryobj.msExchRequireAuthToSendTo) -eq $true) { write-Slog "$alias" "AR" "Secondary msExchRequireAuthToSendTo attr update required" $secondaryobj.msExchRequireAuthToSendTo = $false $supdate = $true } #msExchHideFromAddressLists and msExchSenderHintTranslations if ($activity -eq "migrate") { if ($($primaryobj.msExchHideFromAddressLists) -ne $($secondaryobj.msExchHideFromAddressLists)) { write-Slog "$alias" "AR" "Secondary msExchHideFromAddressLists attr update required" $secondaryobj.msExchHideFromAddressLists = $primaryobj.msExchHideFromAddressLists $supdate = $true } if ($($primaryobj.msExchSenderHintTranslations) -ne $($secondaryobj.msExchSenderHintTranslations)) { write-Slog "$alias" "AR" "Secondary msExchSenderHintTranslations attr update required" $secondaryobj.msExchSenderHintTranslations = $primaryobj.msExchSenderHintTranslations $supdate = $true } } if ($activity -eq "galsync") { if ($($secondaryobj.msExchHideFromAddressLists) -ne $true) { write-Slog "$alias" "AR" "Secondary msExchHideFromAddressLists attr update required" $secondaryobj.msExchHideFromAddressLists = $true $supdate = $true } $tip = $null; $tip = "default:<html>`n<body>`nPlease be aware this is a contact for an external recipient.`n</body>`n</html>`n" if (($($secondaryobj.msExchSenderHintTranslations) -ne $tip) -or ($($secondaryobj.msExchSenderHintTranslations) -eq $null)) { write-Slog "$alias" "AR" "Secondary msExchSenderHintTranslations attr update required" $secondaryobj.msExchSenderHintTranslations = $tip $supdate = $true } } #extensionAttribute1 if ($($primaryobj.extensionAttribute1) -ne $($secondaryobj.extensionAttribute1)) { write-Slog "$alias" "AR" "extensionAttribute1 attr update required" $secondaryobj.extensionAttribute1 = $primaryobj.extensionAttribute1 $supdate = $true } #extensionAttribute2 if ($($primaryobj.extensionAttribute2) -ne $($secondaryobj.extensionAttribute2)) { write-Slog "$alias" "AR" "extensionAttribute2 attr update required" $secondaryobj.extensionAttribute2 = $primaryobj.extensionAttribute2 $supdate = $true } #extensionAttribute3 if ($($primaryobj.extensionAttribute3) -ne $($secondaryobj.extensionAttribute3)) { write-Slog "$alias" "AR" "extensionAttribute3 attr update required" $secondaryobj.extensionAttribute3 = $primaryobj.extensionAttribute3 $supdate = $true } #extensionAttribute4 if ($($primaryobj.extensionAttribute4) -ne $($secondaryobj.extensionAttribute4)) { write-Slog "$alias" "AR" "extensionAttribute4 attr update required" $secondaryobj.extensionAttribute4 = $primaryobj.extensionAttribute4 $supdate = $true } #extensionAttribute5 if ($($primaryobj.extensionAttribute5) -ne $($secondaryobj.extensionAttribute5)) { write-Slog "$alias" "AR" "extensionAttribute5 attr update required" $secondaryobj.extensionAttribute5 = $primaryobj.extensionAttribute5 $supdate = $true } #extensionAttribute6 if ($($primaryobj.extensionAttribute6) -ne $($secondaryobj.extensionAttribute6)) { write-Slog "$alias" "AR" "extensionAttribute6 attr update required" $secondaryobj.extensionAttribute6 = $primaryobj.extensionAttribute6 $supdate = $true } #extensionAttribute7 if ($($primaryobj.extensionAttribute7) -ne $($secondaryobj.extensionAttribute7)) { write-Slog "$alias" "AR" "extensionAttribute7 attr update required" $secondaryobj.extensionAttribute7 = $primaryobj.extensionAttribute7 $supdate = $true } #extensionAttribute8 if ($($primaryobj.extensionAttribute8) -ne $($secondaryobj.extensionAttribute8)) { write-Slog "$alias" "AR" "extensionAttribute8 attr update required" $secondaryobj.extensionAttribute8 = $primaryobj.extensionAttribute8 $supdate = $true } #extensionAttribute9 if ($($primaryobj.extensionAttribute9) -ne $($secondaryobj.extensionAttribute9)) { write-Slog "$alias" "AR" "extensionAttribute9 attr update required" $secondaryobj.extensionAttribute9 = $primaryobj.extensionAttribute9 $supdate = $true } #extensionAttribute10 if ($($primaryobj.extensionAttribute10) -ne $($secondaryobj.extensionAttribute10)) { write-Slog "$alias" "AR" "extensionAttribute10 attr update required" $secondaryobj.extensionAttribute10 = $primaryobj.extensionAttribute10 $supdate = $true } #extensionAttribute11 if ($($primaryobj.extensionAttribute11) -ne $($secondaryobj.extensionAttribute11)) { write-Slog "$alias" "AR" "extensionAttribute11 attr update required" $secondaryobj.extensionAttribute11 = $primaryobj.extensionAttribute11 $supdate = $true } #extensionAttribute12 if ($($primaryobj.extensionAttribute12) -ne $($secondaryobj.extensionAttribute12)) { write-Slog "$alias" "AR" "extensionAttribute12 attr update required" $secondaryobj.extensionAttribute12 = $primaryobj.extensionAttribute12 $supdate = $true } #extensionAttribute13 if ($($primaryobj.extensionAttribute13) -ne $($secondaryobj.extensionAttribute13)) { write-Slog "$alias" "AR" "extensionAttribute13 attr update required" $secondaryobj.extensionAttribute13 = $primaryobj.extensionAttribute13 $supdate = $true } #extensionAttribute14 if ($($primaryobj.extensionAttribute14) -ne $($secondaryobj.extensionAttribute14)) { if ($secondaryobj.extensionAttribute14 -cmatch "^ID.*/CF:$" -and $secondaryobj.extensionAttribute15 -cmatch "^\w{32}$") { write-Slog "$alias" "WARN" "Secondary extensionAttribute14 used by QMM and will be ignored" } else { write-Slog "$alias" "AR" "Secondary extensionAttribute14 attr update required" $secondaryobj.extensionAttribute14 = $primaryobj.extensionAttribute14 $supdate = $true } } #extensionAttribute15 if ($($primaryobj.extensionAttribute15) -ne $($secondaryobj.extensionAttribute15)) { if ($secondaryobj.extensionAttribute14 -cmatch "^ID.*/CF:$" -and $secondaryobj.extensionAttribute15 -cmatch "^\w{32}$") { write-Slog "$alias" "WARN" "Secondary extensionAttribute15 used by QMM and will be ignored" } else { write-Slog "$alias" "AR" "Secondary extensionAttribute15 attr update required" $secondaryobj.extensionAttribute15 = $primaryobj.extensionAttribute15 $supdate = $true } } #managedby and msExchCoManagedByLink $primaryallmanagers = @() $primaryobj.managedby | % {$primaryallmanagers += $_} $primaryobj.msExchCoManagedByLink | % {$primaryallmanagers += $_} $primaryallmanagers = $primaryallmanagers | sort | get-unique $sdns = $null; $sdns = @(); $sdns = Get-EMSecondaryDistinguishedNames -Identity $alias -Attribute 'managedBy msExchCoManagedByLink'-PrimaryCred $PrimaryCred -PrimaryPDC $primarypdc -PrimaryDNs $primaryallmanagers -SecondaryCred $secondarycred -SecondaryPDC $secondarypdc try { if (($sdns | measure).count -eq 0) { if ($($secondaryobj.managedBy) -ne $null) { write-Slog "$alias" "AR" "Secondary managedBy attr update required" $secondaryobj.managedBy = $null $supdate = $true } if ($($secondaryobj.msExchCoManagedByLink) -ne $null) { write-Slog "$alias" "AR" "Secondary msExchCoManagedByLink attr update required" $secondaryobj.msExchCoManagedByLink = $null $supdate = $true } } if (($sdns | measure).count -eq 1) { if ($($secondaryobj.managedBy) -ne $sdns) { write-Slog "$alias" "AR" "Secondary managedBy attr update required" $secondaryobj.managedBy = $sdns $supdate = $true } if ($($secondaryobj.msExchCoManagedByLink) -ne $null) { write-Slog "$alias" "AR" "Secondary msExchCoManagedByLink attr update required" $secondaryobj.msExchCoManagedByLink = $null $supdate = $true } } if (($sdns | measure).count -gt 1) { if ($($secondaryobj.managedBy) -ne $sdns[0]) { write-Slog "$alias" "AR" "Secondary managedBy attr update required" $secondaryobj.managedBy = $sdns[0] $supdate = $true } $sdns = $sdns | ? {$_ -ne $sdns[0]} | % {$_.tostring()} if (($(try{compare-object $($secondaryobj.msExchCoManagedByLink) $($sdns)}catch{})) -or ($($secondaryobj.msExchCoManagedByLink) -xor $($sdns))) { write-Slog "$alias" "AR" "Secondary msExchCoManagedByLink attr update required" $secondaryobj.msExchCoManagedByLink = $sdns $supdate = $true } } } catch { write-Slog "$alias" "ERR" "Problem preparing secondary managedBy or msExchCoManagedByLink attr. $($_.exception.message)" } #PROXYADDRESSES $pproxsticky = $null; $pproxsticky = $primaryobj.proxyaddresses -notmatch "^smtp:|^x500:" $sproxsticky = $null; $sproxsticky = $secondaryobj.proxyaddresses -notmatch "^smtp:|^x500" $x500sticky = $null; $x500sticky = $primaryobj.proxyaddresses -match "^x500:"; $x500sticky += $secondaryobj.proxyaddresses -match "^x500:" $x500sticky = $x500sticky | sort -Unique $pprox = $primaryobj.proxyaddresses -match "^smtp:" $sprox = $pprox #primary #smtp if ($pprox -notcontains $("smtp:" + $primaryRoutingSMTP)) { $pprox += $("smtp:" + $primaryRoutingSMTP) } #nonsmtp $pproxsticky | % {$pprox += $_} $x500sticky | % {$pprox += $_} if ($pprox -notcontains $secondaryRoutingX500) { $pprox += $secondaryRoutingX500 } #remove unwanted $pprox = $pprox -notmatch "mail\.on$($secondarydomain)$" $pprox = $pprox -notmatch [regex]::escape($primaryRoutingX500) #formatting $pprox = $pprox | sort -Unique $pproxarray = $null; $pproxarray = @(); $pprox | % {$pproxarray += ($_.tostring())} if ($(try{compare-object $($primaryobj.proxyaddresses) $($pproxarray)}catch{}) -or ($($primaryobj.proxyaddresses) -xor $($pproxarray))) { try { write-Slog "$alias" "AR" "Primary proxyaddresses attr update required" $primaryobj.proxyaddresses = $pproxarray $pupdate = $true } catch { write-Slog "$alias" "ERR" "Problem preparing primary proxyaddresses attr. $($_.exception.message)" } } #secondary #smtp if ($sprox -notcontains $("smtp:" + $secondaryRoutingSMTP)) { $sprox += $("smtp:" + $secondaryRoutingSMTP) } #nonsmtp $sproxsticky | % {$sprox += $_} $x500sticky | % {$sprox += $_} if ($sprox -notcontains $primaryRoutingX500) { $sprox += $primaryRoutingX500 } #remove unwanted $sprox = $sprox -notmatch "mail\.on$($primarydomain)$" $sprox = $sprox -notmatch [regex]::escape($secondaryRoutingX500) #formatting $sprox = $sprox | sort -Unique $sproxarray = $null; $sproxarray = @(); $sprox | % {$sproxarray += ($_.tostring())} if ($(try{compare-object $($secondaryobj.proxyaddresses) $($sproxarray)}catch{}) -or $($($secondaryobj.proxyaddresses) -xor $($sproxarray))) { try { write-Slog "$alias" "AR" "Secondary proxyaddresses attr update required" $secondaryobj.proxyaddresses = $sproxarray $supdate = $true } catch { write-Slog "$alias" "ERR" "Problem preparing secondary proxyaddresses attr. $($_.exception.message)" } } #targetaddress if ($($primaryobj.targetaddress) -ne $($secondaryobj.targetaddress)) { try { write-Slog "$alias" "AR" "Secondary targetaddress attr update required" $secondaryobj.targetaddress = $primaryobj.targetaddress $supdate = $true } catch { write-Slog "$alias" "ERR" "Problem setting secondary targetaddress attr. $($_.exception.message)" } } if ($($primaryobj.msexchrecipientdisplaytype) -ne $($secondaryobj.msexchrecipientdisplaytype)) { try { write-Slog "$alias" "AR" "Secondary msExchRecipientDisplayType attr update required" $secondaryobj.msexchrecipientdisplaytype = $primaryobj.msexchrecipientdisplaytype $supdate = $true } catch { write-Slog "$alias" "ERR" "Problem setting secondary msExchRecipientDisplayType attr. $($_.exception.message)" } } if ($($primaryobj.msExchRemoteRecipientType) -ne $($secondaryobj.msExchRemoteRecipientType)) { try { write-Slog "$alias" "AR" "Secondary msExchRemoteRecipientType attr update required" $secondaryobj.msExchRemoteRecipientType = $primaryobj.msExchRemoteRecipientType $supdate = $true } catch { write-Slog "$alias" "ERR" "Problem setting secondary msExchRemoteRecipientType attr. $($_.exception.message)" } } #maximum size restrictions if ($($primaryobj.delivContLength) -ne $($secondaryobj.delivContLength)) { try { write-Slog "$alias" "AR" "Secondary delivContLength attr update required" $secondaryobj.delivContLength = $primaryobj.delivContLength $supdate = $true } catch { write-Slog "$alias" "ERR" "Problem setting secondary delivContLength attr. $($_.exception.message)" } } #msExchPoliciesExcluded msExchPoliciesIncluded if ($mode -eq "prepare") { if ($primaryobj.msExchPoliciesExcluded -notcontains '{26491cfc-9e50-4857-861b-0cb8df22b5d7}') { write-Slog "$alias" "AR" "Primary msExchPoliciesExcluded attr update required" $primaryobj.msExchPoliciesExcluded = '{26491cfc-9e50-4857-861b-0cb8df22b5d7}' $pupdate = $true } if ($primaryobj.msExchPoliciesIncluded -ne $null) { write-Slog "$alias" "AR" "Primary msExchPoliciesIncluded attr update required" $primaryobj.msExchPoliciesIncluded = $null $pupdate = $true } if ($secondaryobj.msExchPoliciesExcluded -notcontains '{26491cfc-9e50-4857-861b-0cb8df22b5d7}') { write-Slog "$alias" "AR" "Secondary msExchPoliciesExcluded attr update required" $secondaryobj.msExchPoliciesExcluded = '{26491cfc-9e50-4857-861b-0cb8df22b5d7}' $supdate = $true } if ($secondaryobj.msExchPoliciesIncluded -ne $null) { write-Slog "$alias" "AR" "Secondary msExchPoliciesIncluded attr update required" $secondaryobj.msExchPoliciesIncluded = $null $supdate = $true } } #commit changes if ($Mode -eq "Prepare") { try { if ($pupdate -eq $true) { set-adobject -instance $primaryobj -server $primarypdc -Credential $primaryCred -ea stop write-Slog "$alias" "OK" "Primary contact prepared in domain '$primarydomain'" } } catch { write-Slog "$alias" "ERR" "Problem preparing primary contact in domain '$primarydomain'. $($_.exception.message)" } try { if ($supdate -eq $true) { set-adobject -instance $secondaryobj -server $secondarypdc -Credential $secondaryCred -ea stop write-Slog "$alias" "OK" "Secondary contact prepared in domain '$secondarydomain'" } } catch { write-Slog "$alias" "ERR" "Problem preparing secondary contact in domain '$secondarydomain'. $($_.exception.message)" } } #Move to OU try { $ou = $null; $ou = Get-EMTargetOU -Identity $Alias -Activity $Activity -Source $primaryobj -SourceDN $primarydn -TargetCred $secondarycred -TargetDomain $secondarydomain -TargetDN $secondarydn } catch { write-Slog "$alias" "ERR" "Problem getting OU for secondary domain '$secondarydomain'. $($_.exception.message)" } if ($Secondaryobj.distinguishedname -notmatch $OU ) { write-Slog "$alias" "AR" "Moving secondary to OU '$OU'" try { Move-ADObject -Identity $($Secondaryobj.objectguid.guid) -TargetPath $OU -Server $secondarypdc -Credential $secondarycred -ea Stop write-Slog "$alias" "OK" "Moved secondary to OU '$OU'" } catch { write-Slog "$alias" "ERR" "Problem moving secondary to OU '$OU'. $($_.exception.message)" } } write-Slog "$alias" "LOG" "Ready" } } # Health checking ################################################################################################################ function Start-EMCheckHealth() { <# .SYNOPSIS Process a contact. .DESCRIPTION This cmdlet is used to prepare and migrate a contact from the source to the target Exchange Organization. .PARAMETER Identity This is the samaccountname (or mailnickname if checking a contact) attribute of the objects you want the cmdlet to process. .PARAMETER SourceCred Specify the source credentials of the source domain. .PARAMETER TargetCred Specify the target credentials of the target domain. .PARAMETER SourceDomain Specify the source domain. .PARAMETER TargetDomain Specify the target domain. #> #=============================================================================================================== [cmdletbinding()] Param ( [Parameter(mandatory=$true,valuefrompipelinebypropertyname=$true)][string]$Identity, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$SourceCred = $Script:ModuleSourceCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][system.management.automation.pscredential]$TargetCred = $Script:ModuleTargetCred, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceDomain = $Script:ModuleSourceDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetDomain = $Script:ModuleTargetDomain, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$SourceEndPoint = $Script:ModuleSourceEndPoint, [Parameter(mandatory=$false,valuefrompipelinebypropertyname=$true)][string]$TargetEndPoint = $Script:ModuleTargetEndPoint ) Process { $sourcedomain = $sourcedomain.toupper() $targetdomain = $targetdomain.toupper() #get source data try { try { $sourcepdc = $Script:ModuleSourcePDC } catch { write-Slog "$identity" "ERR" "Issue getting domain information for source domain '$sourcedomain'. $($_.exception.message)" } $src = $null; $src = get-adobject -server $sourcepdc -filter {samaccountname -eq $identity -and objectclass -ne "contact"} -properties * -credential $sourcecred -ea stop if (!($src)) { $src = $null; $src = get-adobject -server $sourcepdc -filter {mailnickname -eq $identity -and objectclass -eq "contact"} -properties * -credential $sourcecred -ea stop } if (($src | measure).count -gt 1) { throw "Too many objects returned." } if (($src | measure).count -eq 0) { throw "No objects returned." } if (($src | measure).count -eq 1) { write-Slog "$identity" "OK" "Successfully found $($src.objectclass) object in '$sourcedomain'." try { if ($src.objectclass -eq "user"){ $SrcType = $null; $SrcType = invoke-emexchangecommand -endpoint $sourceendpoint -domaincontroller $sourcepdc -credential $sourcecred -command "get-user -identity ""$($src.objectguid.guid)"" -domaincontroller ""$($sourcepdc)"" -ea stop" | select -expandproperty recipienttypedetails } if ($src.objectclass -eq "group"){ $SrcType = $null; $SrcType = invoke-emexchangecommand -endpoint $sourceendpoint -domaincontroller $sourcepdc -credential $sourcecred -command "get-group -identity ""$($src.objectguid.guid)"" -domaincontroller ""$($sourcepdc)"" -ea stop" | select -expandproperty recipienttypedetails } if ($src.objectclass -eq "contact"){ $SrcType = $null; $SrcType = invoke-emexchangecommand -endpoint $sourceendpoint -domaincontroller $sourcepdc -credential $sourcecred -command "get-contact -identity ""$($src.objectguid.guid)"" -domaincontroller ""$($sourcepdc)"" -ea stop" | select -expandproperty recipienttypedetails } if ($srcType) { write-Slog "$identity" "LOG" "Source recipient type is $SrcType." } else { throw "Unable to determine source recipient type" } } catch { throw $($_.exception.message) } } } catch { write-Slog "$identity" "ERR" "Issue getting data from '$sourcedomain'. $($_.exception.message)" } #get target data try { try { $targetpdc = $Script:ModuleTargetPDC } catch { write-Slog "$identity" "ERR" "Issue getting domain information for target domain '$targetdomain'. $($_.exception.message)" } $tar = $null; $tar = get-adobject -server $targetpdc -filter {samaccountname -eq $identity -and objectclass -ne "contact"} -properties * -credential $targetcred -ea stop if (!($tar)) { $tar = $null; $tar = get-adobject -server $targetpdc -filter {mailnickname -eq $identity -and objectclass -eq "contact"} -properties * -credential $targetcred -ea stop } if (($tar | measure).count -gt 1) { throw "Too many objects returned." } if (($tar | measure).count -eq 0) { throw "No objects returned." } if (($tar | measure).count -eq 1) { write-Slog "$identity" "OK" "Successfully found $($tar.objectclass) object in '$targetdomain'." try { if ($tar.objectclass -eq "user"){ $tarType = $null; $tarType = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-user -identity ""$($tar.objectguid.guid)"" -domaincontroller ""$($targetpdc)"" -ea stop" | select -expandproperty recipienttypedetails } if ($tar.objectclass -eq "group"){ $tarType = $null; $tarType = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-group -identity ""$($tar.objectguid.guid)"" -domaincontroller ""$($targetpdc)"" -ea stop" | select -expandproperty recipienttypedetails } if ($tar.objectclass -eq "contact"){ $tarType = $null; $tarType = invoke-emexchangecommand -endpoint $targetendpoint -domaincontroller $targetpdc -credential $targetcred -command "get-contact -identity ""$($tar.objectguid.guid)"" -domaincontroller ""$($targetpdc)"" -ea stop" | select -expandproperty recipienttypedetails } if ($tarType) { write-Slog "$identity" "LOG" "Target recipient type is $TarType." } else { throw "Unable to determine target recipient type" } } catch { throw $($_.exception.message) } } } catch { write-Slog "$identity" "ERR" "Issue getting data from '$targetdomain'. $($_.exception.message)" } #calculate source routing SMTP address try { $srcSMTP = $null; $srcSMTP = $("smtp:" + "$($src.mailnickname)@mail.on$($sourcedomain)").tolower() } catch { write-Slog "$identity" "ERR" "Problem preparing source routing SMTP address. $($_.exception.message)" } #calculate source routing X500 address try { $srcX500 = $null; $srcX500 = $("X500:" + $src.legacyexchangedn) } catch { write-Slog "$identity" "ERR" "Problem preparing source routing X500 address. $($_.exception.message)" } #calculate source routing SMTP address try { $tarSMTP = $null; $tarSMTP = $("smtp:" + "$($tar.mailnickname)@mail.on$($targetdomain)").tolower() } catch { write-Slog "$identity" "ERR" "Problem preparing target routing SMTP address. $($_.exception.message)" } #calculate source routing X500 address try { $tarX500 = $null; $tarX500 = $("X500:" + $tar.legacyexchangedn) } catch { write-Slog "$identity" "ERR" "Problem preparing target routing X500 address. $($_.exception.message)" } #check source proxyaddresses if ($src.proxyaddresses -contains $srcSMTP){ write-Slog "$identity" "OK" "Source proxyaddresses contains $srcSMTP" } else { write-Slog "$identity" "WARN" "Source proxyaddresses does not contain $srcSMTP" } write-Slog "$identity" "LOG" "Target legacyexchangedn is $($tar.legacyexchangedn)" if ($src.proxyaddresses -contains $tarX500){ write-Slog "$identity" "OK" "Source proxyaddresses contains $tarX500" } else { write-Slog "$identity" "WARN" "Source proxyaddresses does not contain $tarX500" } #check target proxyaddresses if ($tar.proxyaddresses -contains $tarSMTP){ write-Slog "$identity" "OK" "Target proxyaddresses contains $tarSMTP" } else { write-Slog "$identity" "WARN" "Target proxyaddresses does not contain $tarSMTP" } write-Slog "$identity" "LOG" "Source legacyexchangedn is $($src.legacyexchangedn)" if ($tar.proxyaddresses -contains $srcX500){ write-Slog "$identity" "OK" "Target proxyaddresses contains $srcX500" } else { write-Slog "$identity" "WARN" "Target proxyaddresses does not contain $srcX500" } #check targetaddress if ($srcType -match "^usermailbox$|^linkedmailbox$|^sharedmailbox$|^roommailbox$|^equipmentmailbox$") { if ($src.targetaddress -eq $null){ write-Slog "$identity" "OK" "Source targetaddress is null" } else { write-Slog "$identity" "WARN" "Source targetaddress is not null" } } else { if (($src.targetaddress) -and $src.targetaddress -match "@mail`.on$targetdomain$") { write-Slog "$identity" "OK" "Source targetaddress is $($src.targetaddress)" } else { write-Slog "$identity" "WARN" "Source targetaddress is $($src.targetaddress)" } } if ($tarType -match "^usermailbox$|^linkedmailbox$|^sharedmailbox$|^roommailbox$|^equipmentmailbox$") { if ($tar.targetaddress -eq $null){ write-Slog "$identity" "OK" "Target targetaddress is null" } else { write-Slog "$identity" "WARN" "Target targetaddress is not null" } } else { if (($tar.targetaddress) -and $tar.targetaddress -match "@mail`.on$sourcedomain$") { write-Slog "$identity" "OK" "Target targetaddress is $($tar.targetaddress)" } else { write-Slog "$identity" "WARN" "Target targetaddress is $($tar.targetaddress)" } } } } |