PowerShellメモ　Outlookメールをテキスト出力

# 実行イメージ

![ExportOutlook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/27ad38f5-3425-7c88-ef2d-118dbe81c8c4.png)

# 出力ファイルイメージ

```
件名　　：XXXXXXXXXXXXXXXXXXXXXXX
送信者　：XXXXXX XXXXXX
宛先　　：XXXXXXX XXXXXXXX
ＣＣ　　：
送信日時：XXXX/XX/XX(XX) XX:XX:XX
添付　　：XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
添付　　：XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－
XXXXXXXXXX

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

XXXXXXXXXXXXXXXXXXXXXX
```

# 起動用BAT

```Bat
@ECHO OFF
ECHO しばらくお待ちください...
PowerShell.exe -NoLogo -NoProfile -ExecutionPolicy RemoteSigned .\ExportOutlookMsg.ps1

ECHO.
PAUSE
```

# スクリプト本体

`InboxFolderPath`に「受信トレイ」の出力先フォルダを、`SentItemFolderPath`に「送信済みアイテム」の出力先フォルダを指定しておく。

```PowerShell:ExportOutlookMsg.ps1
#requires -version 3.0
Set-StrictMode -Version Latest

<#
【Outlookメールをテキスト形式でエクスポート】

「受信トレイ」または「送信済みアイテム」のメールを
所定の場所にテキスト形式で出力する。
添付ファイルはテキストのヘッダ部にファイル名のみ出力する。

※スクリプト実行前にOutlookを起動しておくこと。
#>

#region 変数・定数定義

# ■「受信トレイ」メール用出力フォルダ
Set-Variable -Name InboxFolderPath -Value    '.\受信トレイExport' -Option ReadOnly -Force
# ■「送信済みアイテム」メール用出力フォルダ
Set-Variable -Name SentItemFolderPath -Value '.\送信済みアイテムExport' -Option ReadOnly -Force

# エラーログ出力先
Set-Variable -Name ErrLogPath -Value '.' -Option ReadOnly -Force
# エラーログファイル名プレフィックス
Set-Variable -Name ErrLogFileNamePrefix -Value 'ExportOutlookMsg_Err' -Option ReadOnly -Force
# 出力ファイル名部分の最大文字数
Set-Variable -Name MaxFileNameLength -Value 60 -Option ReadOnly -Force

# 処理終了番号
Set-Variable -Name InputNo_Exit -Value '0' -Option ReadOnly -Force

# 対象フォルダ（受信トレイ）
Set-Variable -Name FolderKind_Inbox -Value '1' -Option ReadOnly -Force
# 対象フォルダ（送信済みアイテム）
Set-Variable -Name FolderKind_SentMail -Value '2' -Option ReadOnly -Force

# 抽出対象期間（直近1週間）
Set-Variable -Name PeriodKind_1Week -Value '1' -Option ReadOnly -Force
# 抽出対象期間（直近2週間）
Set-Variable -Name PeriodKind_2Week -Value '2' -Option ReadOnly -Force
# 抽出対象期間（直近1ヶ月）
Set-Variable -Name PeriodKind_1Month -Value '3' -Option ReadOnly -Force
# 抽出対象期間（直近2ヶ月）
Set-Variable -Name PeriodKind_2Month -Value '4' -Option ReadOnly -Force
# 抽出対象期間（開始日を直接指定）
Set-Variable -Name PeriodKind_UserInput -Value '5' -Option ReadOnly -Force
# 抽出対象期間（全件）
Set-Variable -Name PeriodKind_All -Value '9' -Option ReadOnly -Force

# 受信トレイを表す定数
Set-Variable -Name olFolderInbox -Value 6 -Option Constant
# 送信済みアイテムを表す定数
Set-Variable -Name olFolderSentMail -Value 5 -Option Constant
# Outlookのプロセス名（起動チェック用）
Set-Variable -Name OutlookProcesName -Value 'OUTLOOK' -Option Constant
# ウィンドウタイトル
Set-Variable -Name WindowTitle -Value 'Outlookメール エクスポート' -Option Constant

# 選択メニュー1
[string]$FolderSelectMenuText = 
@'

Outlookメールをテキスト形式でファイル出力します。
処理対象の数字を入力してEnterキーを押下してください。

[対象メールフォルダ]
 1 ... 受信トレイ
 2 ... 送信済みアイテム

 0 ... 終了

'@

# 選択メニュー2
[string]$PeriodSelectMenuText = 
@'

[抽出対象期間]
 1 ... 直近1週間（今日は除く）
 2 ... 直近2週間（今日は除く）
 3 ... 直近1ヶ月（今日は除く）
 4 ... 直近2ヶ月（今日は除く）
 5 ... 指定日から（今日は除く）
 9 ... 全て
 
 0 ... 終了

'@

#endregion

[datetime]$NowDate = Get-Date
# エラーログファイル名
[string]$ErrLogFilePath = "{0}_{1}.log" -f (Join-Path $ErrLogPath $ErrLogFileNamePrefix), $NowDate.ToString("yyyyMMdd-HHmmss")

#region 関数定義

<#
.SYNOPSIS
    エラーメッセージ表示

.PARAMETER msg
    コンソールに表示するメッセージ
#>
function ErrMsg([string]$msg) {
    Write-Host -Object $msg -ForegroundColor Red
}

<#
.SYNOPSIS
    プロパティ値チェック

.DESCRIPTION
    プロパティが存在し、値がNULL以外のときTrueを返す

.PARAMETER item
    オブジェクト

.PARAMETER prop
    プロパティ名
#>
function HasPropertyValue([object]$item, [string]$prop) {
    if([bool]($item.PsObject.Properties.Name -match ('^' + $prop + '$'))) {
        # プロパティあり
        
        if (($item.$prop) -eq $null) {
            # 値がNULL
            return $false
        }
        else {
            return $true
        }
    }
    else {
        # プロパティなし
        return $false
    }
}

<#
.SYNOPSIS
    Outlookメールを1件テキストファイル出力

.PARAMETER item
    MailItemオブジェクト

.PARAMETER outPath
    出力フォルダパス

.PARAMETER folderIndex
    Outlookの既定フォルダを表すインデックス
#>
function ExportOutlookMailData([object]$item, [string]$outPath, [int]$folderIndex) {
    # 出力ファイル名組み立て
    [string]$dtStr = ""
    if ($folderIndex -eq $olFolderInbox) {
        # 受信トレイ

        if (HasPropertyValue $item "ReceivedTime") {
            # 受信日時
            $dtStr = $item.ReceivedTime.ToString('yyyyMMdd(ddd)-HHmmss')
        }
        else {
            # 日付を取得できなかったものは件名をエラーログに出力
            $item.Subject | Out-File $ErrLogFilePath -Encoding UTF8 -Append
            return ""
        }
    }
    elseif ($folderIndex -eq $olFolderSentMail) {
        # 送信済みアイテム

        if (HasPropertyValue $item "SentOn") {
            # 送信日時
            $dtStr = $item.SentOn.ToString('yyyyMMdd(ddd)-HHmmss')
        }
        elseif (HasPropertyValue $item "ReceivedTime") {
            # 送信日時が取得できない場合、受信日時を使う
            $dtStr = $item.ReceivedTime.ToString('yyyyMMdd(ddd)-HHmmss')
        }
        else {
            # 日付を取得できなかったものは件名をエラーログに出力
            $item.Subject | Out-File $ErrLogFilePath -Encoding UTF8 -Append
            return ""
        }
    }
    else {
        # フォルダインデックス不正
        ErrMsg ("ExportOutlookMailData(), folderIndexが不正です。[" + $item.Subject + "][" + $folderIndex + "]")
        return ""
    }
    
    # 件名をファイル名に使う。ファイル名として使えない文字や使いたくない文字は全角に置換
    [string]$subjectStr = $item.Subject.Replace('/', '／').Replace('\', '￥'). `
        Replace('<', '＜').Replace('>', '＞').Replace('*', '＊').Replace('?', '？'). `
        Replace('|', '｜').Replace(':', '：').Replace(';', '；').Replace('[', '［'). `
        Replace(']', '］').Replace('"', '”')

    # 出力ファイル名決定（受信日/送信日_件名）
    [string]$fname = "${dtStr}_${subjectStr}"
    [int]$len = $fname.Length
    if ($MaxFileNameLength -lt $len) {
        # 長すぎる件名は途中で切る
        $fname = $fname.Substring(0, $MaxFileNameLength)
    }

    # 出力ファイルパス
    [string]$filePath = "${outPath}\${fname}.txt"

    try {
        "件名　　：" + $item.Subject | 
            Out-File $filePath -Encoding UTF8 -ErrorAction Stop

        "送信者　：" + $item.SenderName | 
            Out-File $filePath -Encoding UTF8 -Append -ErrorAction Stop

        if (HasPropertyValue $item "To") {
            "宛先　　：" + $item.To | 
                Out-File $filePath -Encoding UTF8 -Append -ErrorAction Stop
        }

        if (HasPropertyValue $item "CC") {
            "ＣＣ　　：" + $item.CC | 
                Out-File $filePath -Encoding UTF8 -Append -ErrorAction Stop
        }

        if ($folderIndex -eq $olFolderInbox) {
            # 受信トレイ
            "受信日時：" + $item.ReceivedTime.ToString('yyyy/MM/dd(ddd) HH:mm:ss') | 
                Out-File $filePath -Encoding UTF8 -Append -ErrorAction Stop
        }
        elseif ($folderIndex -eq $olFolderSentMail) {
            # 送信済みアイテム
            "送信日時：" + $item.SentOn.ToString('yyyy/MM/dd(ddd) HH:mm:ss') | 
                Out-File $filePath -Encoding UTF8 -Append -ErrorAction Stop
        }

        # 添付ファイル
        $item.Attachments | ForEach-Object {
            "添付　　：" + $_.FileName  | 
                Out-File $filePath -Encoding UTF8 -Append -ErrorAction Stop
        }
        # 本文との区切り線
        "－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－" | 
            Out-File $filePath -Encoding UTF8 -Append -ErrorAction Stop
        # 本文
        $item.Body | 
            Out-File $filePath -Encoding UTF8 -Append -ErrorAction Stop
    }
    catch {
        ErrMsg "${filePath} の出力でエラーが発生しました。"
        ErrMsg $Error[0]
    }

    return $filePath
}

