# Author: Alexander Zarenko, https://blog.zarenko.net
#
# Provided as-is, feel free to change and redistribute
# If you do, please include a short notice mentioning me such as this.
#
# Use at you own risk!
[CmdletBinding()]
Param(
[string]$PublicFolderPath,
[string]$Target,
[switch]$DoNotcopyItems,
[switch]$CopyOnly,
[switch]$cutoverMailflow
)
### load functions
. $PSScriptRoot\PFmig-functions_onprem-only.ps1
$startdate=get-date
Write-Host "`nStarting at $startdate...`n" -ForegroundColor Green
$adminSAM="$env:USERDOMAIN\$env:USERNAME"
"Running as [$adminSAM]"
### load ex shell
$snapin = Get-PSSnapin -Registered Microsoft.Exchange.Management.PowerShell.E* -ErrorAction 'SilentlyContinue'
if ($snapin -eq $null){Write-Warning "Snapin not available"}
else
{
$command = Get-Command "Get-Mailbox" -ErrorAction SilentlyContinue
if ($command -eq $null)
{
Write-Host "Loading Exchange Commands"
Add-PSSnapin $snapin -ErrorAction 'SilentlyContinue'
}
else {Write-Host "Exchange Snapin is already loaded"}
}
$adminUPN=(Get-User $adminSAM).UserPrincipalName
Set-ADServerSettings -ViewEntireForest:$true
### get hierarchy
# Examples:
#$folderStruct = "\Public Folder 5\5.1\5.1.1\5.1.1.1"
#$folderStruct = "\Public Folder 3\Public Folder 3.2\Public Folder 3.2.1"
# show dialog, let user select the migration source folder
if(-not $PublicFolderPath){
[pscustomobject]$PublicFolderPath = (Get-PublicFolder -Recurse | select Name,'Parentpath'| ogv -PassThru -Title "Select the source PF structure")
if($PublicFolderPath.Name -eq "IPM_SUBTREE"){
$PublicFolderFullPath = "\"
Write-Host $PublicFolderFullPath -ForegroundColor Magenta
}
else{
if($PublicFolderPath.ParentPath -ne "\"){
$PublicFolderFullPath=$PublicFolderPath.ParentPath + "\" + $PublicFolderPath.Name
Write-Host $PublicFolderFullPath -ForegroundColor Cyan
}
else{
$PublicFolderFullPath="\" + $PublicFolderPath.Name
Write-Host $PublicFolderFullPath -ForegroundColor Green
}
}
}
$folderStruct = $PublicFolderFullPath
# hard coded urls in case of problems, uncomment and change if needed
#$url="https://mail.domain.tld/EWS/Exchange.asmx"
#$autodiscourl="https://mail.domain.tld/autodiscover/autodiscover.svc"
#region TargetMailboxCreation
### target and Admin Mailboxes
# of a given folder structure, use the last piece
# e.g. in case of "\Public Folder 1\Public Folder 1.4" use "Public Folder 1.4"
Write-Host "### Creating the target mailbox ###" -ForegroundColor Yellow
$targetMBX = $folderStruct.Split("\") | select -Last 1
# if there is no meaningful last piece, we assume that the PF Root was selected
if($targetMBX.Length -eq 0){$targetMBX="ROOT"}
# take care special characters to create a nice target mailbox name
$targetMBX = "PF_" + $targetMBX -ireplace 'ä', 'ae' -ireplace 'ö', 'oe' -ireplace 'ü', 'ue' -ireplace 'ß', 'ss' -ireplace ' ', ' ' -ireplace ' ', '' -ireplace '\\', '_'
# show a dialog and let the user select the destination OU for the target mailbox
$OU=Get-ADOrganizationalUnit -Filter * | select DistinguishedName,Name | ogv -PassThru -Title "Mailbox creation: Select OU for disabled user account"
$OU=$OU.DistinguishedName
# create the target mailbox if it does not yet exist
if (-not (get-mailbox $targetMBX -ErrorAction SilentlyContinue)){
New-Mailbox $targetMBX -OrganizationalUnit $OU -Shared #-DisplayName $targetMBXdisplayname
Start-Sleep 10
do{
Write-host "Mailbox creation started, checking progress in 10 seconds..."
Start-Sleep 10
}until($targetMBX=Get-Mailbox $targetMBX -ErrorAction SilentlyContinue)
$targetMBX=Get-Mailbox $targetMBX -ErrorAction SilentlyContinue
# Add full access permission to the current admin user
$addFApermResult=Add-MailboxPermission $targetMBX -User $adminSAM -AccessRights fullaccess -AutoMapping:$false
Write-Host( $addFApermResult | Out-String) -ForegroundColor Gray
}
Write-host "using [$targetMBX] as target mailbox"
#endregion TargetMailboxCreation
#region Preparation
$targetalias=(Get-Mailbox $targetMBX).Alias
$targetMBX=(Get-Mailbox $targetMBX).WindowsEmailAddress.Address
$adminMBX = (Get-Mailbox $adminSAM).WindowsEmailAddress.Address
### get a list of PFs beginning at the path of $folderStruct
$folders2process=Get-PublicFolder $folderStruct -Recurse | ?{$_.Parentpath}
### print the list to the screen
$folders2process | ft
### variable to contain all mail addresses of mail-enabled PFs in the $folderStruct
[System.Collections.ArrayList]$pfaddresses = @()
if(-not $credentials){$credentials = Get-Credential -Message "Enter onprem Admin credentials for EWS connection" -UserName "$env:USERDOMAIN\$env:USERNAME"}
$global:service = Connect-Exchange -MailboxName $adminMBX -Credentials $Credentials -url $url
### starting to process the relevant PFs
foreach($folder in $folders2process){
$mailpf=$null
### concat the parent folder name with the child folder name. on the top level this results in a double \\ which is replaced with a single \ so these paths work correctly
$fullpath=(($folder.ParentPath + "\" + $folder.Name).Replace("\\","\"))
### take note of the folder class of the source PF to create an appropriate target folder
$class=$folder.FolderClass
Write-Host "### Copying items ###" -ForegroundColor Yellow
Write-Host "Processing [$fullpath] - FolderClass [$class]"
#endregion Preparation
#region FolderCreation
$newfoldernamearray=($folder.ParentPath).Split("\")
for($i=1;$i -lt $newfoldernamearray.Length;$i++){
$newfolder=$newfoldernamearray[$i]
$parentfolder=$newfoldernamearray[$i - 1]
if($i -eq 1){
#Create level 1 folder.
$parentpath=$null
if($folder.ParentPath -eq "\"){$newfolder=$folder.Name}
Write-Host "Creating folder [$newfolder]"
Create-Folder -MailboxName $targetMBX -Credentials $credentials -NewFolderName $newfolder # without parent
### adding default.read for traversal
# If the folder contains no data and is used only for traversal, everyone should have READ. Otherwise the permissions here are overwritten later by migrated folder permissions
Set-MailboxFolderPermission "$targetalias`:\" -User "Default" -AccessRights Reviewer #-WhatIf
}
else{
#Create level 2 folder and onwards. The same as above applies in terms of traversal and permissions
Write-Host "Creating folder [$newfolder] as a subfolder of [$parentfolder]";
$parentpath=$parentpath+="\$parentfolder"
Write-Verbose "This is the complete parent path used for folder creation: [$parentpath]";
Create-Folder -MailboxName $targetMBX -Credentials $credentials -NewFolderName $newfolder -ParentFolder $parentpath #with parent [$i-1]
### adding default.read for traversal
Set-MailboxFolderPermission "$targetalias`:$parentpath" -User "Default" -AccessRights Reviewer #-WhatIf
}
}
#endregion FolderCreation
#region ContentMigration
Write-Verbose "Read the contents of the public folder [$($folder.Name)]"
Write-Verbose "Create [$($folder.Name)] in [$targetMBX] as a subfolder of [$($folder.ParentPath)]"
Write-Verbose "Copy all contents to [$targetMBX`:$($folder.ParentPath)\$($folder.Name)]."
# Get-PublicFolderItems is not only getting the items - it also copies them to the target Mailbox
# Debug output if parameter is given
if($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent){
Get-PublicFolderItems -MailboxName $adminMBX -Credentials $credentials -PublicFolderPath $fullpath -targetMBX $targetMBX -folderName $folder.Name -parentpath $folder.ParentPath -folderClass $class -url $url -autodiscourl $autodiscourl -Debug
}
# If not: just normal output
else{
Get-PublicFolderItems -MailboxName $adminMBX -Credentials $credentials -PublicFolderPath $fullpath -targetMBX $targetMBX -folderName $folder.Name -parentpath $folder.ParentPath -folderClass $class -url $url -autodiscourl $autodiscourl
}
Write-Host "Finished copying, moving on...`n"
#endregion ContentMigration
#region SMTPAddresses
### collecting mail addresses of mail-enabled PFs
Write-Host "### Collecting mail addresses ###" -ForegroundColor Yellow
Write-Host "Check if [$fullpath] is mail-enabled. If yes, get its mail address"
try{
$mailpf=Get-MailPublicFolder $fullpath -ErrorAction Stop
}
catch{
Write-Host "`t$($_.Exception.Message)" -ForegroundColor DarkGray
}
if($mailpf){
$strPFMailAddresses=[string]::join(";",($mailpf.EmailAddresses.AddressString))
Write-Host -ForegroundColor Cyan "`tFolder [$fullpath] has these mail addresses: [$strPFMailAddresses]"
foreach($smtp in $mailpf.EmailAddresses){
$pfaddresses.Add("$($smtp.AddressString)") | Out-Null
}
$global:primarySMTP=$mailpf.WindowsEmailAddress.Address
}
#endregion SMTPAddresses
#region SendAsPermissions
### Migrating SendAs permissions
if($mailpf){
Write-Host "### Reading and copying SendAs permissions ###" -ForegroundColor Yellow
$perms=$mailpf | Get-ADPermission
$sendAsPerms=$perms | ?{$_.ExtendedRights -like "Send-As"}
foreach($sendAsPerm in $sendAsPerms){
$user=$sendAsPerm.User
"Copying sendAs permission for [$user] to [$($targetalias)]"
$mbx = get-Mailbox $targetMBX
$mbx | Add-ADPermission -ExtendedRights Send-As -User $user | Out-Null #-WhatIf
$user=$null
}
$perms,$sendAsPerms,$user=$null
}
#endregion SendAsPermissions
#region FolderPermissions
### Migrating folder permissions
Write-Host "### Migrating / Copying folder permissions ###" -ForegroundColor Yellow
$folderperms=Get-PublicFolderClientPermission $fullPath
foreach($folderperm in $folderperms){
$user=$folderperm.User
$accessrights=$folderperm.AccessRights
# ToDo: add routine, for remotemailboxes : get-AdUser -Identity $_.Guid | Set-ADObject -Replace @{msExchRecipientDisplayType=-1073741818}
# WHY again?
if($user.DisplayName -eq "None" -or $user.DisplayName -eq "Default" -or $user.DisplayName -eq "Standard" -or $user.DisplayName -eq "Anonymous" -or $user.DisplayName -eq "Anonym" ){
#"Setting folder permission for [$user] on [$fullpath] to migrated folder in [$targetMBX]"
$ret=Set-MailboxFolderPermission "$targetalias`:$fullpath" -User $user -AccessRights $accessrights #-WhatIf
$ret | Out-String -Verbose
# everything commented out, do nothing
}
else{
write-host "Migrating folder permission for [$user] on [$fullpath] to AccessRight [FullAccess] on [$targetMBX]"
try{
#Add-MailboxFolderPermission "$targetalias`:$fullpath" -User $user -AccessRights $accessrights -ErrorAction Stop #-WhatIf
if($copyonly){
$res=Add-MailboxPermission $targetalias -AccessRights FullAccess -User $user.ADRecipient.WindowsEmailAddress.Address -WhatIf
}
else{
$res=Add-MailboxPermission $targetalias -AccessRights FullAccess -User $user.ADRecipient.WindowsEmailAddress.Address
}
$res | Out-String -Verbose
}
catch{
Write-Warning $Error[0]
}
}
}
if($additionaladminSAM){
### add exchange admin with owner rights to every migrated folder as well
"Adding folder permission for [$additionaladminSAM] on [$fullpath] to migrated folder in [$targetMBX]"
try{
Add-MailboxFolderPermission "$targetalias`:$fullpath" -User $additionaladminSAM -AccessRights $accessrights -ErrorAction Stop #-WhatIf
}
catch{
Write-Warning $Error[0]
}
$user,$accessrights=$null
}
$folderperms=$null
#endregion FolderPermissions
#region MailFlowCutover
# make sure that the target mbx is there and ready
$mbx=get-mailbox $targetMBX
if($mailpf -and $mbx){
### Disabling mail-enabled PFs
Write-Host "### Disabling mail-enabled PFs ###" -ForegroundColor Yellow
if($cutoverMailflow){
Write-Host -ForegroundColor Cyan "Disabling mail public folder [$fullpath] now..."
Disable-MailPublicFolder $mailpf -Confirm:$false
Write-host "checking progress in 10 seconds..."
Start-Sleep 10
try{
$mailpf=Get-MailPublicFolder $fullpath -ErrorAction stop
}
catch{
Write-Warning $Error[0]
}
if(-not $mailpf){Write-Host -ForegroundColor Cyan "[$fullpath] has been disabled successfully."}
}
else{
Write-Host -ForegroundColor Cyan "WHATIF: Disabling mail public folder [$fullpath] now..."
Disable-MailPublicFolder $mailpf -WhatIf
}
}
}
### migrating mail addresses of the disabled PFs to the target mailbox
if($cutoverMailflow){
Write-Host "### Assigning mail addresses of the disabled PFs to the target mailbox ###" -ForegroundColor Yellow
Write-Host "Waiting 1 minute before proceeding..."
if($pfaddresses){
Write-Host "`tSetting Emailaddresses of [$targetMBX] to Emailaddresses of the mail-enabled PFs [[string]$pfaddresses]..."
$mbx | Set-Mailbox -EmailAddresses $pfaddresses -EmailAddressPolicyEnabled $false
Write-Host "`tSetting WindowsEmailAddress of [$targetMBX] to that of the previously mail-enabled PF [-> $global:primarySMTP]..."
$mbx | Set-Mailbox -WindowsEmailAddress $global:primarySMTP
}
}
else{
if($pfaddresses){
Write-Host "WHAT IF: `tSetting Emailaddresses of [$targetMBX] to Emailaddresses of the mail-enabled PFs [[string]$pfaddresses]..." -ForegroundColor Cyan
Set-Mailbox $targetMBX -EmailAddresses $pfaddresses -EmailAddressPolicyEnabled $false -WhatIf
}
}
#endregion MailFlowCutover
$enddate=get-date
$timediff=$enddate-$startdate
Write-Host "Finished. This took $($timediff.Hours) hours, $($timediff.Minutes) minutes and $($timediff.Seconds) seconds." -ForegroundColor Green