4

We have a exchange hybrid environment. (some users are in onpremises exchange and some users are office 365. )

When we want to export emails from onpremis we use , below command to export mail box and archive.

 New-MailboxExportRequest -Mailbox "user" -FilePath \\mysrv\l$\PST\Mailbox-user.pst; New-MailboxExportRequest -Mailbox "user" -IsArchive  -FilePath \\mysrv\l$\PST\Mailbox-user-archive.pst -confirm:$false

New-MailboxExportRequest works fine for onpremis users not for office 365. is there a way to export office 365 user mail box to pst using powershell ?

What i have tried so far:

I logged to office 365

$UserCredential = Get-Credential
Import-Module MSOnline

Connect-MsolService -Credential $UserCredential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication  Basic -AllowRedirection

Import-PSSession $Session

and tried New-MailboxExportRequest

But it generates an error. apparently office 365 dont know that command

PS C:\Users\pp> New-MailboxExportRequest
New-MailboxExportRequest : The term 'New-MailboxExportRequest' is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is
correct and try again.
At line:1 char:1
+ New-MailboxExportRequest
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (New-MailboxExportRequest:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Also Get-Command *Export* results are as follows

enter image description here

Tried googling, yet could not find a viable option. can some one guide me please ? What is the compatible command in office environment to do this ?

PS: I have tried https://www.codetwo.com/admins-blog/how-to-export-office-365-mailboxes-to-pst-using-ediscovery/ with E5 license and it works in the GUI perfectly. but my concern is even with e-discovery, and having license any possibility to do it with powershell ? i mean to script/automate via powershell ?

user879
  • 269
  • 2
  • 6
  • 21
  • 1
    can be done via ediscovery though, https://www.codetwo.com/admins-blog/how-to-export-office-365-mailboxes-to-pst-using-ediscovery/ – Aravinda May 10 '18 at 08:14
  • need additional licences .. such as E5 https://support.office.com/en-us/article/set-up-users-and-cases-in-office-365-advanced-ediscovery-60ffd80b-4376-419d-b6e4-a72029b9907c – Aravinda May 11 '18 at 10:10
  • Even with e-discovery, and having license any possibility to do it with powershell ? i mean to script/automate via powershell ? – user879 May 11 '18 at 10:18

2 Answers2

5

You can't export Exchange Online mailbox directly to PST via PowerShell with built-in tools. Required New-MailboxExportRequest doesn't exist Online (or is not exposed to us mortals).

You can:

  • eDiscovery, that seems to be GUI-only.
  • Offload/migrate mailbox to on-prem Exchange and run New-MailboxExportRequest on-prem (and migrate back to Exchange Online if needed)
  • Use various 3rd party tools, that perform export via EWS or MAPI
  • Script delegation full access, Outlook bind to delegated mailbox and export to PST. Technically very likely possible but I've never seen anyone do it. I haven't looked deeply into eDiscovery but I believe that's more-less how eDiscovery does export to PST (Old Exchanges also used to bind to Outlook for PST export). But without significant MAPI experience, Outlook COM model is quite complex to use (I've done some Outlook scripting but emulating PST export is challenging to say the least).

I'm as frustrated as you are. Exporting mailboxes for departing users for long-term storage is needlessly annoying. If we could just export to Azure Blob just as with import, it'd be a good start.

Don Zoomik
  • 1,458
  • 9
  • 12
  • It seems Microsoft is so stingy to enable powershell method. – user879 May 15 '18 at 18:27
  • veem office 365 backup seems promising, unlike ms ediscovery its pretty user friendly. https://www.veeam.com/backup-microsoft-office-365.html im trying a trial of it now.. anyway thanks for the answer! – user879 May 17 '18 at 08:35
0

This is not an answer to the question: I have yet to try exporting directly from Exchange Online to PST.

This is meant as a supplement to the last point on @DonZoomiks answer. A bit of Powershell, Frankensteined from various obscure sources and which pulls mail from Exchange Online. Customize as needed.

The product is a custom object containing retrieved mail. Perhaps someone can work out and share how to go from there to PST without passing Outlook?

A requirement is Exchange Web Services (EWS) Managed API 2.2

So first 2 functions:

function Enter-ExchangeOnlineSession {
  <#
      .Synopsis
      Sets up a user session with ExchangeOnline, typically to fetch mail.
      .DESCRIPTION
      Sets up a user session with ExchangeOnline, typically to fetch mail.
      .EXAMPLE
      $CredMail = Get-Credential -UserName user@domain.com
      $Service = Enter-ExchangeOnlineSession -Credential $CredMail -MailDomain domain.com
      .INPUTS
      Inputs to this cmdlet (if any)
      .OUTPUTS
      Output from this cmdlet (if any)
      .NOTES
      General notes
      .COMPONENT
      The component this cmdlet belongs to
      .ROLE
      The role this cmdlet belongs to
      .FUNCTIONALITY
      The functionality that best describes this cmdlet
  #>
  Param(
    [Parameter(ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true,
        Position=0)]
    [ValidateNotNullOrEmpty()]
    [string]$EWSAssemblyPath = 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll',

    # Parameter Credential
    [Parameter(Mandatory=$true, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=1)]
    [System.Management.Automation.CredentialAttribute()]
    $Credential,

    # Parameter Maildomain
    [Parameter(Mandatory=$true, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=2)]
    [string]$MailDomain,

    # Parameter Maildomain
    [Parameter(Mandatory=$false, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=3)]
    [string]$AutoDiscoverCallbackUrl = 'https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml'

  )

  # Set the path to your copy of EWS Managed API and load the assembly.
  [void][Reflection.Assembly]::LoadFile($EWSAssemblyPath) 

  # Establish the session and return the service connection object.
  $service =  New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
  $Service.Credentials = New-Object System.Net.NetworkCredential($Credential.UserName, $Credential.GetNetworkCredential().Password, $MailDomain)

  $TestUrlCallback = {
    param ([string] $url)
    if ($url -eq $AutoDiscoverCallbackUrl) {$true} else {$false}
  }
  $service.AutodiscoverUrl($Credential.UserName, $TestUrlCallback)

  return $Service
}

and

