PowerShellでOutlookのメールを検索する

PowerShellでOutlookメールを検索するスクリプトサンプルです。 OutlookのAdvancedSearchというメソッドを使ってやってみたところかなり高速に動いてくれました。

コード

function Get-OLMail {
<#
.Synopsis
    Outlookメール検索コマンドレット
.Description
    Outlook内のメールをAdvancedSearchメソッドを使って検索するコマンドレット
.Example
    # 2/26 00:00から2/28 00:00のタイトルに"テスト"を含み、"Microsoft Outlook"からのメールを検索する
    $mails = Get-OLMail `
            -Start "2015/02/26" `
            -Finish "2015/02/28" `
            -Subject "テスト" `
            -From "Microsoft Outlook"
    $mails | Select ReceivedTime,Subject,@{N="Folder";E={$_.Parent.FolderPath}} | ft -auto
.Parameter Start
    検索範囲開始日時 初期値 : (Get-Date).AddDays(-1)
.Parameter Finish
    検索範囲終了日時 初期値 : (Get-Date)
.Parameter Subject
    タイトルに含まれる文字列
.Parameter From
    送信者に含まれる文字列
.Parameter To
    受信者に含まれる文字列
.Parameter Body
    本文に含まれる文字列(※HTMLメールは微妙か?)
#>
[CmdletBinding()]
    Param(
        [Parameter(Mandatory=$False)][DateTime]$Start = (Get-Date).AddDays(-1),
        [Parameter(Mandatory=$False)][DateTime]$Finish = (Get-Date),
        [Parameter(Mandatory=$False)][String]$Subject,
        [Parameter(Mandatory=$False)][String]$From,
        [Parameter(Mandatory=$False)][String]$To,
        [Parameter(Mandatory=$False)][String]$Body
    )
    Begin {
        $ol = New-Object -ComObject Outlook.Application
        $start_str = $Start.ToString("MM/dd/yyyy HH:mm")
        $finish_str = $Finish.ToString("MM/dd/yyyy HH:mm")
        $ns = "urn:schemas:httpmail"
        $query = "`"${ns}:datereceived`" >= `'${start_str}`' "
        $query += "AND `"${ns}:datereceived`" <= `'${finish_str}`' "
        if($Subject){
            $query += "AND `"${ns}:subject`" LIKE `'%${Subject}%`' "
        }
        if($From){
            $query += "AND `"${ns}:sendername`" LIKE `'%${From}%`' "
        }
        if($To) {
            $query += "AND `"${ns}:to`" LIKE `'%${To}%`' "
        }
        if($Body) {
            $query += "AND `"${ns}:textdescription`" LIKE `'%${Body}%`' "
        }
        $e = Register-ObjectEvent -InputObject $ol AdvancedSearchComplete -Action {
            $global:OUTLOOK_SEARCH = $false
        }
    }
    Process {

        Write-Verbose $query
        $folders = $ol.Session.Folders
        foreach($f in $folders) {
            $folder_path = $f.FolderPath
            Write-Progress -Activity "Outlookを検索中……" -Status $query
            $as = $ol.AdvancedSearch("`'$folder_path`'",$query,$true,"Search")
            $global:OUTLOOK_SEARCH = $true
            while($global:OUTLOOK_SEARCH) {
               Start-Sleep 1
            }
            $as.Results
        }
    }
    End {
        $e | Stop-Job
    }
}

使い方

実行例

$mails = Get-OLMail `
            -Start "2015/02/26" `
            -Finish "2015/02/28" `
            -Subject "テスト" `
            -From "Microsoft Outlook"
$mails | Select ReceivedTime,Subject,@{N="Folder";E={$_.Parent.FolderPath}} | ft -auto

結果例

ReceivedTime       Subject                             Folder
------------       -------                             ------
2015/02/27 8:14:12 Microsoft Outlook テスト メッセージ \\hoge@btnb.jp\受信トレイ
2015/02/27 8:14:12 Microsoft Outlook テスト メッセージ \\hoge@btnb.jp\[Gmail]\重要
2015/02/27 8:14:12 Microsoft Outlook テスト メッセージ \\hoge@btnb.jp\[Gmail]\送信済みメール