#endregion

#=======================================================================

# ウィンドウタイトル設定
$ui = (Get-Host).UI.RawUI
$ui.WindowTitle = $WindowTitle

[string]$inputFolderKind = ""

# 対象のメールフォルダを選択
while ($true) {
    # コンソール画面クリア
    Clear-Host

    # メニュー表示（対象のメールフォルダ）
    Write-Host $FolderSelectMenuText -ForegroundColor Cyan
    # キー入力
    $inputFolderKind = Read-Host '番号を入力'

    if ($inputFolderKind -eq $InputNo_Exit) {
        # 終了
        return
    }
    elseif ($inputFolderKind -ne $FolderKind_Inbox -and 
        $inputFolderKind -ne $FolderKind_SentMail) {
        ErrMsg '処理対象番号が不正です。'
        continue
    }
    else {
        # ループを抜けて次のメニュー表示へ
        break
    }
}

[string]$inputPeriod = ""

# 抽出期間を選択
while ($true) {
    # メニュー表示（対象の期間）
    Write-Host $PeriodSelectMenuText -ForegroundColor Cyan
    # キー入力
    $inputPeriod = Read-Host '番号を入力'

    if ($inputPeriod -eq $InputNo_Exit) {
        # 終了
        return
    }
    elseif ($inputPeriod -ne $PeriodKind_All -and 
        $inputPeriod -ne $PeriodKind_1Week -and 
        $inputPeriod -ne $PeriodKind_2Week -and 
        $inputPeriod -ne $PeriodKind_1Month -and 
        $inputPeriod -ne $PeriodKind_2Month -and
        $inputPeriod -ne $PeriodKind_UserInput) {
        # 再度入力
        ErrMsg '処理対象番号が不正です。'
        continue
    }
    else {
        # 入力受け付け、次の処理へ
        break
    }
}