function Get-ExchangeOnlineMailContent {
  <#
      .Synopsis
      Fetches content from an Exchange Online user session, typically mail.
      .DESCRIPTION
      Fetches content from an Exchange Online user session, typically mail.
      .EXAMPLE
      $Service = Enter-ExchangeOnlineSession -Credential $CredMail -MailDomain example.com
      Get-ExchangeOnlineMailContent -ServiceObject $Service -PageSize 5 -Offset 0 -PageIndexLimit 15 -WellKnownFolderName Inbox
      .INPUTS
      Inputs to this cmdlet (if any)
      .OUTPUTS
      Output from this cmdlet (if any)
      .NOTES
      General notes
      .COMPONENT
      The component this cmdlet belongs to
      .ROLE
      The role this cmdlet belongs to
      .FUNCTIONALITY
      The functionality that best describes this cmdlet
  #>

  Param(
    [Parameter(Mandatory=$true, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=0)]
    $ServiceObject,

    # Define paging as described in https://msdn.microsoft.com/en-us/library/office/dn592093(v=exchg.150).aspx
    [Parameter(Mandatory=$true, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=1)]
    [int]$PageSize,

    [Parameter(Mandatory=$true, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=2)]
    [int]$Offset,

    #Translates into multiples of $PageSize
    [Parameter(Mandatory=$true, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=3)]
    [int]$PageIndexLimit,

    # WellKnownFolderNames doc https://msdn.microsoft.com/en-us/library/microsoft.exchange.webservices.data.wellknownfoldername(v=exchg.80).aspx
    [Parameter(Mandatory=$true, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=4)]
    [ValidateSet('Calendar',
                'Contacts',
                'DeletedItems',
                'Drafts',
                'Inbox',
                'Journal',
                'Notes',
                'Outbox',
                'SentItems',
                'Tasks',
                'MsgFolderRoot',
                'PublicFoldersRoot',
                'Root',
                'JunkEmail',
                'SearchFolders',
                'VoiceMail',
                'RecoverableItemsRoot',
                'RecoverableItemsDeletions',
                'RecoverableItemsVersions',
                'RecoverableItemsPurges',
                'ArchiveRoot',
                'ArchiveMsgFolderRoot',
                'ArchiveDeletedItems',
                'ArchiveRecoverableItemsRoot',
                'ArchiveRecoverableItemsDeletions',
                'ArchiveRecoverableItemsVersions',
                'ArchiveRecoverableItemsPurges',
                'SyncIssues',
                'Conflicts',
                'LocalFailures',
                'ServerFailures',
                'RecipientCache',
                'QuickContacts',
                'ConversationHistory',  
                'ToDoSearch')]
    [string]$WellKnownFolderName,

    [Parameter(Mandatory=$false, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=5)]
    [switch]$ParseOriginalRecipient,

    [Parameter(Mandatory=$false, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=6)]
    [switch]$OriginalRecipientAddressOnly,

    [Parameter(Mandatory=$false, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=7)]
    [datetime]$MailFromDate,

    [Parameter(Mandatory=$false, 
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true, 
        ValueFromRemainingArguments=$false, 
        Position=8)]
    [switch]$ConsoleOutput
  )

  # Create Property Set to include body and header, set body to text.
  $PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
  $PropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text

  if ($ParseOriginalRecipient)
  {
    $PR_TRANSPORT_MESSAGE_HEADERS = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x007D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
    $PropertySet.add($PR_TRANSPORT_MESSAGE_HEADERS)
  }

  $PageIndex = 0

  # Page through folder.
  do 
  { 
    # Limit the view to $pagesize number of email starting at $Offset.
    $PageView = New-Object Microsoft.Exchange.WebServices.Data.ItemView($PageSize,$PageIndex,$Offset)

    # Get folder data.
    $FindResults = $ServiceObject.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::$WellKnownFolderName,$PageView) 
    foreach ($MailItem in $FindResults.Items)
    {
      # Load extended properties.
      $MailItem.Load($propertySet)

      if ($ParseOriginalRecipient)
      {
        # Extended properties are one string, split by linebreak then find the line beginning with 'To:', containing original recipient address before exchange aliasing replaces it.
        $OriginalRecipientStringRaw = ($MailItem.ExtendedProperties.value.split([Environment]::NewLine) | Where-Object {$_ -match '^To:'}).trimstart('To:').trim()

        $MailItem | Add-Member -NotePropertyName ExtendedHeader -NotePropertyValue $OriginalRecipientStringRaw

        if ($OriginalRecipientAddressOnly)
        {
          # Removes everything but the address 'my.name@example.com' when string has form of e.g. '"My Name" <my.name@example.com>'
          $MailItem | Add-Member -NotePropertyName OriginalRecipient -NotePropertyValue ($OriginalRecipientStringRaw | ForEach-Object {($_.ToString() -creplace '^[^<]*<', '').trimend('>')})
        }
        else
        {
          $MailItem | Add-Member -NotePropertyName OriginalRecipient -NotePropertyValue $OriginalRecipientStringRaw
        }
        if ($ConsoleOutput) {write-host -ForegroundColor Cyan "$($MailItem.DateTimeReceived) | $($MailItem.OriginalRecipient) | $($MailItem.Sender)"}
      }

      # Output result.
      $MailItem | Select-Object -Property *
    } 

    # Increment $index to next page.
    $PageIndex += $PageSize
  } while (($FindResults.MoreAvailable) `
            -and ($PageIndex -lt $PageIndexLimit) `
            -and ($MailItem.DateTimeReceived -gt $MailFromDate)) # Do/While there are more emails to retrieve and pagelimit is not exceeded and datetimereceived is later than date.
}

then one can:

$Cred = Get-Credential #Example user: user@example.com
$domain = 'example.com'

# Set how many emails we want to read at a time
$PageSizeNumOfEmails = 10
$OffSet = 0
$PageIndexLimit = 2000
$MailFolder = 'Inbox'
$DeltaTimeStamp = (Get-Date).AddDays(-30) #Go how many days back?

try
{
  $Session = Enter-ExchangeOnlineSession -Credential $Cred -MailDomain $domain
}
catch
{
  $Message = $_.exception.message
  Write-Host -ForegroundColor Yellow $Message
}

$Mails = Get-ExchangeOnlineMailContent -ServiceObject $Session `
                                        -PageSize $PageSizeNumOfEmails `
                                        -Offset $OffSet `
                                        -PageIndexLimit $PageIndexLimit `
                                        -WellKnownFolderName $MailFolder `
                                        -ParseOriginalRecipient `
                                        -OriginalRecipientAddressOnly `
                                        -MailFromDate $DeltaTimeStamp | Where-Object {$_.DateTimeReceived -gt $DeltaTimeStamp} | select *

and finally process $Mails as you will.

ErikE
  • 4,676
  • 1
  • 19
  • 25