Public/Export-Mailbox.ps1

function Export-Mailbox {
<#
.SYNOPSIS
    Exports Gmail messages to .eml files.
.DESCRIPTION
    Exports messages from a Google Workspace user's mailbox to individual .eml files.
    Supports filtering by search query. Automatically renames files by receipt date for
    natural sorting. Skips suspended and archived accounts.
    When no query is provided, exports all non-Spam/non-Trash messages so counts align
    with Get-Mailbox message totals.
.PARAMETER Email
    The email address of the mailbox to export.
.PARAMETER Suspended
    Whether the account is suspended (used for pipeline filtering).
.PARAMETER Archived
    Whether the account is archived (used for pipeline filtering).
.PARAMETER Query
    Optional Gmail search query to filter messages (e.g., "from:example.com" or "WorkSafeBC").
    Default: -in:spam -in:trash
    Supports pipeline-by-property-name input.
.EXAMPLE
    Export-Mailbox -Email user@domain.com
.EXAMPLE
    Export-Mailbox -Email user@domain.com -Query "WorkSafeBC"
.EXAMPLE
    Get-Mailbox | Where-Object { -not $_.Suspended } | Export-Mailbox
.OUTPUTS
    PSCustomObject with Email, Query, OutputDir, Exported count, Renamed count, and Unrenamed count.
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
    [string] $Email,
    [Parameter(ValueFromPipelineByPropertyName)]
    [bool] $Suspended,
    [Parameter(ValueFromPipelineByPropertyName)]
    [bool] $Archived,
    [Parameter(ValueFromPipelineByPropertyName)]
    [string] $Query
)

process {
    $activity     = 'Export-Mailbox'

    if ($Suspended) {
        Write-Warning "Skipping $Email - account is suspended"
        return
    }
    if ($Archived) {
        Write-Warning "Skipping $Email - account is archived"
        return
    }
    $hasCustomQuery = -not [string]::IsNullOrWhiteSpace($Query)
    $effectiveQuery = if ($hasCustomQuery) { $Query.Trim() } else { '-in:spam -in:trash' }
    $folderSuffix = if ($hasCustomQuery) { $effectiveQuery } else { 'all' }
    $OutDir       = "./exports/$Email+$folderSuffix"

    Write-Verbose "$activity : $Email"
    Write-Verbose "Query : $effectiveQuery"
    Write-Verbose "Output : $OutDir"

    Write-Progress -Activity $activity -Status 'Creating export directory...' -PercentComplete 5
    New-Item -ItemType Directory -Path $OutDir -Force | Out-Null

    Write-Progress -Activity $activity -Status 'Exporting messages via GAM...' -PercentComplete 10

    # GAM outputs per-message progress to stdout - let it flow through as the primary progress indicator
    & gam user $Email export messages query $effectiveQuery max_to_export 0 targetfolder $OutDir overwrite

    # Rename exported files: yyyy-MM-dd_HHmmss_Msg-{id}.eml so they sort naturally by date
    Write-Progress -Activity $activity -Status 'Renaming files by receipt date...' -PercentComplete 90

    $files   = @(Get-ChildItem -Path $OutDir -Filter '*.eml' | Where-Object { $_.Name -notmatch '^\d{4}-\d{2}-\d{2}_' })
    $total   = $files.Count
    $renamed = 0
    $skipped = 0
    $i       = 0

    foreach ($file in $files) {
        $i++
        Write-Progress -Activity $activity -Status "Renaming $($file.Name)" `
            -PercentComplete (90 + [int](9 * $i / [Math]::Max($total, 1)))

        $dateLine = Get-Content -Path $file.FullName -TotalCount 100 |
            Where-Object { $_ -match '^Date:\s' } |
            Select-Object -First 1

        if ($dateLine -match '^Date:\s*(.+)') {
            try {
                $date    = [datetime]::Parse($matches[1].Trim())
                $newName = '{0}_{1}' -f $date.ToString('yyyy-MM-dd_HHmmss'), $file.Name
                Rename-Item -Path $file.FullName -NewName $newName -ErrorAction Stop
                $renamed++
            } catch {
                $skipped++
            }
        } else {
            $skipped++
        }
    }

    Write-Progress -Activity $activity -Completed

    $exportedFiles = @(Get-ChildItem -Path $OutDir -Filter '*.eml')

    [PSCustomObject]@{
        Email     = $Email
        Query     = $effectiveQuery
        OutputDir = $OutDir
        Exported  = $exportedFiles.Count
        Renamed   = $renamed
        Unrenamed = $skipped
    } | Format-List
}
}