# Outlook起動チェック
$p = Get-Process -Name "${OutlookProcesName}*"
[bool]$hit = $false
$p | ForEach-Object {
    if ($_.Name -ieq $OutlookProcesName) {
        $hit = $true
    }
}
if ($hit -eq $false) {
    ErrMsg 'Outlookを起動しておいてください。'
    return
}

[string]$targetName = ""
[string]$outFolderPath = ""
[int]$folderIndex = -1
if ($inputFolderKind -eq $FolderKind_Inbox) {
    # 受信トレイの処理
    $targetName = "受信トレイ"
    $outFolderPath = $InboxFolderPath
    $folderIndex = $olFolderInbox
}
elseif ($inputFolderKind -eq $FolderKind_SentMail) {
    # 送信済みアイテムの処理
    $targetName = "送信済みアイテム"
    $outFolderPath = $SentItemFolderPath
    $folderIndex = $olFolderSentMail
}
else {
    Write-Error 'エラーです。'
    return
}

[string]$extractionPeriod = ""
[datetime]$fromDate = [DateTime]::MinValue
# 抽出終了日（昨日）
[datetime]$toDate = $NowDate.AddDays(-1)

# 抽出開始日
if ($inputPeriod -eq $PeriodKind_All) {
    # 全件対象
}
elseif ($inputPeriod -eq $PeriodKind_1Week) {
    # 1週間前
    $fromDate = $NowDate.AddDays(-7)
}
elseif ($inputPeriod -eq $PeriodKind_2Week) {
    # 2週間前
    $fromDate = $NowDate.AddDays(-14)
}
elseif ($inputPeriod -eq $PeriodKind_1Month) {
    # 1ヶ月前
    $fromDate = $NowDate.AddMonths(-1)
}
elseif ($inputPeriod -eq $PeriodKind_2Month) {
    # 2ヶ月前
    $fromDate = $NowDate.AddMonths(-2)
}
elseif ($inputPeriod -eq $PeriodKind_UserInput) {
    # 直接指定
    $inputDate = Read-Host "yyyymmdd形式で入力してください"
    if ([datetime]::TryParseExact(
        $inputDate, 
        'yyyyMMdd', 
        [Globalization.DateTimeFormatInfo]::CurrentInfo, 
        [Globalization.DateTimeStyles]::None,
        [ref]$fromDate) -eq $false) {
        Write-Host "処理を中断します。"
        return
    }
}
else {
    Write-Error 'エラーです。'
    return
}

