# 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