解説

前述のとおり、OutlookのAdvancedSearchメソッドを使っています。

Begin

$ol = New-Object -ComObject Outlook.Application

Outlookオブジェクトを$olとして定義しています。

$start_str = $Start.ToString("MM/dd/yyyy HH:mm")
$finish_str = $Finish.ToString("MM/dd/yyyy HH:mm")
$ns = "urn:schemas:httpmail"
$query = "`"${ns}:datereceived`" >= `'${start_str}`' "
$query += "AND `"${ns}:datereceived`" <= `'${finish_str}`' "
if($Subject){
    $query += "AND `"${ns}:subject`" LIKE `'%${Subject}%`' "
}
if($From){
    $query += "AND `"${ns}:sendername`" LIKE `'%${From}%`' "
}
if($To) {
    $query += "AND `"${ns}:to`" LIKE `'%${To}%`' "
}
if($Body) {
    $query += "AND `"${ns}:textdescription`" LIKE `'%${Body}%`' "
}

引数より、検索クエリ を組み立てています。

$e = Register-ObjectEvent -InputObject $ol AdvancedSearchComplete -Action {
    $global:OUTLOOK_SEARCH = $false
}

このスクリプトのポイントです。AdvancedSearchメソッドが非同期処理である為、実行後は完了を待ちません。完了を知るためにはAdvancedSearchCompleteイベントを受け取る必要があります。 $ol | Get-Member とすると、AdvancedSearchCompleteイベントが存在する事が判りますが、他のプロパティと同じように $ol.AdvancedSearchComplete などとすれば検索が終わった事が判るのかなと思いきや、何も返してくれません。

PS> $ol | gm
   TypeName: Microsoft.Office.Interop.Outlook.ApplicationClass

Name                     MemberType Definition
----                     ---------- ----------
AdvancedSearchComplete   Event      Microsoft.Office.....
...

PS> $ol.AdvancedSearchComplete
PS>

ではどうするかというと、PowerShellでEventを受け取る為にはPowershellセッションに処理するイベントを登録してやる必要があります。それが上記コードになります。 意味としては、"オブジェクト $ol が持つ、AdvancedSearchCompleteイベントを受け取ったら、グローバル変数 OUTLOOK_SEARCH$false を設定する" という意味です。 これで、この後に出てくる…

while($global:OUTLOOK_SEARCH) {
   Start-Sleep 1
}

によって検索完了を待ち合わせられるようになりました。

尚、Register-ObjectEvent で生成されるオブジェクトは Get-Job で確認できます。つまりバックグラウンドジョブとして稼動しているという事ですね。なのでAction内の変数への代入が上手くいかないんじゃないかと思いきや、グローバル変数なら想定通りに動いてくれましたので、普通のバックグラウンドジョブとはまた違うようです。

Process

$folders = $ol.Session.Folders

全親フォルダを取得しています。適宜フィルターしてください。

$as = $ol.AdvancedSearch("`'$folder_path`'",$query,$true,"Search")
$global:OUTLOOK_SEARCH = $true
while($global:OUTLOOK_SEARCH) {
   Start-Sleep 1
}
$as.Results

AdvancedSearch開始後にグローバル変数 OUTLOOK_SEARCH$true をセットし、それが$false になるまで待ち合わせて、結果を返す。 という処理になっています。 ちなみにAdvancedSearchの使い方は

AdvancedSearch(フォルダパス,DASL文,サブフォルダも検索するか?,検索処理名)

です。

参考までに、$as オブジェクトは Save("hoge") というメソッドを持っていて、これを実行するとOutlookの検索フォルダーとして保存出来ます。

End

最後に Register-ObjectEvent で生成したイベントジョブを停止しています。

$e | Stop-Job

以上です。これでPowerShellでOutlookの検索が出来るようになりました。 Outlookメールの自動処理も自由自在です!