[string]$receivedTimeFilter = ""

if ($inputPeriod -eq $PeriodKind_All) {
    $extractionPeriod = "全て"
}
else {
    # 表示用の抽出対象期間
    $extractionPeriod = 
        "{0} ～ {1}" -f $fromDate.ToString("yyyy/MM/dd(ddd)"), $toDate.ToString("yyyy/MM/dd(ddd)")
    # Outlookフィルタ条件
    $receivedTimeFilter = 
        '[ReceivedTime] >= "{0}" AND [ReceivedTime] <= "{1}"' -f $fromDate.ToString("yyyy/MM/dd"), $toDate.ToString("yyyy/MM/dd")
}

Write-Host "${extractionPeriod}の「${targetName}」のメールを`n「${outFolderPath}」にテキスト保存します。" -ForegroundColor Cyan

# フォルダ存在チェック
if ((Test-Path $outFolderPath) -eq $false) {
    # 無ければ作成
    mkdir $outFolderPath -ErrorAction Stop > $null
}

$mapi = (New-Object -ComObject Outlook.Application).GetNamespace('MAPI')

# Outlookから対象のメールフォルダを取得
$outlookFolder = $mapi.GetDefaultFolder($folderIndex)

# 抽出期間を反映
$targetCollection = $outlookFolder.Items
if ($inputPeriod -ne $PeriodKind_All) {
    $targetCollection = $outlookFolder.Items.Restrict($receivedTimeFilter)
}

# 件数確認
if ($targetCollection.Count -eq 0) {
    Write-Host "データはありません。"
    return
}

Write-Host ("対象データ " + $targetCollection.Count + " 件") -ForegroundColor Yellow

# 処理を続行するか確認
while ($true) {
    [string]$yesNo = Read-Host "処理を開始しますか？(y/n)"
    if ($yesNo -ieq 'y') {
        # 処理続行
        Write-Host "処理中..."
        break
    }
    elseif ($yesNo -ieq 'n') {
        # 中断
        Write-Host "処理を中断しました。"
        return
    }
}

# ウィンドウタイトル設定
$ui.WindowTitle = '処理中 - ' + $WindowTitle

[int]$errCnt = 0

# 指定した条件でファイル出力
foreach ($item in $targetCollection) {
    # 1件、ファイル出力
    [string]$ret = ExportOutlookMailData $item $outFolderPath $folderIndex

    if ($ret -eq "") {
        # 処理失敗
        Write-Host 'x' -NoNewline
        $errCnt++
    }
    else {
        # 一件処理した
        Write-Host '.' -NoNewline
    }
}

# ウィンドウタイトル設定
$ui.WindowTitle = $WindowTitle

Write-Host "`n「${targetName}」のメールエクスポート処理が完了しました。"
Write-Host ("出力先⇒" + $outFolderPath)
if ($errCnt -ne 0) {
    Write-Host "${errCnt}件の出力に失敗しました。"
    Write-Host "${ErrLogFilePath}を参照してください。"
}
```