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.
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)
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...:- 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.