# Authors: # Alexander Zarenko, https://blog.zarenko.net # Glen Scales, https://github.com/gscales # # All functions originally created by Glen Scales # Adapted for this use case by Alexander Zarenko # # 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! function Get-PublicFolderItems{ [CmdletBinding()] param( [Parameter(Position=0, Mandatory=$true)] [string]$MailboxName, [Parameter(Position=1, Mandatory=$true)] [PSCredential]$Credentials, [Parameter(Position=2, Mandatory=$true)] [string]$PublicFolderPath, [Parameter(Position=3, Mandatory=$false)] [switch]$useImpersonation, [Parameter(Position=4, Mandatory=$false)] [string]$url, [string]$autodiscourl, [string]$targetMBX, [string]$parentpath, [string]$folderName, [string]$folderClass ) Begin { if($useImpersonation.IsPresent) { $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName) } Get-PublicFolderRoutingHeader -service $service -Credentials $Credentials -MailboxName $MailboxName -Header "X-AnchorMailbox" -autodiscourl $autodiscourl $fldId = PublicFolderIdFromPath -FolderPath $PublicFolderPath -SmtpAddress $MailboxName -service $Global:service -autodiscourl $autodiscourl $SubFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId($fldId) #Define ItemView to retrive just 1000 Items $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000) $ItemPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties) $ivItemView.PropertySet = $ItemPropset $fiItems = $null ############### Create folder in target mailbox Create-Folder -MailboxName $targetMBX -Credentials $Credentials -NewFolderName $folderName -ParentFolder $parentpath -FolderClass $folderClass $targetFolder = Get-FolderFromPath -FolderPath $PublicFolderPath -MailboxName $targetMBX -service $service if(-not ($DoNotCopyItems)){ $totalprocessed=1 do{ $fiItems = $service.FindItems($SubFolderId,$ivItemView) $itemtotalcount=$fiItems.Items.Count $itemcount=1 # load previous migration results if(Test-Path -Path .\$targetMBX.csv){ $alreadycopied = Import-Csv .\$targetMBX.csv -Encoding UTF8 Write-Host "Previous copy results imported" -ForegroundColor Cyan } else{ Write-Host "No file with previous copy results found" -ForegroundColor Yellow } # Write-Host ("Found [" + $fiItems.Items.Count + "] items to copy.") # this is just for debugging purposes, feel free to ignore :) $copy="1" if($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent){$copy=Read-Host -Prompt "Enter 1 to copy or nothing to skip copying."} if($copy -eq "1"){ $wp=$false #wp = write-progress foreach($Item in $fiItems.Items){ # take care of items with no subject if($Item.Subject -eq $null){$itemSubject = "#no subject#"} else{$itemSubject = $Item.Subject} Write-Host "Copying item [$itemSubject] to folder [$($targetFolder.DisplayName)] in MBX [$targetMBX]. Total count: $totalprocessed / $($fiItems.Totalcount)" $progress = $itemcount / $itemtotalcount *100 ; Write-Progress -Activity "$itemcount/$itemtotalcount - Copying items to MBX [$targetMBX]" -Status "$itemSubject" -PercentComplete $progress $wp=$true if(-not ($DoNotCopyItems)){ ## skip copying if flag is set if($alreadycopied.UniqueId -notcontains $Item.Id){ ## only copy new items $Item.Copy($targetFolder.Id) $newitem = $null $newitem=$item.Id # append the previous results CSV file $newitem | export-csv .\$targetMBX.csv -NoTypeInformation -Encoding UTF8 -Append } Else{ Write-Host "`tItem already copied, skipping..." -ForegroundColor DarkGray } } $itemcount = $itemcount + 1 $totalprocessed = $totalprocessed + 1 } } $ivItemView.Offset += $fiItems.Items.Count }while($fiItems.MoreAvailable -eq $true) } if($wp){ Write-Progress -Activity "$itemcount/$itemtotalcount - Copying items to MBX [$targetMBX]" -Status "$itemSubject" -PercentComplete $progress -Completed } } } function Connect-Exchange{ param( [Parameter(Position=0, Mandatory=$true)] [string]$MailboxName, [Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials, [Parameter(Position=2, Mandatory=$false)] [string]$url ) Begin { Load-EWSManagedAPI ## Set Exchange Version $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2 ## Create Exchange Service Object $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion) ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials #Credentials Option 1 using UPN for the windows Account #$psCred = Get-Credential $creds = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString()) $service.Credentials = $creds #Credentials Option 2 #service.UseDefaultCredentials = $true #$service.TraceEnabled = $true ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates Handle-SSL ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use #CAS URL Option 1 Autodiscover if($url){ $uri=[system.URI] $url $service.Url = $uri } else{ $service.AutodiscoverUrl($MailboxName,{$true}) } Write-Verbose ("Using CAS Server : " + $Service.url) #CAS URL Option 2 Hardcoded #$uri=[system.URI] "https://casservername/ews/exchange.asmx" #$service.Url = $uri ## Optional section for Exchange Impersonation #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName) if(!$service.URL){ throw "Error connecting to EWS" } else { return $service } } } function Load-EWSManagedAPI{ param( ) Begin { ## Load Managed API dll ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll") if (Test-Path $EWSDLL) { Import-Module $EWSDLL } else { "$(get-date -format yyyyMMddHHmmss):" "This script requires the EWS Managed API 1.2 or later." "Please download and install the current version of the EWS Managed API from" "http://go.microsoft.com/fwlink/?LinkId=255472" "" "Exiting Script." exit } } } function Handle-SSL{ param( ) Begin { ## Code From http://poshcode.org/624 ## Create a compilation environment $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider $Compiler=$Provider.CreateCompiler() $Params=New-Object System.CodeDom.Compiler.CompilerParameters $Params.GenerateExecutable=$False $Params.GenerateInMemory=$True $Params.IncludeDebugInformation=$False $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null $TASource=@' namespace Local.ToolkitExtensions.Net.CertificatePolicy{ public class TrustAll : System.Net.ICertificatePolicy { public TrustAll() { } public bool CheckValidationResult(System.Net.ServicePoint sp, System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) { return true; } } } '@ $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) $TAAssembly=$TAResults.CompiledAssembly ## We now create an instance of the TrustAll and attach it to the ServicePointManager $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll ## end code from http://poshcode.org/624 } } function PublicFolderIdFromPath{ param ( [Parameter(Position=0, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service, [Parameter(Position=1, Mandatory=$true)] [String]$FolderPath, [Parameter(Position=2, Mandatory=$true)] [String]$SmtpAddress, [string]$autodiscourl ) process{ ## Find and Bind to Folder based on Path #Define the path to search should be seperated with \ #Bind to the MSGFolder Root $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot) $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties) $PR_REPLICA_LIST = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6698,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary); $psPropset.Add($PR_REPLICA_LIST) $tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset) $PR_REPLICA_LIST_Value = $null if($tfTargetFolder.TryGetProperty($PR_REPLICA_LIST,[ref]$PR_REPLICA_LIST_Value)){ $GuidAsString = [System.Text.Encoding]::ASCII.GetString($PR_REPLICA_LIST_Value, 0, 36); $HeaderAddress = new-object System.Net.Mail.MailAddress($service.HttpHeaders["X-AnchorMailbox"]) $pfHeader = $GuidAsString + "@" + $HeaderAddress.Host Write-Verbose ("Root Public Folder Routing Information Header : " + $pfHeader ) if(-not $service.HttpHeaders."X-PublicFolderMailbox"){$service.HttpHeaders.Add("X-PublicFolderMailbox", $pfHeader)} } #Split the Search path into an array $fldArray = $FolderPath.Split("\") #Loop through the Split Array and do a Search for each level of folder for ($lint = 1; $lint -lt $fldArray.Length; $lint++) { #Perform search based on the displayname of each folder level $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1) $fvFolderView.PropertySet = $psPropset $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint]) $findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView) if ($findFolderResults.TotalCount -gt 0){ foreach($folder in $findFolderResults.Folders){ $tfTargetFolder = $folder } } else{ "Error Folder Not Found" $tfTargetFolder = $null break } } if($tfTargetFolder -ne $null){ $PR_REPLICA_LIST_Value = $null if($tfTargetFolder.TryGetProperty($PR_REPLICA_LIST,[ref]$PR_REPLICA_LIST_Value)){ $GuidAsString = [System.Text.Encoding]::ASCII.GetString($PR_REPLICA_LIST_Value, 0, 36); $HeaderAddress = new-object System.Net.Mail.MailAddress($service.HttpHeaders["X-AnchorMailbox"]) $pfHeader = $GuidAsString + "@" + $HeaderAddress.Host Write-Verbose ("Target Public Folder Routing Information Header : " + $pfHeader ) Get-PublicFolderContentRoutingHeader -service $global:service -Credentials $Credentials -MailboxName $SmtpAddress -pfAddress $pfHeader -autodiscourl $autodiscourl } return $tfTargetFolder.Id.UniqueId.ToString() } else{ throw "Folder not found" } } } function Get-PublicFolderRoutingHeader{ param ( [Parameter(Position=0, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service, [Parameter(Position=1, Mandatory=$true)] [PSCredential]$Credentials, [Parameter(Position=2, Mandatory=$true)] [string]$MailboxName, [Parameter(Position=3, Mandatory=$true)] [string]$Header, [string]$autodiscourl ) process { $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1 $AutoDiscoverService = New-Object Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverService($ExchangeVersion); $creds = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString()) $AutoDiscoverService.Credentials = $creds $AutoDiscoverService.EnableScpLookup = $false; $AutoDiscoverService.RedirectionUrlValidationCallback = {$true}; $AutoDiscoverService.PreAuthenticate = $true; $AutoDiscoverService.KeepAlive = $false; if($autodiscourl){$AutoDiscoverService.Url = $autodiscourl} if($Header -eq "X-AnchorMailbox") { $gsp = $AutoDiscoverService.GetUserSettings($MailboxName,[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::PublicFolderInformation); $PublicFolderInformation = $null if ($gsp.Settings.TryGetValue([Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::PublicFolderInformation, [ref] $PublicFolderInformation)) { Write-Verbose ("Public Folder Routing Information Header : " + $PublicFolderInformation) if(-not $service.HttpHeaders.$Header){$service.HttpHeaders.Add($Header, $PublicFolderInformation)} } } } } function Get-PublicFolderContentRoutingHeader{ param ( [Parameter(Position=0, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service, [Parameter(Position=1, Mandatory=$true)] [PSCredential]$Credentials, [Parameter(Position=2, Mandatory=$true)] [string]$MailboxName, [Parameter(Position=3, Mandatory=$true)] [string]$pfAddress, [string]$autodiscourl ) process { $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1 $AutoDiscoverService = New-Object Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverService($ExchangeVersion); $creds = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString()) $AutoDiscoverService.Credentials = $creds $AutoDiscoverService.EnableScpLookup = $false; $AutoDiscoverService.RedirectionUrlValidationCallback = {$true}; $AutoDiscoverService.PreAuthenticate = $true; $AutoDiscoverService.KeepAlive = $false; if($autodiscourl){$AutoDiscoverService.Url = $autodiscourl} $gsp = $AutoDiscoverService.GetUserSettings($MailboxName,[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::AutoDiscoverSMTPAddress); #Write-Host $AutoDiscoverService.url $auDisXML = "<Autodiscover xmlns=`"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006`"><Request>`r`n" + "<EMailAddress>" + $pfAddress + "</EMailAddress>`r`n" + "<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>`r`n" + "</Request>`r`n" + "</Autodiscover>`r`n"; $AutoDiscoverRequest = [System.Net.HttpWebRequest]::Create($AutoDiscoverService.url.ToString().replace(".svc",".xml")); $bytes = [System.Text.Encoding]::UTF8.GetBytes($auDisXML); $AutoDiscoverRequest.ContentLength = $bytes.Length; $AutoDiscoverRequest.ContentType = "text/xml"; $AutoDiscoverRequest.UserAgent = "Microsoft Office/16.0 (Windows NT 6.3; Microsoft Outlook 16.0.6001; Pro)"; $AutoDiscoverRequest.Headers.Add("Translate", "F"); $AutoDiscoverRequest.Method = "POST"; $AutoDiscoverRequest.Credentials = $creds; $RequestStream = $AutoDiscoverRequest.GetRequestStream(); $RequestStream.Write($bytes, 0, $bytes.Length); $RequestStream.Close(); $AutoDiscoverRequest.AllowAutoRedirect = $truee; $Response = $AutoDiscoverRequest.GetResponse().GetResponseStream() $sr = New-Object System.IO.StreamReader($Response) [XML]$xmlReposne = $sr.ReadToEnd() if($xmlReposne.Autodiscover.Response.User.AutoDiscoverSMTPAddress -ne $null) { Write-Verbose ("Public Folder Content Routing Information Header : " + $xmlReposne.Autodiscover.Response.User.AutoDiscoverSMTPAddress) $service.HttpHeaders["X-AnchorMailbox"] = $xmlReposne.Autodiscover.Response.User.AutoDiscoverSMTPAddress $service.HttpHeaders["X-PublicFolderMailbox"] = $xmlReposne.Autodiscover.Response.User.AutoDiscoverSMTPAddress } } } function Get-FolderFromPath{ param ( [Parameter(Position=0, Mandatory=$true)] [string]$FolderPath, [Parameter(Position=1, Mandatory=$true)] [string]$MailboxName, [Parameter(Position=2, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service, [Parameter(Position=3, Mandatory=$false)] [Microsoft.Exchange.WebServices.Data.PropertySet]$PropertySet ) process{ #Bind to the MSGFolder Root $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName) $tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid) #Split the Search path into an array to get the folder names $fldArray = $FolderPath.Split("\") #Loop through the Split Array and do a Search for each level of folder for ($lint = 1; $lint -lt $fldArray.Length; $lint++) { #Perform search based on the displayname of each folder level $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1) if(![string]::IsNullOrEmpty($PropertySet)){ $fvFolderView.PropertySet = $PropertySet } $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint]) $findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView) if ($findFolderResults.TotalCount -gt 0){ foreach($folder in $findFolderResults.Folders){ $tfTargetFolder = $folder } } else{ Write-host ("Error Folder not found check path and try again") $tfTargetFolder = $null break } } ### if($FolderPath -eq "\"){$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)} if($tfTargetFolder -ne $null){ return [Microsoft.Exchange.WebServices.Data.Folder]$tfTargetFolder } else{ throw ("Folder Not found") } } } function Create-Folder{ param( [Parameter(Position=0, Mandatory=$true)] [string]$MailboxName, [Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials, [Parameter(Position=2, Mandatory=$true)] [String]$NewFolderName, [Parameter(Position=3, Mandatory=$false)] [String]$ParentFolder, [Parameter(Position=4, Mandatory=$false)] [String]$FolderClass, [Parameter(Position=5, Mandatory=$false)] [switch]$useImpersonation ) Begin { $service = $Global:service if($useImpersonation.IsPresent){ $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName) } $NewFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service) $NewFolder.DisplayName = $NewFolderName if(([string]::IsNullOrEmpty($folderClass))){ $NewFolder.FolderClass = "IPF.Note" } else{ $NewFolder.FolderClass = $folderClass } $EWSParentFolder = $null if(([string]::IsNullOrEmpty($ParentFolder))){ # Bind to the MsgFolderRoot folder $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName) $EWSParentFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid) } else{ write-host "Check if the parent folder [$parentfolder] of folder [$NewFolderName] is present in MBX [$MailboxName]" ### use it as search base to verify if the tobecreated folder is already present $EWSParentFolder = Get-FolderFromPath -MailboxName $MailboxName -service $service -FolderPath $ParentFolder } #Define Folder View - only want to return one object $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1) #Define a Search folder that is going to do a search based on the DisplayName of the folder $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$NewFolderName) #Do the Search $findFolderResults = $service.FindFolders($EWSParentFolder.Id,$SfSearchFilter,$fvFolderView) if ($findFolderResults.TotalCount -eq 0){ Write-host ("Folder Doesn't Exist") $NewFolder.Save($EWSParentFolder.Id) Write-host -ForegroundColor Green ("Folder [$NewFolderName] Created") } else{ Write-host -ForegroundColor DarkGray ("Folder [$NewFolderName] already exists.") } } }