#Variables $useAutodiscover = $false $serviceUrl = 'https://outlook.office365.com/ews/exchange.asmx' #requires -version 3 #Check for EWS API $apiPath = (($(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 $apiPath) { Add-Type -Path $apiPath } else { Write-Error "The Exchange Web Services Managed API is required to use this module." -Category NotInstalled break } #MAPI properties being referenced $propFolderPath = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition( 26293,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String) $propLastParentFid = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition( 0x348A,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary) $propStoreRecordKey = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition( 0x0FFA,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary) $propPolicyTag = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition( 0x3019,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary) #region Helper Functions function New-ExchangeServiceObject { $exchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1 $exchangeService = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExchangeService($exchangeVersion) if ($Credential) { $exchangeService.Credentials = New-Object -TypeName Microsoft.Exchange.WebServices.Data.WebCredentials($Credential) } if ($useAutodiscover) { #Set to true (or comment out) to first try SCP, then DNS $exchangeService.EnableScpLookup = $false Write-Verbose -Message "Performing autodiscover lookup." $exchangeService.AutodiscoverUrl($Identity,{$true}) } else { $exchangeService.Url = $serviceUrl } Write-Verbose -Message ("Autodiscover URL: " + $exchangeService.Url) if ($UseImpersonation) { $exchangeService.ImpersonatedUserId = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ImpersonatedUserId( [Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $Identity) } return $exchangeService } function Get-FolderEnum ($folder) { switch ($folder) { 'RecoverableItems' {return [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsDeletions} 'Purges' {return [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsPurges} 'ArchiveRecoverableItems' {return [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveRecoverableItemsDeletions} 'ArchivePurges' {return [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveRecoverableItemsPurges} } } function New-ItemView { $itemView = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView( $pageSize,$offset,[Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning) $itemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Shallow #Sort results by newest item first $itemView.OrderBy.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::LastModifiedTime, [Microsoft.Exchange.WebServices.Data.SortDirection]::Descending) #Keep properties to return to a minimum for faster results $itemView.PropertySet = New-Object -TypeName Microsoft.Exchange.WebServices.Data.PropertySet( [Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly, [Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass, [Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject, [Microsoft.Exchange.WebServices.Data.ItemSchema]::LastModifiedTime, $propLastParentFid, $propStoreRecordKey ) $itemView } function New-SearchFilter { $searchFilterCollection = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection( [Microsoft.Exchange.WebServices.Data.LogicalOperator]::And) if ($FilterStartTime) { $searchFilterStartTime = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan( [Microsoft.Exchange.WebServices.Data.ItemSchema]::LastModifiedTime,$FilterStartTime) $searchFilterCollection.Add($searchFilterStartTime) } if ($FilterEndTime) { $searchFilterEndTime = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThan( [Microsoft.Exchange.WebServices.Data.ItemSchema]::LastModifiedTime,$FilterEndTime) $searchFilterCollection.Add($searchFilterEndTime) } if ($SubjectContains) { $searchFilterSubject = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring( [Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject,$SubjectContains) $searchFilterCollection.Add($searchFilterSubject) } if ($RetentionTagGuid) { $searchFilterTag = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( $propPolicyTag, ([guid]$RetentionTagGuid).ToByteArray()) $searchFilterCollection.Add($searchFilterTag) } if ($FilterItemType) { $searchFilterClass = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( [Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,$FilterItemType) $searchFilterCollection.Add($searchFilterClass) } if ($EntryId) { #Convert hex-formatted ID the cmdlet shows in the output to the format used by EWS $EwsId = Convert-EntryId -Identity $Identity -Id $EntryId -sourceFormat HexEntryId -targetFormat EwsId $searchFilterEntryId = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( [Microsoft.Exchange.WebServices.Data.ItemSchema]::Id,$EwsId) $searchFilterCollection.Add($searchFilterEntryId) } if ($LastParentFolderId) { #Search for the truncated folder ID that is stored in the item $b64Fid = [System.Convert]::ToBase64String((Convert-HexToByteArray -string $LastParentFolderId)) $searchFilterLastFid = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( $propLastParentFid,$b64Fid) $searchFilterCollection.Add($searchFilterLastFid) } return ,$searchFilterCollection } function Bind-MailboxFolder ($emailAddress, $folderName, $folderEntryId, $service) { if ($folderEntryId) { Write-Verbose -Message "Defining folder ID based on folder entry ID." $folderID = New-Object -TypeName Microsoft.Exchange.WebServices.Data.FolderId($folderEntryId) } else { $folderID = New-Object -TypeName Microsoft.Exchange.WebServices.Data.FolderId($folderName,$emailAddress) } $ps = New-Object -TypeName Microsoft.Exchange.WebServices.Data.PropertySet( [Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly, [Microsoft.Exchange.WebServices.Data.FolderSchema]::WellKnownFolderName, $propFolderPath) [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderID,$ps) } #Convert MAPI entry IDs between formats function Convert-EntryId ($Identity, $Id, $sourceFormat, $targetFormat) { $altId = New-Object -TypeName Microsoft.Exchange.WebServices.Data.AlternateId $altId.Mailbox = $Identity $altId.UniqueId = $Id $altId.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::$sourceFormat $convertedId = $exchangeService.ConvertId($altId, [Microsoft.Exchange.WebServices.Data.IdFormat]::$targetFormat) $convertedId.UniqueId } function Convert-HexToByteArray ($string) { $byteArray = New-Object -Type Byte[] -ArgumentList ($string.Length/2) for ($i=0; $i -lt $string.Length; $i+=2) { $byteArray[$i/2] = [System.Convert]::ToByte($string.Substring($i, 2), 16) } $byteArray } function Get-FolderPath ($folder) { $folderPathPropValue = $null [void]$folder.TryGetProperty($propFolderPath,[ref]$folderPathPropValue) if ($folder.WellKnownFolderName -like 'Archive*') { $pathPrefix = 'Archive\' } #Replace non-printable separator with backslash return $pathPrefix + $folderPathPropValue.Replace([char][int]65534,'\').Substring(1) } #endregion #region Main Functions function Get-MailboxRecoverableItems { <# .Synopsis Search a mailbox for items that are no longer visible to the end user but can still be restored .Description Search a mailbox's recoverable items folder (Deletions and Purges), as well as the archive mailbox's respective folders, for items to potentially restore. The search can be filtered on modified date, item class, subject, retention tag, the item's entry ID, and the folder ID the folder the item was deleted from (if stored on the item). .Parameter Identity Email address of the mailbox to search. Supports piping the output of Get-Mailbox (or any object with a PrimarySMTPAddress property) to this cmdlet. .Parameter SourceFolder Folders in which to search for items. Valid values are RecoverableItems, Purges, ArchiveRecoverableItems, and ArchivePurges. Default value is RecoverableItems. .Parameter FilterStartTime Search for items where the last modified date is after this. .Parameter FilterEndTime Search for items where the last modified date is before this. .Parameter SubjectContains Search for items where the subject contains the provided string. .Parameter RetentionTagGuid GUID of the retention tag stamped on the item. Dashes and curly braces are optional, but if curly braces are used, dashes are required. .Parameter ResultSize Maximum number of items to include the output. Default is no limit. .Parameter EntryId The hex-formatted entry ID that uniquely identifies the item. .Parameter LastParentFolderId The hex-formatted entry ID of the folder that the item was in when it was deleted. Not all items have this value stamped on them. The value of this property does not contain the complete entry ID of the original folder, so it is recommended to first get the value from an object returned with Get-MailboxRecoverableItems. .Parameter FilterItemType Item class to restrict the search to. Valid value is IPM.Note, IPM.Appointment, IPM.Contact, or IPM.Task. .Parameter Credential Credential object of a user that has permission to the mailbox. If searching an Exchange Online mailbox, this parameter is required. .Parameter UseImpersonation Switch to indicate that impersonation should be used instead of full or delegate mailbox access. .Example Get-MailboxRecoverableItems -Identity johndoe@company.com -SubjectContains 'upcoming meeting' -Credential (Get-Credential) .Example Get-Mailbox | Get-MailboxRecoverableItems -RetentionTagGuid e597622d-26a9-4eee-a0ee-96918e21c4d3 -SourceFolder Purges .Notes Version: 1.0.1 Date: 9/27/18 #> [CmdletBinding()] param ( [parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)][Alias("PrimarySMTPAddress")][string]$Identity, [parameter(Mandatory=$false)][ValidateSet('RecoverableItems','Purges','ArchiveRecoverableItems','ArchivePurges')] [string[]]$SourceFolder = @('RecoverableItems'), [parameter(Mandatory=$false)][DateTime]$FilterStartTime, [parameter(Mandatory=$false)][DateTime]$FilterEndTime, [parameter(Mandatory=$false)][string]$SubjectContains, [parameter(Mandatory=$false)][ValidateScript({$_ -as [guid]})][string]$RetentionTagGuid, [parameter(Mandatory=$false)][int]$ResultSize = [int]::MaxValue, [parameter(Mandatory=$false)][string]$EntryId, [parameter(Mandatory=$false)][string]$LastParentFolderId, [parameter(Mandatory=$false)][ValidateSet('IPM.Note','IPM.Contact','IPM.Appointment','IPM.Task')][string]$FilterItemType, [parameter(Mandatory=$false)][pscredential]$Credential, [parameter(Mandatory=$false)][switch]$UseImpersonation ) $exchangeService = New-ExchangeServiceObject $resultCount = 0 foreach ($folder in $SourceFolder) { $wellKnownFolder = Get-FolderEnum -folder $folder Write-Verbose -Message "Binding to source folder $wellKnownFolder." try { $baseFolder = Bind-MailboxFolder -emailAddress $Identity -folderName $wellKnownFolder -service $exchangeService } catch { #Well known folder doesn't exist, such as no archive mailbox for user if ($_.Exception -like '*specified folder could not be found in the store*') { Write-Verbose -Message "Cannot bind to $wellKnownFolder because it does not exist." } continue } Write-Verbose -Message "Getting source folder path." $sourceFolderPath = Get-FolderPath -folder $baseFolder $pageSize = 50 $offset = 0 $moreItems = $true $itemView = New-ItemView $searchFilter = New-SearchFilter Write-Verbose -Message "Beginning search operation." while ($moreItems) { if ($searchFilter.Count -ge 1) { $searchResult = $baseFolder.FindItems($searchFilter,$itemView) } else { $searchResult = $baseFolder.FindItems($itemView) } foreach ($item in $searchResult.Items) { $noLastParentFolder = $null $cannotRetrieve = $null $outputObject = '' | Select-Object -Property MailboxIdentity,ItemClass,Subject,EntryId,SourceFolder, LastParentFolderId,LastModifiedTime,LastParentPath,OriginalFolderExists $outputObject.MailboxIdentity = $Identity $outputObject.ItemClass = $item.ItemClass $outputObject.Subject = $item.Subject $outputObject.EntryId = Convert-EntryId -identity $Identity -Id $item.Id.UniqueId -sourceFormat EwsId -targetFormat HexEntryId $outputObject.SourceFolder = $sourceFolderPath $shortLastFolderId = $null if ($item.TryGetProperty($propLastParentFid,[ref]$shortLastFolderId)) { $hexShortLastFolderId = ($shortLastFolderId | ForEach-Object {$_.ToString("X2")}) -join "" $outputObject.LastParentFolderId = $hexShortLastFolderId } $outputObject.LastModifiedTime = $item.LastModifiedTime #Because EWS returns incorrect folder entry ID when converting to EwsId #for a folder in the archive mailbox, treat as if original folder does not exist if ($outputObject.LastParentFolderId.Length -gt 0 -and $folder -notlike 'Archive*') { try { $storeRecordKey = $null [void]$item.TryGetProperty($propStoreRecordKey,[ref]$storeRecordKey) $hexStoreRecordKey = ($storeRecordKey | ForEach-Object {$_.ToString("X2")}) -join "" #Value stored in last folder ID property is only 22 of the full 46-byte value needed #Build complete folder entry ID [string]$fId = '00000000' + $hexStoreRecordKey + '0100' + $hexShortLastFolderId + '0000' $lastParentFolder = Bind-MailboxFolder -emailAddress $Identity ` -folderEntryId (Convert-EntryId -Identity $Identity -Id $fId -SourceFormat HexEntryId -TargetFormat EwsId) ` -service $exchangeService $folderPath = Get-FolderPath -folder $lastParentFolder $outputObject.OriginalFolderExists = $true } catch { #if ($_.Exception -like '*object was not found in the store*') #Failure to get last parent folder means folder no longer exists $cannotRetrieve = $true } } else {$noLastParentFolder = $true} if ($noLastParentFolder -or $cannotRetrieve) { #If no last parent folder, populate value of parent path as the default folder #of the item class switch ($item.ItemClass) { 'IPM.Note' {$folderPath = 'Inbox'} 'IPM.Contact' {$folderPath = 'Contacts'} 'IPM.Appointment' {$folderPath = 'Calendar'} 'IPM.Task' {$folderPath = 'Tasks'} #Put all others in Inbox, such as NDRs, delivery reports default {$folderPath = 'Inbox'} } if ($cannotRetrieve) { #Only state original folder doesn't exist if last folder value #populated but couldn't retrieve because it has been deleted $outputObject.OriginalFolderExists = $false } } #Last parent path is always populated, and means where the #item will be placed if it is recovered #Product Group should really have named it something like RecoveryPath $outputObject.LastParentPath = $folderPath Write-Output -InputObject $outputObject $resultCount++ if ($resultCount -ge $ResultSize) { Write-Warning -Message 'There are more results available than are currently displayed. To view them, increase the value for the ResultSize parameter.' break } } if ($searchResult.MoreAvailable -eq $false) {$moreItems = $false} if ($moreItems) {$offset += $pageSize} } } } function Restore-MailboxRecoverableItems { <# .Synopsis Search a mailbox for items that are no longer visible to the end user and restore them .Description Search a mailbox's recoverable items folder (Deletions and Purges), as well as the archive mailbox's respective folders, and restore them. If applicable, they will be restored to the folder they were in when deleted. Otherwise, they will be restored to the default folder for the respective item class. Recoverable items in an archive mailbox will always be restored to the priomary mailbox's default folder for the item class. The search can be filtered on modified date, item class, subject, retention tag, the item's entry ID, and the folder ID the folder the item was deleted from (if stored on the item). .Parameter Identity Email address of the mailbox. Supports piping the output of Get-Mailbox (or any object with a PrimarySMTPAddress property) to this cmdlet. .Parameter SourceFolder Folders in which to search for items. Valid values are RecoverableItems, Purges, ArchiveRecoverableItems, and ArchivePurges. Default value is RecoverableItems. .Parameter FilterStartTime Search for items where the last modified date is after this. .Parameter FilterEndTime Search for items where the last modified date is before this. .Parameter SubjectContains Search for items where the subject contains the provided string. .Parameter RetentionTagGuid GUID of the retention tag stamped on the item. Dashes and curly braces are optional, but if curly braces are used, dashes are required. .Parameter ResultSize Maximum number of items to attempt to restore. Default is no limit. .Parameter EntryId The hex-formatted entry ID that uniquely identifies the item. .Parameter LastParentFolderId The hex-formatted entry ID of the folder that the item was in when it was deleted. Not all items have this value stamped on them. The value of this property does not contain the complete entry ID of the original folder, so it is recommended to first get the value from an object returned with Get-MailboxRecoverableItems. .Parameter FilterItemType Item class to restrict the search to. Valid value is IPM.Note, IPM.Appointment, IPM.Contact, or IPM.Task. .Parameter Credential Credential object of a user that has permission to the mailbox. If searching an Exchange Online mailbox, this parameter is required. .Parameter UseImpersonation Switch to indicate that impersonation should be used instead of full or delegate mailbox access. .Example Restore-MailboxRecoverableItems -Identity johndoe@company.com -EntryId 000000007725A2D01B1A9F4EB72163C64014CE4607001C53DD0FE0004A4B8E17F5475F15AC6B00000000011A00001C53DD0FE0004A4B8E17F5475F15AC6B0000D26ADC1C0000 -Credential (Get-Credential) .Example Get-Mailbox | Restore-MailboxRecoverableItems -RetentionTagGuid e597622d-26a9-4eee-a0ee-96918e21c4d3 -SourceFolder Purges .Notes Version: 1.0.1 Date: 9/27/18 #> [CmdletBinding()] param ( [parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)][Alias("PrimarySMTPAddress")][string]$Identity, [parameter(Mandatory=$false)][ValidateSet('RecoverableItems','Purges','ArchiveRecoverableItems','ArchivePurges')] [string[]]$SourceFolder = @('RecoverableItems'), [parameter(Mandatory=$false)][DateTime]$FilterStartTime, [parameter(Mandatory=$false)][DateTime]$FilterEndTime, [parameter(Mandatory=$false)][string]$SubjectContains, [parameter(Mandatory=$false)][ValidateScript({$_ -as [guid]})][string]$RetentionTagGuid, [parameter(Mandatory=$false)][int]$ResultSize = [int]::MaxValue, [parameter(Mandatory=$false)][string]$EntryId, [parameter(Mandatory=$false)][string]$LastParentFolderId, [parameter(Mandatory=$false)][ValidateSet('IPM.Note','IPM.Contact','IPM.Appointment','IPM.Task')][string]$FilterItemType, [parameter(Mandatory=$false)][pscredential]$Credential, [parameter(Mandatory=$false)][switch]$UseImpersonation ) $exchangeService = New-ExchangeServiceObject $resultCount = 0 foreach ($folder in $SourceFolder) { $wellKnownFolder = Get-FolderEnum -folder $folder Write-Verbose -Message "Binding to source folder $wellKnownFolder." try { $baseFolder = Bind-MailboxFolder -emailAddress $Identity -folderName $wellKnownFolder -service $exchangeService } catch { #Well known folder doesn't exist, such as no archive mailbox for user if ($_.Exception -like '*specified folder could not be found in the store*') { Write-Verbose -Message "Cannot bind to $wellKnownFolder because it does not exist." } continue } Write-Verbose -Message "Getting source folder path." $sourceFolderPath = Get-FolderPath -folder $baseFolder $pageSize = 50 $offset = 0 $moreItems = $true $itemView = New-ItemView $searchFilter = New-SearchFilter Write-Verbose -Message "Beginning search operation." while ($moreItems) { if ($searchFilter.Count -ge 1) { $searchResult = $baseFolder.FindItems($searchFilter,$itemView) } else { $searchResult = $baseFolder.FindItems($itemView) } foreach ($item in $searchResult.Items) { $noLastParentFolder = $null $cannotRetrieve = $null $outputObject = '' | Select-Object -Property MailboxIdentity,ItemClass,Subject,EntryId,SourceFolder, RestoreToFolderId,LastModifiedTime,WasRestoredToOriginalFolder,WasRestoredSuccessfully,ReturnedToFilePath $outputObject.MailboxIdentity = $Identity $outputObject.ItemClass = $item.ItemClass $outputObject.Subject = $item.Subject $outputObject.EntryId = Convert-EntryId -identity $Identity -Id $item.Id.UniqueId -sourceFormat EwsId -targetFormat HexEntryId $outputObject.SourceFolder = $sourceFolderPath $shortLastFolderId = $null if ($item.TryGetProperty($propLastParentFid,[ref]$shortLastFolderId)) { $hexShortLastFolderId = ($shortLastFolderId | ForEach-Object {$_.ToString("X2")}) -join "" $outputObject.RestoreToFolderId = $hexShortLastFolderId } $outputObject.LastModifiedTime = $item.LastModifiedTime $originalFolderExists = $null #Because EWS returns incorrect folder entry ID when converting to EwsId #for a folder in the archive mailbox, treat as if original folder does not exist if ($outputObject.RestoreToFolderId.Length -gt 0 -and $folder -notlike 'Archive*') { try { $storeRecordKey = $null [void]$item.TryGetProperty($propStoreRecordKey,[ref]$storeRecordKey) $hexStoreRecordKey = ($storeRecordKey | ForEach-Object {$_.ToString("X2")}) -join "" #Value stored in last folder ID property is only 22 of the full 46-byte value needed #Build complete folder entry ID [string]$fId = '00000000' + $hexStoreRecordKey + '0100' + $hexShortLastFolderId + '0000' $lastParentFolder = Bind-MailboxFolder -emailAddress $Identity ` -folderEntryId (Convert-EntryId -Identity $Identity -Id $fId -SourceFormat HexEntryId -TargetFormat EwsId) ` -service $exchangeService $folderPath = Get-FolderPath -folder $lastParentFolder $originalFolderExists = $true $outputObject.WasRestoredToOriginalFolder = $true } catch { #if ($_.Exception -like '*object was not found in the store*') #Failure to get last parent folder means folder no longer exists $cannotRetrieve = $true $originalFolderExists = $false } } else { $noLastParentFolder = $true $originalFolderExists = $false } $folderId = $null if ($noLastParentFolder -or $cannotRetrieve) { #If no last parent folder, populate value of parent path as the default folder #of the item class switch ($item.ItemClass) { 'IPM.Note' { $folderPath = 'Inbox' $folderId = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox } 'IPM.Contact' { $folderPath = 'Contacts' $folderId = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts } 'IPM.Appointment' { $folderPath = 'Calendar' $folderId = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar } 'IPM.Task' { $folderPath = 'Tasks' $folderId = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Tasks } #Put all others in Inbox, such as NDRs, delivery reports default { $folderPath = 'Inbox' $folderId = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox } } $outputObject.WasRestoredToOriginalFolder = $false } $outputObject.ReturnedToFilePath = $folderPath #Restore item if (-not($folderId)) { $folderId = New-Object -TypeName Microsoft.Exchange.WebServices.Data.FolderId( Convert-EntryId -Identity $Identity -Id $fId -SourceFormat HexEntryId -TargetFormat EwsId) } try { [void]$item.Move($folderId) $outputObject.WasRestoredSuccessfully = $true } catch { Write-Error -ErrorRecord $_ $outputObject.WasRestoredSuccessfully = $false } Write-Output -InputObject $outputObject $resultCount++ if ($resultCount -ge $ResultSize) { Write-Warning -Message 'There are more results available than are currently displayed. To view them, increase the value for the ResultSize parameter.' break } } if ($searchResult.MoreAvailable -eq $false) {$moreItems = $false} if ($moreItems) {$offset += $pageSize} } } } #endregion Export-ModuleMember -Function Get-MailboxRecoverableItems, Restore-MailboxRecoverableItems