# 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.")
}
}
}