2018-05-05 PerlでOutlook受信トレイ内のメールを取得・表示
PerlでOutlook受信トレイ内のメールを取得・表示するサンプルコードを作りました。巷で見掛けないようなので。
概要
・Outlookの任意フォルダ内にあるメールのヘッダ,件名,受信日時,本文を標準出力に出力
・WindowsのActivePerlを使用。Windows以外は対象外
・文字エンコードを意識しない版と,UTF-8版の2種類を作成。不慣れな人は,「Win32::OLEでActiveXオブジェクトがどうの」という問題よりも,PerlのUTF-8フラグの扱いでハマると思われます
・実行するPCにはOutlookのインストールが必要。OutlookがインストールされていないPCでは使用できない
詳細
Perl独特の作法で注意すべき点を中心に。
・Win32::OLEクラスを使用しOutlook.Applicationのインスタンスを獲得
・「Outlook.ApplicationからMAPIという名前のName Spaceを獲得。Outlookのデータフォルダ一覧からフォルダ階層を辿って,受信トレイや送信トレイのFolderオブジェクトを‥‥」という以後の手順は,Perlとはほとんど関係がなくて,Microsoftが提供しているVBやC#の説明と同様
・オブジェクトの型はどれもWin32::OLEになる。Perlからは型の区別が付かない。VBA/VBSで作成したものをPerlに移植しながら作業すると「察し」が付きやすくなる
・変数に格納したデータをData::Dumpしたいところだが,ダンプするとPerlインタプリタが強制終了。内部のデータがどうなっているか,覗き見することは無理なようです
・タイムスタンプの扱いもちょっと面倒。日付時刻は,Win32::OLE::Variantオブジェクトで返されます。Win32::OLE::VariantのDateメソッドとTimeメソッドで,日付と時刻に各々分けて取得。一度,ISO8601形式(YYYY-MM-DDTHH:MM:SS)の文字列にしてから,Time::Pieceクラスで解析。Time::Piece クラスのepochメソッドでUNIXエポック秒に変換してみました
文字エンコードなし版
Win32::OLEが使用できることを確認するためのサンプルです。
シェルの標準出力に表示する以上のことはできません。しようとすると途端に難易度が上がります。たとえば,JSON形式でファイル保存する。XML形式でファイル保存する。HTTPでリモートホストにリクエストを投げる,などなど。さらに話を進めたい方は,つぎのUTF-8版をご利用ください。
# このファイルはCP932(Shift_JIS)で保存すること use strict; use Win32::OLE; use Win32::OLE::Const; use Time::Piece; ##### use constant TZ => -9*60*60; # 日本時間のタイムゾーン(JST-9) ##### #my $data_folder = 'Outlook データ ファイル/受信トレイ'; my $data_folder = undef; # 空の場合はフォルダ選択ダイアログを表示 my $outlook; eval { # すでにOutlookが起動しているならそのインスタンスを取得 $outlook = Win32::OLE->GetActiveObject('Outlook.Application'); }; if($@ or !defined $outlook) { $outlook = Win32::OLE->new('Outlook.Application', sub {$_[0]->Quit}) or die; } # Windowsタイプライブラリの定数一覧をハッシュにロード my $const = Win32::OLE::Const->Load($outlook); my $namespace = $outlook->GetNameSpace('MAPI') or die; my $folders; if(!$data_folder) { # $data_folderが空の場合はフォルダ選択ダイアログを表示 $folders = $namespace->PickFolder(); } else { $folders = $namespace; foreach(split('/', $data_folder)) { # フォルダ階層を辿る。指定したフォルダの参照を得る $folders = $folders->Folders()->Item($_); } } # 選択したフォルダのパスを出力 my @path; for(my $o = $folders; $o; $o = $o->Parent) { if($o->Class == $const->{olFolder}) { push(@path, $o->Name); } } print join('/', reverse(@path)), "\n"; print "\n"; # フォルダからアイテム一覧を取得 my $items = $folders->Items(); for(my $item = $items->getFirst(); $item; $item = $items->GetNext()) { my $header = $item->PropertyAccessor->GetProperty('http://schemas.microsoft.com/mapi/proptag/0x007D001E'); my $subject = $item->Subject; my $body = $item->Body; my $received_time = $item->ReceivedTime; # Win32::OLE::Variantオブジェクト my $tp = Varinat2time($received_time, &TZ); # Variant -> シリアル値 $header =~ s/\x0d\x0a/\n/g; # 改行コードを\nにする $body =~ s/\x0d\x0a/\n/g; print $header; print $subject; print Time::Piece->localtime($tp)->strftime('%Y/%m/%d %H:%M:%S'), "\n"; print "\n"; print $body, "\n"; } Win32::OLE->FreeUnusedLibraries(); exit; sub Varinat2time { my $p = shift; # Win32::OLE::Variantオブジェクト my $tz = shift || 0; # 日付時刻はWin32::OLE::Variant型でラップされる # 日付と時刻を分けてパース。Time::Pieceを介して整数のエポック秒に変換する my $iso8601 = sprintf('%sT%s', $p->Date('yyyy-MM-dd'), $p->Time('HH:mm:ss')); my $tp = Time::Piece->strptime($iso8601, '%Y-%m-%dT%H:%M:%S'); $tp += $tz; # タイムゾーンを合わせる return $tp->epoch; } # EOF
UTF-8版
UTF-8フラグの挙動を合わせたバージョンです。以下,概要。
・ソースファイルはUTF-8(BOMなし)で保存する
・Win32::OLEオブジェクトからゲットした文字列は,CP932からUTF-8に文字コードを変換する。Encode::decodeでUTF-8フラグを付ける
・Win32::OLEオブジェクトにセットする文字列は,UTF-8からCP932に文字コードを変換する。Encode::encodeでUTF-8フラグを外す
# UTF-8版 # このファイルはUTF-8N(UTF-8,BOM(Byte Order Mark)なし)で保存すること # Windowsの「メモ帳」は不適なので注意。ファイル保存するとBOMありになる use strict; use utf8; use Win32::OLE; use Win32::OLE::Const; use Time::Piece; ##### use constant TZ => -9*60*60; # 日本時間のタイムゾーン(JST-9) use constant CODE_PAGE => 'cp932'; # 文字コード:日本語Windows(CP932) ##### binmode(STDOUT, ':raw :encoding(cp932)'); ##### #my $data_folder = 'Outlook データ ファイル/受信トレイ'; my $data_folder = undef; # 空の場合はフォルダ選択ダイアログを表示 my $outlook; eval { # すでにOutlookが起動しているならそのインスタンスを取得 $outlook = Win32::OLE->GetActiveObject('Outlook.Application'); }; if($@ or !defined $outlook) { $outlook = Win32::OLE->new('Outlook.Application', sub {$_[0]->Quit}) or die; } # Windowsタイプライブラリの定数一覧をハッシュにロード my $const = Win32::OLE::Const->Load($outlook); my $namespace = $outlook->GetNameSpace('MAPI') or die; my $folders; if(!$data_folder) { # $data_folderが空の場合はフォルダ選択ダイアログを表示 $folders = $namespace->PickFolder(); } else { $folders = $namespace; foreach(split('/', $data_folder)) { # フォルダ階層を辿る。指定したフォルダの参照を得る $folders = $folders->Folders()->Item(Encode::encode(CODE_PAGE, $_)); } } # 選択したフォルダのパスを出力 my @path; for(my $o = $folders; $o; $o = $o->Parent) { if($o->Class == $const->{olFolder}) { push(@path, Encode::decode(CODE_PAGE, $o->Name)); } } print join('/', reverse(@path)), "\n"; print "\n"; # フォルダからアイテム一覧を取得 my $items = $folders->Items(); for(my $item = $items->getFirst(); $item; $item = $items->GetNext()) { my $header = $item->PropertyAccessor->GetProperty('http://schemas.microsoft.com/mapi/proptag/0x007D001E'); my $subject = Encode::decode(CODE_PAGE, $item->Subject); my $body = Encode::decode(CODE_PAGE, $item->Body); my $received_time = $item->ReceivedTime; # Win32::OLE::Variantオブジェクト my $tp = Varinat2time($received_time, &TZ); # Variant -> シリアル値 $header =~ s/\x0d\x0a/\n/g; # 改行コードを\nにする $body =~ s/\x0d\x0a/\n/g; # 改行コードを\nにする print $header; print $subject; print Time::Piece->localtime($tp)->strftime('%Y/%m/%d %H:%M:%S'), "\n"; print "\n"; print $body, "\n"; } Win32::OLE->FreeUnusedLibraries(); exit; sub Varinat2time { my $p = shift; # Win32::OLE::Variantオブジェクト my $tz = shift || 0; # 日付時刻はWin32::OLE::Variant型でラップされる # 日付と時刻を分けてパース。Time::Pieceを介して整数のエポック秒に変換する my $iso8601 = sprintf('%sT%s', $p->Date('yyyy-MM-dd'), $p->Time('HH:mm:ss')); my $tp = Time::Piece->strptime($iso8601, '%Y-%m-%dT%H:%M:%S'); $tp += $tz; # タイムゾーンを合わせる return $tp->epoch; } # EOF