Forum Discussion
Export to PST via Powershell
- Aug 10, 2017
No way to do it without going to the SCC and initializing the download via the click-one app, sorry. Perhaps you can automate it via AzCopy or some other tool that takes container/token as input - you can get those via the Result property of Get-ComplianceSearchAction.
As for the cmdlet, make sure you also use the -Format parameter!
The Format parameter specifies the format of the search results when you use the Export switch. Valid values are:
FxStream Export to PST files. This is the only option that's available when you export search results from the Security & Compliance Center.
Mime Export to .eml messsage files. This the default value when you use cmdlets to export the search results.It's most likely what causes the issue in your case.
Is this still working for you? Have you made any changes/improvements since you posted this?
How'd you get the container URL?
Thanks!
Yes, this still worked quite well as of a week ago. I can only see this breaking if Microsoft changes the format of the "Results" property of the content search or splits it into separate properties of their own.
I haven't made any real improvements to it, but this reminds me that I should probably combine my code with Brad's so that there's a minimally functional example. I'll edit my previous post at some time for that.
The container URL comes from the "$exportdetails =" line.
It first gets the compliance search, selects the Results property that contains the container URL, then parses the URL and token from the property using the $exporttemplate
The results property is just one giant string of information, instead of separate properties of their own, so parsing it with Convert-FromString was the easiest way I could think of to get those properties.
Convert-FromString requires a template consisting of at least two lines of data, formatted so that it knows what you're looking to parse. The data in $exporttemplate is just two of our old searches, modified with the parsing tags. Sorry if that's not incredibly clear.
- Tom AgueroOct 18, 2018Copper Contributor
Awesome, I got it working and I figured out a way to monitor the process along with the percent complete. Sorry, I'm not sure how to do the fancy formatting for this post.
#Do while microsoft.office.client.discovery.unifiedexporttool.exe running
$started = $false
Do { $status = Get-Process microsoft.office.client.discovery.unifiedexporttool -ErrorAction SilentlyContinue
If (!($status)) {Write-Host 'Waiting for process to start' ; Start-Sleep -Seconds 5 }
Else {
Write-Host 'Process has started' ; $started = $true
}
}Until ( $started )
Do{
$ProcessesFound = Get-Process | ? {$_.Name -like "*unifiedexporttool*"}
If ($ProcessesFound) { $Progress = get-ComplianceSearchAction -Identity $exportname -IncludeCredential -Details | select -ExpandProperty Results | ConvertFrom-String -TemplateContent $exporttemplate | %{$_.Progress}
Write-Host "Export still downloading, progress is $Progress, waiting 60 seconds"
Start-Sleep -s 60}
}Until (!$ProcessesFound)
- Edgar GuerreroApr 12, 2019Copper Contributor
Tom Aguero Can you share the whole script to download pst physically from EXO mailboxes
- Tom AgueroApr 12, 2019Copper Contributor
Edgar Guerrero Sure, I think I sanitized it
#region Connect to all the things#Test O365 ConnectionIf($Null -eq $O365Cred){$O365Cred = $Host.ui.PromptForCredential("","Enter your OFFICE 365 admin creds","ENTER YOUR ACCOUNT NAME HERE","")}Try{Get-O365Mailbox aguerot -ErrorAction Stop > $Null}Catch{$O365Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $O365Cred -Authentication Basic -AllowRedirectionImport-PSSession $O365Session –Prefix o365}Try{Get-MsolUser -UserPrincipalName ENTER TEST USER HERE -ErrorAction Stop > $Null}Catch{Connect-MsolService -Credential $O365Cred}Try{Get-ComplianceSearch > $Null}Catch{#Get login credentials$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid -Credential $O365Cred -Authentication Basic -AllowRedirectionImport-PSSession $Session -AllowClobber -DisableNameChecking}#endregion#Get User Info$User = Read-host "Enter user name"$Logfile = "PATH TO LOG FILE\$User.txt"Function LogWrite{Param ([string]$logstring)Write-Host $LogStringAdd-content $Logfile -value $logstring}$Date = Get-Date -Format "MM/dd/yyyy"LogWrite $DateLogWrite " "LogWrite "Username: $User"#Begin Compliance Search$UPN = Get-ADUser $User | ForEach-Object{$_.UserPrincipalName}$SearchName = $User + "_Termed"New-ComplianceSearch -Name $SearchName -ExchangeLocation $UPNStart-ComplianceSearch $SearchNameLogwrite " "LogWrite "Compliance search $SearchName started"Do{$complianceSearch = Get-ComplianceSearch $SearchName | ForEach-Object{$_.Status}Write-Host "Compliance Search in Progress"Start-Sleep -s 30}While ($complianceSearch -ne 'Completed')$Size = Get-ComplianceSearch $SearchName | ForEach-Object{$_.Size}$Size = $Size / 1048576 | Out-String$Size = $Size.SubString(0,6)# Create Compliance Search in exportable formatNew-ComplianceSearchAction -SearchName $SearchName -EnableDedupe $true -Export -Format FxStream -ArchiveFormat PerUserPST > $Null#Wait for Export to complete$ExportName = $SearchName + "_Export"Start-Sleep -s 20do{$SearchAction = Get-ComplianceSearchAction -Identity $ExportName | Select-Object Status,JobProgress$Status = $SearchAction.Status$ExportProgress = $SearchAction.JobProgressWrite-Host "Export in progress, $ExportProgress complete"If($Status -ne "Completed"){Start-Sleep -s 60}}while ($Status -ne 'Completed')LogWrite "Compliance search completed"$exportlocation = "C:\pst" #enter the path to your export here !NO TRAILING BACKSLASH!$exportexe = "\UnifiedExportTool\microsoft.office.client.discovery.unifiedexporttool.exe" #path to your microsoft.office.client.discovery.unifiedexporttool.exe file. Usually found somewhere in ForEach-ObjectLOCALAPPDATAForEach-Object\Apps\2.0\# Gather the URL and Token from the export in order to start the download#We only need the ContainerURL and SAS Token but I parsed some other fields as well while working with AzCopy#The Container URL and Token in the following template has been altered to protect the innocent:$exporttemplate = @'Container url: {ContainerURL*:https://xicnediscnam.blob.core.windows.net/da3fecb0-4ed4-447e-0315-08d5adad8a5a}; SAS token: {SASToken:?sv=2014-02-14&sr=c&si=eDiscoveryBlobPolicy9ForEach-Object7C0&sig=RACMSyH6Cf0k4EP2wZSoAa0QrhKaV38Oa9ciHv5Y8MkForEach-Object3D}; Scenario: General; Scope: BothIndexedAndUnindexedItems; Scope details: AllUnindexed; Max unindexed size: 0; File type exclusions for unindexed: <null>; Total sources: 2; Exchange item format: Msg; Exchange archive format: IndividualMessage; SharePoint archive format: SingleZip; Include SharePoint versions: True; Enable dedupe: EnableDedupe:True; Reference action: "<null>"; Region: ; Started sources: StartedSources:3; Succeeded sources: SucceededSources:1; Failed sources: 0; Total estimated bytes: 12,791,334,934; Total estimated items: 143,729; Total transferred bytes: {TotalTransferredBytes:7,706,378,435}; Total transferred items: {TotalTransferredItems:71,412}; Progress: {Progress:49.69 ForEach-Object}; Completed time: ; Duration: 00:50:43.9321895; Export status: {ExportStatus:DistributionCompleted}Container url: {ContainerURL*:https://zgrbediscnam.blob.core.windows.net/5c21f7c7-42a2-4e24-9e69-08d5acf316f5}; SAS token: {SASToken:?sv=2014-02-14&sr=c&si=eDiscoveryBlobPolicy9ForEach-Object7C0&sig=F6ycaX5eWcRBCS1Z5nfoTKJWTrHkAciqbYRP5ForEach-Object2FhsUOoForEach-Object3D}; Scenario: General; Scope: BothIndexedAndUnindexedItems; Scope details: AllUnindexed; Max unindexed size: 0; File type exclusions for unindexed: <null>; Total sources: 1; Exchange item format: FxStream; Exchange archive format: PerUserPst; SharePoint archive format: IndividualMessage; Include SharePoint versions: True; Enable dedupe: True; Reference action: "<null>"; Region: ; Started sources: 2; Succeeded sources: 2; Failed sources: 0; Total estimated bytes: 69,952,559,461; Total estimated items: 107,707; Total transferred bytes: {TotalTransferredBytes:70,847,990,489}; Total transferred items: {TotalTransferredItems:100,808}; Progress: {Progress:93.59 ForEach-Object}; Completed time: 4/27/2018 11:45:46 PM; Duration: 04:31:21.1593737; Export status: {ExportStatus:Completed}'@$exportdetails = Get-ComplianceSearchAction -Identity $exportname -IncludeCredential -Details | Select-Object -ExpandProperty Results | ConvertFrom-String -TemplateContent $exporttemplate$exportdetails$exportcontainerurl = $exportdetails.ContainerURL$exportsastoken = $exportdetails.SASToken# Download the exported files from Office 365Write-Host "Compliance Search Size:" $SizeLogWrite "Initiating download"LogWrite "Saving export to: $exportlocation"$arguments = "-name ""$searchname""","-source ""$exportcontainerurl""","-key ""$exportsastoken""","-dest ""$exportlocation""","-trace true"Start-Process -FilePath "$exportexe" -ArgumentList $arguments#Do while microsoft.office.client.discovery.unifiedexporttool.exe running$started = $falseDo {$status = Get-Process microsoft.office.client.discovery.unifiedexporttool -ErrorAction SilentlyContinueIf (!($status)) {Write-Host 'Waiting for process to start' ; Start-Sleep -Seconds 5}Else {Write-Host 'Process has started' ; $started = $true}}Until ( $started )Do{$Finished = $FalseWrite-host "Sleeping 300 seconds"Start-Sleep -s 300$DownloadSize = Get-Childitem "C:\pst" -recurse -include *.pst | ForEach-Object{$_.Length}$DownloadSize = $DownloadSize / 1048576 | Out-String$DownloadSize = $DownloadSize.SubString(0,6)Write-Host " "Write-Host "Compliance Search Size:" $SizeWrite-Host "Downloaded Size:" $DownloadSizeWrite-Host " "<## Commenting this out to test performance$ProcessesFound = Get-Process | ? {$_.Name -like "*unifiedexporttool*"}Write-Host "Process still running"If ($ProcessesFound) {$Progress = get-ComplianceSearchAction -Identity $exportname -IncludeCredential -Details | Select-Object -ExpandProperty Results | ConvertFrom-String -TemplateContent $exporttemplate | ForEach-Object{$_.Progress}Write-Host "Export still downloading, progress is $Progress, waiting 300 seconds"Start-Sleep -s 300}#>If($DownloadSize -ge $Size){$status = Get-Process microsoft.office.client.discovery.unifiedexporttool -ErrorAction SilentlyContinueIf (!($status)) {Write-Host "Download has finished"$Finished = $True}}}Until ($Finished -eq $True)$DownloadSize = Get-Childitem "C:\pst" -recurse -include *.pst | ForEach-Object{$_.Length}$DownloadSize = $DownloadSize / 1048576 | Out-String$DownloadSize = $DownloadSize.SubString(0,6)Write-Host " "Write-Host "Compliance Search Size:" $SizeWrite-Host "Downloaded Size:" $DownloadSizeWrite-Host " "#Read-Host "Pausing script until you verify download. Seriously. If the download isn't done this gets really bad. You sure you're ready? Press enter to continue"LogWrite "PST exported & downloaded"#Remove compliance search & export$ExportName = $SearchName + "_Export"Remove-ComplianceSearchAction -Identity $ExportName -Confirm:$FalseRemove-ComplianceSearch $SearchName -Confirm:$FalseIf($?){Write-Host "Compliance search & export removed"}#Get & Remove O365 licenses$UPN2 = Get-ADUser -Identity $User | ForEach-Object{$_.UserPrincipalName}$MSOLUser = Get-MsolUser -UserPrincipalName $UPN2 -ErrorAction SilentlyContinue#$IsLicensed = $MSOLUser.IsLicensed$Licenses = $MSOLUser.Licenses[0].AccountSkuIdSet-MsolUserLicense -UserPrincipalName $UPN2 -RemoveLicenses $LicensesIf($?){Write-Host "$User $Licenses removed"}#Move to Disabled Accts OUGet-AdUser $User | Move-AdObject -TargetPath "" #Moves to an OU that doesnt sync to O365LogWrite "$User account has been moved to Disabled Accts OU"#Run DirSync on Azure AD Connect sync serverInvoke-Command -Computername "" -ScriptBlock {C:\DirSync-ForceDelta.ps1} #Runs an Azure AD Connect Directory sync on a remote server#Move PST for uploadGet-Childitem "C:\pst" -recurse -include *.pst | Move-Item -Destination "C:\pst"#Upload mailbox to AzureWrite-Host "Uploading to Azure"Start-Process "Disable User Process\LaunchCmd-Custom.cmd" #Customized Azure Storage AZ copy upload script#Confirm upload to finishedwrite-host " "$confirmation = Read-Host "Did Azure upload complete successfully? You need to enter y here"if ($confirmation -eq 'y'){#Delete pstRemove-Item "C:\pst\*" -Recurse -Confirm:$FalseIf($?){Write-host " "Write-Host "PST Deleted"}}Also, here is the AZ copy script, you need to get your own key and destination info. I honestly forget how to get that.@echo offtitle Microsoft Azure Storage toolsset LaunchCmdFolder="C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy"set PATH=%PATH%;%LaunchCmdFolder%AzCopySet azcopy="C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy\AzCopy.exe"Set source="C:\pst"Set dest=set key=%azcopy% /source:%source% /Dest:%dest% /DestKey:%key% /Pattern:*.pst /S /V:C:\AZCopy.logPause
- JChupOct 18, 2018Brass Contributor
Oh nice! I forgot I parsed that and didn't even think about polling the server-side progress.
The only thing I can see being an issue is if it's a very large export, or you have a slow connection, your session with office 365 might timeout before it's done.
I'll try adding it to our implementation and see if I run into any problems.
Are you logging the entire session with a transcript? If not, I'd recommend writing the progress to a file so you can quickly check if it finished or not, in case your computer restarts for windows updates or something while you're not looking. "Write-Output $Progress | Out-File" is quick and easy, but you might want to include a timestamp as well.