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.
Edgar Guerrero Sure, I think I sanitized it
Hi, Tom Aguero Thanks for the help so far.
I am not able to start the export.
From the tracert file i got this message:
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Office.Client.Discovery.UnifiedExport, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.
Do I need to do something with the Microsoft.Office.Client.Discovery.UnifiedExportTool.application file or should it work with Container url and SAS token?
I also fund this (he has stolen some part from you). Still no luck.
https://www.reddit.com/r/PowerShell/comments/ba4fpu/automated_download_of_o365_inbox_archive/
Any help is appreciated
- SystemEngineerFeb 06, 2024Iron Contributor
Would you be able to share the link for your PS module?
- tylermontney_accSep 21, 2022Brass Contributor
Honestly, I think converting to a Shared Mailbox and avoiding PSTs all together is the best idea. However, in the event you can't, the Outlook Interop seems the best alternative for now...
function Add-PSOutlookStore([string]$StoreFilePath) { [void]($comOutlookNS.AddStore($StoreFilePath)) } function Remove-PSOutlookStore([string]$StoreFilePath) { [void]($comOutlookNS.RemoveStore($StoreFilePath)) } function Export-PSOutlookInbox([string]$EmailAddress, [string]$DestinationFilePath) { Add-PSOutlookStore($DestinationFilePath) $comOutlookRcpt = $null try { $comOutlookRcpt = $Script:comOutlookNS.CreateRecipient($EmailAddress) [void]($comOutlookRcpt.Resolve()) } catch { Write-PSFMessage -Level Critical -Message "Failed to resolve recipient $EmailAddress" Write-PSFMessage -Level Verbose -Message $Error[0] } if ($comOutlookRcpt) { for ($i = 0; $i -lt $comOutlookFolderTypes.Length; $i++) { $comOutlookFolderType = $comOutlookFolderTypes[$i] Write-PSFMessage -Level VeryVerbose -Message "Iterating through shared folder type $comOutlookFolderType" try { $comOutlookRctpFolder = $Script:comOutlookNS.GetSharedDefaultFolder($comOutlookRcpt, $comOutlookFolderType) if ($comOutlookRctpFolder.DefaultItemType -eq [Microsoft.Office.Interop.Outlook.OlItemType]::olMailItem) { Write-PSFMessage -Level Host -Message "Successfully copied shared folder" } else { Write-PSFMessage -Level Verbose -Message "Skipping shared folder as it is non-mail" } } catch { Write-PSFMessage -Level Warning -Message "Failed to get shared folder $comOutlookFolderType" Write-PSFMessage -Level Verbose -Message $Error[0] } } } Remove-PSOutlookStore($DestinationFilePath) }
This is just a partial from my PS module.
- jordanSep 21, 2022Copper Contributor
I have built out a pretty extensive offboarding script that performs a lot of these tasks. I wonder if there is a way to download from multiple exports simultaneously. I am going to be looking into start-job to see if I can maybe start 4 or 5 downloads at once.
- rodolfoberainSep 08, 2022Copper Contributor
not sure what the issue is but it seems like it doesn't wait for the export to finish before starting the download?
CaseId : CaseName : PagingState : Identity : e7423609-836a-4438-45eb-08da91ee30bf ContentURL : ResultInEOP : False AzureBatchFrameworkEnabled : True IsValid : True ObjectState : New No se puede llamar a un método en una expresión con valor NULL. En C:\Temp\test.ps1: 97 Carácter: 1 + $ExportEstSize = ($ExportDetails[18].TrimStart(" Total estimated byte ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull No se puede llamar a un método en una expresión con valor NULL. En C:\Temp\test.ps1: 98 Carácter: 1 + $ExportTransferred = ($ExportDetails[20].TrimStart(" Total transferre ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull No se puede llamar a un método en una expresión con valor NULL. En C:\Temp\test.ps1: 99 Carácter: 1 + $ExportProgress = $ExportDetails[22].TrimStart(" Progress: ").TrimEnd ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull No se puede llamar a un método en una expresión con valor NULL. En C:\Temp\test.ps1: 100 Carácter: 1 + $ExportStatus = $ExportDetails[25].TrimStart(" Export status: ") + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull Initiating download Saving export to: + d:\temp Write-Progress : No se puede validar el argumento del parámetro 'PercentComplete'. El argumento es NULL, está vacío o un elemento de la colección de argumentos contiene un valor NULL. Proporcione una colección que no contenga ningún valor NULL e intente ejecutar el comando de nuevo. En C:\Temp\test.ps1: 120 Carácter: 96 + ... t in Progress" -Status "Complete..." -PercentComplete $ExportProgress + ~~~~~~~~~~~~~~~ + CategoryInfo : InvalidData: (:) [Write-Progress], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.WriteProgressCommand Intento de dividir por cero. En C:\Temp\test.ps1: 121 Carácter: 45 + ... portStatus){Write-Progress -Id 2 -Activity "Download in Progress" -St ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], RuntimeException + FullyQualifiedErrorId : RuntimeException No se puede llamar a un método en una expresión con valor NULL. En C:\Temp\test.ps1: 126 Carácter: 5 + $ExportEstSize = ($ExportDetails[18].TrimStart(" Total estimated ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull No se puede llamar a un método en una expresión con valor NULL. En C:\Temp\test.ps1: 127 Carácter: 5 + $ExportTransferred = ($ExportDetails[20].TrimStart(" Total transf ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull No se puede llamar a un método en una expresión con valor NULL. En C:\Temp\test.ps1: 128 Carácter: 5 + $ExportProgress = $ExportDetails[22].TrimStart(" Progress: ").Tri ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull No se puede llamar a un método en una expresión con valor NULL. En C:\Temp\test.ps1: 129 Carácter: 5 + $ExportStatus = $ExportDetails[25].TrimStart(" Export status: ") + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull .Download Complete! Presione Entrar para continuar...:
- tylermontney_accSep 06, 2022Brass Contributor
If the application has already been installed, you'll get a misleading InvalidOperationException from AssertApplicationRequirements. This doc "suggests" to check GetManifestCompletedEventArgs before calling AssertApplicationRequirements.
Register-ObjectEvent -InputObject $HostingManager -EventName GetManifestCompleted -Action { New-Event -SourceIdentifier "GetManifestCompleted" -EventArguments @($EventArgs.Error) } | Out-Null
$HostingManagerEvent = Wait-Event -SourceIdentifier "GetManifestCompleted" -Timeout 15 if($HostingManager){ if ($HostingManagerEvent.SourceArgs[0]) { if($HostingManagerEvent.SourceArgs[0].Message.Contains("already installed")){ Write-Output "ClickOnce Application has already been downloaded" }else{ Write-Error -Message $HostingManagerEvent.SourceArgs[0].Message } } }
Additionally, VSCode warned me $event is an automatic variable. Unless it was intentionally named that way, I renamed mine to something else.
- JChupAug 13, 2020Brass Contributor
Hi Riftsan,
Yes, everything still works, and I edited my original post a few months ago to include improvements.
The actual downloading of the PST does require Microsoft's ClickOnce "Universal Export Tool".
The script includes code to automatically download the tool if it's not already on your computer, and download the PST without having to interact with the GUI.
The only issues at this point are that we can't pull real stats from the local application, so we have to poll server-side stats to estimate how far along the download is, and you can't resume a download if it's been interrupted.
- RiftsanAug 13, 2020Copper Contributor
JChup
I am curious to know if this capability is available now since most of the conversation was about a year ago. I am also needing to automate the whole process via a script but it looks like the last piece of downloading the pst file is still a manual process from the GUI?Thanks!
- Di_WangJun 19, 2020Copper Contributor
I finally found the reason. The script should be run by Powershell Administrator !
- Di_WangJun 19, 2020Copper Contributor
Hi JChup Thank you for sharing your code.
I get a problem when I started to export result.
From the trace.log file I got the error:
Fatal error happened. System.UnauthorizedAccessException: Access to the path '.\x86' is denied.
After that, the export process has been cancelled.
Do you know where is the folder '.\x86' and how can I set the permission of that folder?
Thanks
- JChupMay 04, 2020Brass Contributor
It looks like the export tool can't load one of it's dependency files. Did you copy the exe out of your profile folder without all the rest of the files? And, are you using Tom Aguero's code? If so, try replacing:
$exportexe = "\UnifiedExportTool\microsoft.office.client.discovery.unifiedexporttool.exe"
with:
$exportexe = ((Get-ChildItem -Path $($env:LOCALAPPDATA + "\Apps\2.0\") -Filter microsoft.office.client.discovery.unifiedexporttool.exe -Recurse).FullName | Where-Object{ $_ -notmatch "_none_" } | Select-Object -First 1)
and see if that helps?
This should find the exe within your profile, without having to copy the file or use any static paths, which should help if you have multiple people that may run the script.
Also, thanks for the Reddit link, I like their way of pulling the URL and token much better! I'll have to try it out on the next run of the script.
Additionally, If anyone wants to automate downloading the unified export tool:
# Check if the export tool is installed for the user, and download if not. While (-Not ((Get-ChildItem -Path $($env:LOCALAPPDATA + "\Apps\2.0\") -Filter microsoft.office.client.discovery.unifiedexporttool.exe -Recurse).FullName | Where-Object{ $_ -notmatch "_none_" } | Select-Object -First 1)){ Write-Host "Downloading Unified Export Tool ." Write-Host "This is installed per-user by the Click-Once installer." # Credit to Jos Verlinde for his code in Load-ExchangeMFA in the Powershell Gallery! All I've done is update the manifest url and remove all the comments # Ripped from https://www.powershellgallery.com/packages/Load-ExchangeMFA/1.2 # In case anyone else has any ClickOnce applications they'd like to automate the install for: # If you're looking for where to find a manifest URL, once you have run the ClickOnce application at least once on your computer, the url for the application manifest can be found in the Windows Registry at "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall" (yes, CTR apps are installed per-user). # Look through the keys with names that are 16 characters long hex strings. They'll have a string value (REG_SZ) named either "ShortcutAppId" or "UrlUpdateInfo" that contains the URL as the first part of the string. $Manifest = "https://complianceclientsdf.blob.core.windows.net/v16/Microsoft.Office.Client.Discovery.UnifiedExportTool.application" $ElevatePermissions = $true Try { Add-Type -AssemblyName System.Deployment Write-Host "Starting installation of ClickOnce Application $Manifest " $RemoteURI = [URI]::New( $Manifest , [UriKind]::Absolute) if (-not $Manifest){ throw "Invalid ConnectionUri parameter '$ConnectionUri'" } $HostingManager = New-Object System.Deployment.Application.InPlaceHostingManager -ArgumentList $RemoteURI , $False Register-ObjectEvent -InputObject $HostingManager -EventName GetManifestCompleted -Action { new-event -SourceIdentifier "ManifestDownloadComplete" } | Out-Null Register-ObjectEvent -InputObject $HostingManager -EventName DownloadApplicationCompleted -Action { new-event -SourceIdentifier "DownloadApplicationCompleted" } | Out-Null $HostingManager.GetManifestAsync() $event = Wait-Event -SourceIdentifier "ManifestDownloadComplete" -Timeout 15 if ($event ) { $event | Remove-Event Write-Host "ClickOnce Manifest Download Completed" $HostingManager.AssertApplicationRequirements($ElevatePermissions) $HostingManager.DownloadApplicationAsync() $event = Wait-Event -SourceIdentifier "DownloadApplicationCompleted" -Timeout 60 if ($event ) { $event | Remove-Event Write-Host "ClickOnce Application Download Completed" } else { Write-error "ClickOnce Application Download did not complete in time (60s)" } } else { Write-error "ClickOnce Manifest Download did not complete in time (15s)" } } finally { Get-EventSubscriber|? {$_.SourceObject.ToString() -eq 'System.Deployment.Application.InPlaceHostingManager'} | Unregister-Event } } $exportexe = ((Get-ChildItem -Path $($env:LOCALAPPDATA + "\Apps\2.0\") -Filter microsoft.office.client.discovery.unifiedexporttool.exe -Recurse).FullName | Where-Object{ $_ -notmatch "_none_" } | Select-Object -First 1)