PowerShellでemlファイルを閲覧する

1.PowerShellでemlファイルを見たい!

 メールについてデータ量が多くなってきた関係で.emlファイルとして抽出して、ZIP圧縮しているが、過去のメールを検索する方法がなくて困っていた。ネットのフリーソフトを使うことも考えたが、処理速度を気にしなければPowerShellでできるのではないかと思っていろいろと調べてみた。

 結果として.emlファイルやzipファイルとして圧縮しているものについては一覧表示できるようになった。


2.成果物



 メール解析のためにMicrosoft Collaboration Data Objects(CDO)を利用することで意外と簡単に作ることができた。
 PowerShellで.NETのFormクラスを継承し、コントロールをインスタンス化して貼り付ける。とりあえず簡単な検索は問題なく動いているが細かいテストをしていないので複数条件を指定するとエラーが出たりするかもしれないが、自分が使う分には特に問題なさそう。
 zipファイルが多かったり重たかったりすると解凍するのに時間がかかるのと、毎回解凍処理をしているので、処理は早くないが、そんなにいっぱい使うわけでもないので十分かなぁと思う。本当は処理したら結果のファイルを生成しておくことで次回から高速に動作するが、バックアップの内容を見る機会自体がまずあんまりないはずなので、とりあえず我慢できるかな。


3.プログラム(.ps1という拡張子で保存)


using namespace System.Windows.Forms;
using namespace System.Drawing;

#デバッグする際にはターミナルで流しておく必要がある。Classの継承を行っており、Class定義がスクリプトの中で一番最初に読み込まれる(?)ためエラーが出る。先に別ファイルやターミナルで実行することで回避

Add-Type -AssemblyName System.Windows.Forms;
Add-Type -AssemblyName System.Drawing;
# ISE上でデバッグするときには実行 →  Set-ExecutionPolicy RemoteSigned -Scope Process

# ------------------------------------------------------------------------------------------------------------
#  メイン画面
# ------------------------------------------------------------------------------------------------------------
Class clsfrm00_0000_MainForm :System.Windows.Forms.Form{

     [string]$DGV_ALIGN_MIDDLECENTER = "MiddleCenter";
     [string]$DGV_ALIGN_MIDDLELEFT = "MiddleLeft";
     [string]$DGV_ALIGN_MIDDLERIGHT = "MiddleRight";


     [System.Data.DataSet]$fdsData;
     [string]$FSTR_TABLE_NAME_MAILDATA;

     $lblSearhParam=[Label]@{
         Location = [Point]::new(5,5);
         Size=[Size]::new(1043,20);
         Text = “◆検索条件”;
         Anchor="Top,Left,Right";
     }

     $lblSubjectOrTextBody=[Label]@{
         Location = [Point]::new(10,35);
         Size=[Size]::new(100,21);
         Text = “件名・本文”;
     }

     $txtSubjectOrTextBody = [TextBox]@{
         Location = [Point]::new(112,35);
         Size=[Size]::new(200,20);
     }

     $lblSubject =[Label]@{
         Location = [Point]::new(320,35);
         Size=[Size]::new(100,21);
         Text = “件名”;
     }

     $txtSubject = [TextBox]@{
         Location = [Point]::new(422,35);
         Size=[Size]::new(200,20);
     }

     $lblTextBody =[Label]@{
         Location = [Point]::new(627,35);
         Size=[Size]::new(100,21);
         Text = “本文”;
     }

     $txtTextBody = [TextBox]@{
         Location = [Point]::new(730,35);
         Size=[Size]::new(250,20);
     }


     $lblFrom =[Label]@{
         Location = [Point]::new(10,60);
         Size=[Size]::new(100,21);
         Text = “From”;
     }

     $txtFrom = [TextBox]@{
         Location = [Point]::new(112,60);
         Size=[Size]::new(200,20);
     }

     $lblTo =[Label]@{
         Location = [Point]::new(320,60);
         Size=[Size]::new(100,21);
         Text = “To”;
     }

     $txtTo = [TextBox]@{
         Location = [Point]::new(422,60);
         Size=[Size]::new(200,20);
     }

     $lblCC =[Label]@{
         Location = [Point]::new(627,60);
         Size=[Size]::new(100,21);
         Text = “CC”;
     }

     $txtCC = [TextBox]@{
         Location = [Point]::new(730,60);
         Size=[Size]::new(250,20);

     }

     $lblSendOn=[Label]@{
         Location = [Point]::new(10,85);
         Size=[Size]::new(100,21);
         Text = “送信日”;
     }

     $txtSendOnStart= [TextBox]@{
         Location = [Point]::new(112,85);
         Size=[Size]::new(75,20);
     }


     $lblSendOnTo=[Label]@{
         Location = [Point]::new(190,90);
         Size=[Size]::new(15,21);
         Text = “~”;
     }

     $txtSendOnEnd = [TextBox]@{
         Location = [Point]::new(210,85);
         Size=[Size]::new(75,20);
     }

     $lblReceived=[Label]@{
         Location = [Point]::new(320,85);
         Size=[Size]::new(100,21);
         Text = “受信日”;
     }

     $txtReceivedStart= [TextBox]@{
         Location = [Point]::new(422,85);
         Size=[Size]::new(75,20);
     }


     $lblReceivedTo=[Label]@{
         Location = [Point]::new(500,90);
         Size=[Size]::new(15,21);
         Text = “~”;
     }

     $txtReceivedEnd = [TextBox]@{
         Location = [Point]::new(520,85);
         Size=[Size]::new(75,20);
     }

     $btnSearch = [Button]@{
         Location = [Point]::new(950,100);
         Size=[Size]::new(75,25);
         Text = "検索";
     }

     $lblResultTitle=[Label]@{
         Location = [Point]::new(5,130);
         Size=[Size]::new(1043,20);
         Text = “◆検索結果一覧”;
     }

     $splitVertical=[SplitContainer]@{
         Location = [Point]::new(5,153);
         Size=[Size]::new(1043,430);
         Anchor="Top,Left,Bottom,Right";
         Orientation = "Vertical";
         BorderStyle = "FixedSingle";
     }


     $dgv = [DataGridView]@{
         Location = [Point]::new(5,153);
         Size=[Size]::new(624,430);
         AutoGenerateColumns=$False;
         AllowUserToAddRows = $False;
         AllowUserToDeleteRows = $False;
         ReadOnly=$True;
         Dock="Fill";
     }

     $txtResultTextBody = [TextBox]@{
         Location = [Point]::new(630,153);
         Size=[Size]::new(418,430);
         MultiLine=$true;
         Dock="Fill";
     }

     $btnLoadData = [Button]@{
         Location = [Point]::new(5,600);
         Size=[Size]::new(100,25);
         Anchor="Left,Bottom";
         Text = "データ更新";
     }

     # コンストラクタ
     clsfrm00_0000_MainForm(){

         $this.Initialize();

         $this.SetEvents();

         # Load_Events
         $frm_Load = {
             $this.splitVertical.SplitterDistance = 650;

         }
         $this.add_Load($frm_Load);

     }

     # ------------------------------------------------------------------------------------------------------------
     # 初期化処理
     # ------------------------------------------------------------------------------------------------------------
     Initialize() {
         $this.Text = "メール一覧画面"
         $this.Size = [Size]::new(1070, 670);
         $this.Font = New-Object Drawing.Font("MS Gothic",10.5) ;
         $this.dgv.Font = New-Object Drawing.Font("MS Gothic",10.5) ;


         #  ラベルプロパティを設定
         $this.SetLabelDefaultProperty($this.lblSearhParam);
         $this.SetLabelDefaultProperty($this.lblSubjectOrTextBody);
         $this.SetLabelDefaultProperty($this.lblSubject);
         $this.SetLabelDefaultProperty($this.lblTextBody);
         $this.SetLabelDefaultProperty($this.lblFrom);
         $this.SetLabelDefaultProperty($this.lblTo);
         $this.SetLabelDefaultProperty($this.lblCc);
         $this.SetLabelDefaultProperty($this.lblSendOn);
         $this.SetLabelDefaultProperty($this.lblReceived);
         $this.SetLabelDefaultProperty($this.lblResultTitle);

         # テキストボックスを設定
         $this.SetTextBoxDefaultProperty($this.txtSubjectOrTextBody);
         $this.SetTextBoxDefaultProperty($this.txtSubject);
         $this.SetTextBoxDefaultProperty($this.txtTextBody);
         $this.SetTextBoxDefaultProperty($this.txtFrom);
         $this.SetTextBoxDefaultProperty($this.txtTo);
         $this.SetTextBoxDefaultProperty($this.txtCc);
         $this.SetTextBoxDefaultProperty($this.txtSendOnStart);
         $this.SetTextBoxDefaultProperty($this.txtSendOnEnd);
         $this.SetTextBoxDefaultProperty($this.txtReceivedStart);
         $this.SetTextBoxDefaultProperty($this.txtReceivedEnd);
         $this.SetTextBoxDefaultProperty($this.txtResultTextBody);

         # DataGridViewoを設定
         $this.InitializeDataGridView();

         #  検索結果一覧及び履歴一覧を追加する
         $this.Controls.Add($this.lblSearhParam);
         $this.Controls.Add($this.lblSubjectOrTextBody);
         $this.Controls.Add($this.txtSubjectOrTextBody);
         $this.Controls.Add($this.lblSubject);
         $this.Controls.Add($this.txtSubject);
         $this.Controls.Add($this.lblTextBody);
         $this.Controls.Add($this.txtTextBody);
         $this.Controls.Add($this.lblFrom);
         $this.Controls.Add($this.txtFrom);
         $this.Controls.Add($this.lblTo);
         $this.Controls.Add($this.txtTo);
         $this.Controls.Add($this.lblCc);
         $this.Controls.Add($this.txtCc);
         $this.Controls.Add($this.lblSendOn);
         $this.Controls.Add($this.txtSendOnStart);
         $this.Controls.Add($this.lblSendOnTo);
         $this.Controls.Add($this.txtSendOnEnd);
         $this.Controls.Add($this.lblReceived);
         $this.Controls.Add($this.txtReceivedStart);
         $this.Controls.Add($this.lblReceivedTo);
         $this.Controls.Add($this.txtReceivedEnd);


         $this.Controls.Add($this.btnSearch);
         $this.Controls.Add($this.btnLoadData);



         $this.Controls.Add($this.lblResultTitle);
         $this.splitVertical.Panel1.Controls.Add($this.dgv);
         $this.splitVertical.Panel2.Controls.Add($this.txtResultTextBody);
         $this.Controls.Add($this.splitVertical);


     }


     SetEvents(){

         # データ読み込み
         $btnSearch_Click =  {
             $sender = $args[0];
             $e = $args[1];
             $form = $sender.FindForm();
             $form.DoSearch();
         }
         $this.btnSearch.add_Click($btnSearch_Click);


         # データ読み込み
         $btnLoadData_Click =  {
             $sender = $args[0];
             $e = $args[1];
             $form = $sender.FindForm();
             $form.LoadData();
         }
         $this.btnLoadData.add_Click($btnLoadData_Click);


         $dgv_CurrentCellChanged = {
             $sender = $args[0];
             $e = $args[1];
             $form = $sender.FindForm();

             #$ds  = $form.dgv.DataSource;
             #if ($ds  -eq $null){ return ;}
             #if ($ds.Tables[$form.dgv.DataMember] -eq $null){ return ;}
             $dt = $form.dgv.DataSource;
             if ($dt  -eq $null){ return ;}

             if ($form.dgv.CurrentRow -eq $null){ return ;}

             # 改行コードを変換。TextBoxが対応している改行コードはCRLFのみ
             #$form.txtResultTextBody.Text = $ds.Tables[$form.dgv.DataMember].Rows[$form.dgv.CurrentRow.Index]["strTextBody"];
             $form.txtResultTextBody.Text = $dt.Rows[$form.dgv.CurrentRow.Index]["strTextBody"].Replace("`r`n", "`n").Replace("`r", "`n").Replace("`n", "`r`n");
         }
         $this.dgv.add_CurrentCellChanged($dgv_CurrentCellChanged);


     }

     InitializeDataGridView(){

         $this.dgv.Columns.Clear();

         $dc = $this.GetDataGridColumnText("colSubject","件名",200,$this.DGV_ALIGN_MIDDLELEFT,"strSubject");
         $this.dgv.Columns.Add($dc)
         $dc = $this.GetDataGridColumnText("colTo","送信先",100,$this.DGV_ALIGN_MIDDLELEFT,"strTo");
         $this.dgv.Columns.Add($dc)
         $dc = $this.GetDataGridColumnText("colFrom","送信元",100,$this.DGV_ALIGN_MIDDLELEFT,"strFrom");
         $this.dgv.Columns.Add($dc)
         $dc = $this.GetDataGridColumnText("colSendOn","送信日時",100,$this.DGV_ALIGN_MIDDLELEFT,"strSendOn");
         $this.dgv.Columns.Add($dc)
         $dc = $this.GetDataGridColumnText("colReceived","受信日時",100,$this.DGV_ALIGN_MIDDLELEFT,"strReceivedTime");
         $this.dgv.Columns.Add($dc)

         $this.dgv.DataSource = $null;

     }


     # ------------------------------------------------------------------------------------------------------------
     # DataGridViewのColumnを取得する
     # ------------------------------------------------------------------------------------------------------------
     [DataGridViewTextBoxColumn]GetDataGridColumnText([string]$strName,[string]$strHeaderText,[int]$intWidth,[string]$strCellTextAligment,[string]$strDataPropName) 
{
         $dc = New-Object DataGridViewTextBoxColumn ;
         $dc.Name = $strName;
         $dc.HeaderText = $strHeaderText;
         $dc.Width = $intWidth;
         $dc.DefaultCellStyle.Alignment = $strCellTextAligment;
         $dc.HeaderCell.Style.Alignment = "MiddleCenter";
         $dc.ReadOnly=$True;
         $dc.AutoSizeMode = "NotSet"      # 処理速度向上
         #$dc.SortMode = "NotSortable";    # 並び替えを行わないようにすることでヘッダのテキストを中央寄せにできる
         $dc.DataPropertyName = $strDataPropName;
         return $dc;
     }

     SetLabelDefaultProperty([Label]$lbl){
         $lbl.BackColor = [System.Drawing.Color]::FromArgb(213, 219, 255);
         $lbl.BorderStyle="FixedSingle";
         $lbl.TextAlign = "MiddleLeft";
         $lbl.AutoSize = $False;
         $lbl.Anchor="Top,Left";
     }

     SetTextBoxDefaultProperty([TextBox]$txt){
         $txt.BorderStyle="FixedSingle";
         $txt.Anchor="Top,Left";
     }


     LoadData(){

         $MailToDataSet = New-Object clsMailToDataSet;

         Write-Host(Get-Location);

         $dialog = New-Object System.Windows.Forms.FolderBrowserDialog;
         $dialog.Description = "メールのあるフォルダを選択してください";
  
         if($dialog.ShowDialog() -eq "OK") {
            $MailToDataSet.Execute($dialog.SelectedPath);
   
            #$MailToDataSet.fds.WriteXml("sample.xml");

            # RowFilterで検索結果の絞り込みをしようとするとDataTableをバインドしておかないと反映されない
            #$this.dgv.DataSource = $MailToDataSet.fds;
            #$this.dgv.DataMember = $MailToDataSet.FSTR_DATA_TABLE_NAME_MAIL;
            $this.dgv.DataSource = $MailToDataSet.fds.Tables[$MailToDataSet.FSTR_DATA_TABLE_NAME_MAIL];

         }
      
     }

     DoSearch(){

         #$ds = $this.dgv.DataSource;
         #$dt = $ds.Tables[$this.dgv.DataMember];

         [string]$strWhere = "";

         if ($this.txtSubjectOrTextBody.Text -ne ""){
             if( $strWhere -ne "" ) { $strWhere +=" AND "; }
             $strWhere += " (strSubject LIKE '%" + $this.ReplaceFilterValue($this.txtSubjectOrTextBody) + "%' OR strTextBody LIKE '%" + $this.ReplaceFilterValue($this.txtSubjectOrTextBody) + "%') ";
         } elseif ($this.txtSubject.Text -ne ""){
             if( $strWhere -ne "" ) { $strWhere +=" AND "; }
             $strWhere += " (strSubject LIKE '%" + $this.ReplaceFilterValue($this.txtSubject) + "%') ";
         } elseif ($this.txtTextBody.Text -ne ""){
             if( $strWhere -ne "" ) { $strWhere +=" AND "; }
             $strWhere += " (strTextBody LIKE '%" + $this.ReplaceFilterValue($this.txtTextBody) + "%') ";
         } elseif ($this.txtFrom.Text -ne ""){
             if( $strWhere -ne "" ) { $strWhere +=" AND "; }
             $strWhere += " (strFrom LIKE '%" + $this.ReplaceFilterValue($this.txtFrom) + "%') ";
         } elseif ($this.txtTo.Text -ne ""){
             if( $strWhere -ne "" ) { $strWhere +=" AND "; }
             $strWhere += " (strTo LIKE '%" + $this.ReplaceFilterValue($this.txtTo) + "%') ";
         } elseif ($this.txtCc.Text -ne ""){
             if( $strWhere -ne "" ) { $strWhere +=" AND "; }
             $strWhere += " (strCc LIKE '%" + $this.ReplaceFilterValue($this.txtCc) + "%') ";
         } elseif (($this.txtSendOnStart.Text -ne "") -or ($this.txtSendOnEnd.Text -ne "")) {
             if( $strWhere -ne "" ) { $strWhere +=" AND "; }

             [string]$strValue = "";
             $strValue =$this.ReplaceFilterValue($this.txtSendOnStart).Replace("/","").Replace("-","") ;
             if ($strValue -eq ""){$strValue = "00000101";}
             $strWhere += " ( ";
             $strWhere += "   '" + $strValue + "' <= strSendOnForSearch  ";
             $strWhere += "    AND  ";

             $strValue =$this.ReplaceFilterValue($this.txtSendOnEnd).Replace("/","").Replace("-","") ;
             if ($strValue -eq ""){$strValue = "99991231";}
             $strWhere += "  strSendOnForSearch <= '" + $strValue + "' ";
             $strWhere += " ) ";

         } elseif (($this.txtReceivedStart.Text -ne "") -or ($this.txtReceivedEnd.Text -ne "")) {
             if( $strWhere -ne "" ) { $strWhere +=" AND "; }

             [string]$strValue = "";
             $strValue = $this.ReplaceFilterValue($this.txtReceivedStart).Replace("/","").Replace("-","") ;
             if ($strValue -eq ""){$strValue = "00000101";}
             $strWhere += " ( ";
             $strWhere += "   '" + $strValue + "' <= strReceivedTimeForSearch  ";
             $strWhere += "    AND  ";

             $strValue = $this.ReplaceFilterValue($this.txtReceivedEnd).Replace("/","").Replace("-","") ;
             if ($strValue -eq ""){$strValue = "99991231";}
             $strWhere += "  strReceivedTimeForSearch  <= '" + $strValue + "' ";
             $strWhere += " ) ";
         }


         $dt = $this.dgv.DataSource;
         $dt.DefaultView.RowFilter = $strWhere;

     }

     [string]ReplaceFilterValue([TextBox]$txt){
         return $txt.Text;
     }


}


#########################################################
# メール解析用クラス
#  メール解析のためにMicrosoft Collaboration Data Objects(CDO)を利用する
#########################################################
class clsMailAnalyzer {
     $objCdoMessage;
     [int]$fintId;
     [string]$fstrFullPath;
     [string]$fstrFileName;
     [string]$fstrSubject;
     [string]$fstrFrom;
     [string]$fstrTo;
     [string]$fstrCc;
     [string]$fstrBcc;
     [string]$fstrSendOn;        # 送信時間
     [string]$fstrReceivedTime;  # 受信時間
     [string]$fstrTextBody;
     [string]$fstrAttachmentsName;
     $flstAttachmentsName;

# コンストラクタ
     clsMailAnalyzer(){
         $this.objCdoMessage = New-Object -ComObject CDO.Message;
         $this.fintId = 1;
     }

# ----------------------------------------------------------------------------
# 解析処理
# ----------------------------------------------------------------------------
     Analyze([string]$strMailPath){
# 処理対象ファイルをテキストファイルとして読み込み
         $stream = New-Object -ComObject ADODB.Stream
         $stream.Type = 2 # 1=Binary, 2=Text
         $stream.Charset = "utf-8"
         $stream.Open();
         $stream.LoadFromFile($strMailPath);

         # HTML形式のメールの場合は、HTMLBodyに値が設定され、TextBodyが空白になっている。
         # AutoGenerateTextBodyを設定するとHTMLBodyからTextBodyを生成することができる。。
         # 基本はFalseに設定して、メールの原文通りのTextBodyの内容を取得するが、TextBodyが空白の場合にTrueに設定してHTMLBodyからTextBodyを生成する
         $this.objCdoMessage.AutoGenerateTextBody = $false;

# メモリ上に読み込んだメールファイルを開く
         $this.objCdoMessage.DataSource.OpenObject($stream,"_Stream");

         $this.fstrFullPath = $strMailPath;
         $this.fstrFileName = Split-Path $this.fstrFullPath -Leaf
         $this.fstrSubject = $this.objCdoMessage.Subject;          # 件名
         $this.fstrFrom = $this.objCdoMessage.From;                # 差出人
         $this.fstrTo = $this.objCdoMessage.To;                    # 送付先(To)
         $this.fstrCc = $this.objCdoMessage.Cc;                    # 送付先(Bcc)
         $this.fstrBcc = $this.objCdoMessage.Bcc;                  # 送付先(Cc)
         $this.fstrSendOn = $this.objCdoMessage.SentOn.ToString("yyyy-MM-dd hh:mm:ss");            # 送信時間
         $this.fstrReceivedTime =$this.objCdoMessage.ReceivedTime.ToString("yyyy-MM-dd hh:mm:ss"); # 受信時間
         $this.fstrTextBody = $this.objCdoMessage.TextBody;         # 本文

         # TextBodyが空白の場合は、HTMLBodyから自動生成を行う
         if($this.fstrTextBody -eq ""){
             $this.objCdoMessage.AutoGenerateTextBody = $true;
             $this.fstrTextBody = $this.objCdoMessage.TextBody; # 本文
         }

         # 添付ファイルの名前を保持
         $this.flstAttachmentsName = New-Object "System.Collections.Generic.List[string]";
         $this.fstrAttachmentsName = "";
         foreach($item in $this.objCdoMessage.Attachments){
             $this.flstAttachmentsName.Add($item.FileName);
             if($this.fstrAttachmentsName -ne ""){ $this.fstrAttachmentsName += ",";}
             $this.fstrAttachmentsName += $item.FileName ;
         }

         $stream.Close();
         $stream = $null;

     }

     Finish(){

     }

}


class clsMailToDataSet {
     $MailAnalizer;
     [string]$strTemp;
     [string]$FSTR_DATA_TABLE_NAME_MAIL = "dtMail";
     [string]$FSTR_DATA_TABLE_NAME_MAIL_ATTACHMENTS = "dtMailAttachments";
     [System.Data.DataSet]$fds;
     [System.Data.DataTable]$fdt;
     [System.Data.DataTable]$fdtAttachments;

     # コンストラクタ
     clsMailToDataSet(){
         $this.MailAnalizer = New-Object clsMailAnalyzer;
         [string]$strNow = Get-Date -Format "yyyyMMddhhmmss";
         $this.strTemp = Join-Path $PSScriptRoot ("temp" + $strNow);

         $this.fds = New-Object System.Data.DataSet;
         $this.SetDataTableColumn();
         $this.fds.Tables.Add($this.fdt);
         $this.fds.Tables.Add($this.fdtAttachments);

     }


     SetDataTableColumn(){
         $this.fdt = New-Object System.Data.DataTable;
         $this.fdt.TableName = $this.FSTR_DATA_TABLE_NAME_MAIL;
         $this.fdt.Columns.Add("strID",[string])
         $this.fdt.Columns.Add("strSubject",[string])
         $this.fdt.Columns.Add("strFrom",[string])
         $this.fdt.Columns.Add("strTo",[string])
         $this.fdt.Columns.Add("strCc",[string])
         $this.fdt.Columns.Add("strBcc",[string])
         $this.fdt.Columns.Add("strSendOn",[string])
         $this.fdt.Columns.Add("strReceivedTime",[string])
         $this.fdt.Columns.Add("strSendOnForSearch",[string])
         $this.fdt.Columns.Add("strReceivedTimeForSearch",[string])
         $this.fdt.Columns.Add("strTextBody",[string])
         $this.fdt.Columns.Add("strFilePath",[string])
         $this.fdt.Columns.Add("strFileName",[string])
         $this.fdt.Columns.Add("strAttachmentsName",[string])

         $this.fdtAttachments = New-Object System.Data.DataTable;
         $this.fdtAttachments.TableName = 
$this.FSTR_DATA_TABLE_NAME_MAIL_ATTACHMENTS;
         $this.fdtAttachments.Columns.Add("strID",[string])
         $this.fdtAttachments.Columns.Add("strSubID",[string])
         $this.fdtAttachments.Columns.Add("strAttachmentName",[string])


     }

     # 処理を実行
     Execute($strFolderPath){

         # 圧縮ファイルを解凍する
         # ファイルパスが長すぎる場合などはエラーが発生する
         Get-ChildItem -Recurse -Path $strFolderPath -ErrorAction Ignore | ForEach-Object {
             $intCount += 1;

             if ($_.Extension -eq ".zip"){
                 # Zipファイルの場合
                 ## Tempフォルダ作成
                 New-Item -Path $this.strTemp -ItemType Directory;
                 ## Workフォルダへ解凍
                 Expand-Archive -LiteralPath $_.FullName -DestinationPath $this.strTemp;
                 ## Workフォルダ内の.emlファイルを対象に読み取り処理を実行
                 Get-ChildItem -Recurse -Path $this.strTemp -ErrorAction Ignore | ForEach-Object {
                     if ($_.Extension -eq ".eml"){
                         # .emlファイルの場合
                         ## .emlファイルを対象に読み取り処理を実行
                         $this.MailAnalizer.Analyze($_.FullName);
                         $this.AddRowToDataTable();
                     }
                 }
                 ## Workフォルダの中身を削除
                 Remove-Item $this.strTemp -Recurse;

             } elseif ($_.Extension -eq ".eml"){
                 # .emlファイルの場合
                 ## .emlファイルを対象に読み取り処理を実行
                 $this.MailAnalizer.Analyze($_.FullName);
                 $this.AddRowToDataTable();
             }
         }

     }

     AddRowToDataTable(){
         $dr = $this.fdt.NewRow();
         $strID = New-Guid;
         $dr["strID"] = $strID
         $dr["strSubject"]=$this.MailAnalizer.fstrSubject;
         $dr["strFrom"]=$this.MailAnalizer.fstrFrom;
         $dr["strTo"]=$this.MailAnalizer.fstrTo;
         $dr["strCc"]=$this.MailAnalizer.fstrCc;
         $dr["strBcc"]=$this.MailAnalizer.fstrBcc;
         $dr["strSendOn"]=$this.MailAnalizer.fstrSendOn;
         $dr["strReceivedTime"]=$this.MailAnalizer.fstrReceivedTime;
         $dr["strTextBody"]=$this.MailAnalizer.fstrTextBody;
         $dr["strFilePath"]=$this.MailAnalizer.fstrFullPath;
         $dr["strFileName"]=$this.MailAnalizer.fstrFileName;
         $dr["strAttachmentsName"]=$this.MailAnalizer.fstrAttachmentsName;

         if($dr["strSendOn"] -ne "") { $dr["strSendOnForSearch"]=$this.MailAnalizer.fstrSendOn.Substring(0,10).Replace("-","")}
         else {$dr["strSendOnForSearch"] = "" };
         if($dr["strReceivedTime"] -ne "") { $dr["strReceivedTimeForSearch"]=$this.MailAnalizer.fstrReceivedTime.Substring(0,10).Replace("-","")}
         else {$dr["strReceivedTimeForSearch"] = "" };

         $this.fdt.Rows.Add($dr);


         [int]$intCounter = 1;
         foreach($item in $this.MailAnalizer.flstAttachments) {
             $drAttachment = $this.fdtAttachments.NewRow();
             $drAttachment["strID"] = $strID ;
             $drAttachment["strSubID"] = $intCounter.ToString() ;
             $drAttachment["strAttachmentName"] = $item ;
             $this.fdtAttachments.Rows.Add($drAttachment);
         }

     }

}


$frm = New-Object clsfrm00_0000_MainForm;
$frm.ShowDialog();




PowerShellでDataSetのXMLの内容をシリアライズし、生成された文字列を再度デシリアライズする

修正前のテーブルの内容をXMLデータとして保存し、ログテーブルに格納することで、履歴を退避する


 Step1
   DataSetをシリアライズしXML形式の文字列を作成する

 Step2
   文字列をログテーブルへ保存する(普通にInsert)

 Step3
   ログテーブルに格納されているXML形式の文字列をDataSetにデシリアライズする

【サンプルソース】
 # テストデータ作成
[System.Data.DataTable]$dt = New-Object "System.Data.DataTable";
$dt.TableName = "dtMemo";
$dt.Columns.Add("col1",[string]);
$dt.Columns.Add("col2",[string]);
$dt.Columns.Add("col3",[string]);
$dt.Columns.Add("col4",[string]);
$dr = $dt.NewRow();
$dr["col1"]="sample1-1";
$dr["col2"]="sample1-2";
$dr["col3"]="sample1-3";
$dr["col4"]="sample1-4";
$dt.Rows.Add($dr);
$dr = $dt.NewRow();
$dr["col1"]="sample2-1";
$dr["col2"]="sample2-2";
$dr["col3"]="sample2-3";
$dr["col4"]="sample2-4";
$dt.Rows.Add($dr);

[System.Data.DataSet]$ds = New-Object "System.Data.DataSet";
$ds.Tables.Add($dt);
# XMLデータの取得
$strXML = $ds.GetXml() ;
# XMLの内容確認
Write-Host($strXML);
# 文字列のXMLを読み込んでDataSetを作成
[System.Data.DataSet]$dsFromXML = New-Object "System.Data.DataSet";
[System.Xml.XmlReaderSettings]$xmlSettings = New-Object "System.Xml.XmlReaderSettings";
$xmlSettings.Async = $true;
$sr = New-Object "System.IO.StringReader"$strXML;
$xmlDoc = [System.Xml.XmlReader]::Create($sr,$xmlSettings);
$dsFromXML.ReadXml($xmlDoc);
# 結果確認
$dsFromXML.GetXml();

PowerShellでEdgeを自動化(インストール不要。参考:郵便追跡サービス自動操作)

1.経緯について


 RPAのソフトをインストールできないので、これまでVBSでCreateObjectでブラウザの自動操作をすることがたまにあった。
※いざというときの手札として持っているだけで安心感が段違い

 見た目上IEがインストールされていなくても、CreateObjectをしたときにIEを呼び出すことができている状態ではあるが、Edgeでないと動かないようなシステムが出てきた場合のためにWebDriverなどのソフトをインストールせずにPowerShellといくつかのファイルをダウンロードするだけで自動化できる手段を探したところ、qiita.comにいくつか紹介がされているものがあった。
 試してみたところエラーに次ぐエラーですぐに試すことができなかったので備忘録として記録をしておく。
 サンプルとして郵便追跡サービスを自動操作しCSVをダウンロードするソースコードを載せてありますが、あくまで検証用です。サーバーに負荷をかけるような処理はしないでください。
 ※なお今時であればMicrosoftで無料のPowerautomateがあるので、それを使えばもっと安定かつノーコードで行けるのでいいと思いますが・・・。

2.はまったところ


  ・はまったポイント1

    ダウンロードしたdllを「LoadFile("WebDriver.dll")」で読み込んだらエラーが発生。
    エラーメッセージの一部:"ネットワーク上の場所からアセンブリを読み込もうとしました。これにより~~"

     → ネットからダウンロードしたdllはセキュリティ上そのままは使えない、らしい。
       dllを右クリックしてプロパティを修正することで回避する
     

  ・はまったポイント2

    「Newtonsoft.Jsonの依存関係がどうとか」いうエラーが発生し、「EdgeDriver」のインスタンス化でエラーが発生。
    色々調べたところWebDriver.dllをダウンロードしたときに最新のものを取ってきすぎてバージョンが対応していなかった。
          https://www.nuget.org/packages/Selenium.WebDriver/
     最新のdllをダウンロードすればいいと安直に思って、4.22.0を「Download pakage」を押下し、ダウンロードして、「selenium.webdriver.4.22.0.nupkg」の拡張子「nupkg」を「zip」に変更し、「lib\netstandard2.0\WebDriver.dll」を取り出して、使おうとしていた。


   ちゃんと見ると、青背景で「.NET Standard 2.0」と記載がされてる。


     → サイトが表示されたら過去のものを手に入れるため「Full status」をクリックする。


        「4.21.0」から順に開いていったが欲しいフレームワークのものがなかったので、「Show more」をクリックする


       ひたすら順番に見ていくと、「4.12.3」でほしいフレームワークのものがあった。


       すかさず、「Download pakage」をクリックして、中身から「WebDriver.dll」を取り出す。
  
       ※上述の「はまったポイント1」で対応をしたプロパティ設定の「セキュリティ」の設定変更を忘れないように!

  ・はまったポイント3

    要素が表示されるまでの待機を入れるために「WaitDriverWait」(WebDriverSupport.dll)を入れて、「ExpectedConditions」クラスを指定しようとしたら全然できなかった。
    → いろいろと調べていくともう廃止されている。「$edgeDriver.Manage().Timeouts().ImplicitWait = (New-TimeSpan -Seconds 10)」とすることで要素が取得するまで待ってくれる、らしい。

3.最終的な手順

  1)自分の環境に応じたバージョンのWebDriver.dllをダウンロードする

            https://www.nuget.org/packages/Selenium.WebDriver/
      ※自分の環境で作業した時点だと「4.12.3」の「lib\net48\WebDriver.dll」で動いた。人によっては環境が異なるので、頑張って試していただければと思います。

  2)ダウンロードしたWebDriver.dllを右クリック→プロパティを表示し、上述の「セキュリティ」を「許可する」に設定する


  3)EdgeのWebDriver(msedgedriver.exe)をダウンロードする

    https://developer.microsoft.com/ja-jp/microsoft-edge/tools/webdriver/?form=MA13LH&ch=1


  4)デスクトップ等にフォルダを作成し、「Main.ps1」「WebDriver.dll」「msedgedriver.exe」を保存する


  5)Main.ps1に下記のソースを貼り付ける

   
   例)郵便の追跡番号を自動で設定しCSVファイルをダウンロードする

# --------------- Parameter Start

#  ◆ ファイルのダウンロードを待機する設定値
$INT_MAX_LOOP_COUNT_FOR_RETRY_DOWNLOAD_CHECK = 60; # 最大60回まで繰り返す
$INT_SLEEP_SECONDS_FOR_RETRY_DOWNLOAD_CHECK  = 1;  # リトライするまでの待機時間
#    ファイルのダウンロードが終わっているかどうかをチェックする際の待機時間と最大チェック回数。
#    待機時間×最大チェック回数の時間以内にダウンロードが終わらないと処理がエラーで終了する

$STR_DOWNLOAD_DIRECTORY = "C:\Users\ユーザー名\Downloads\DriverFolder";

# --------------- Parameter End ファイルのダウンロードを待機する設定値

# dllのロード
$strCurrentDir  = Split-Path $MyInvocation.MyCommand.path;

$webDriverDllPath = $strCurrentDir + "\WebDriver.dll";
$edgeDriverDirPath = $strCurrentDir + "\";

$asm = [System.Reflection.Assembly]::LoadFile($webDriverDllPath)


# ダウンロードフォルダが存在しない場合はフォルダを作成する
If(!(test-path $STR_DOWNLOAD_DIRECTORY)) { New-Item -ItemType Directory -Force -Path $STR_DOWNLOAD_DIRECTORY; }

# ◆ Edgeのオプションを生成
$Options = New-Object OpenQA.Selenium.Edge.EdgeOptions

##  ダウンロードフォルダを指定し、ダウンロードプロンプトが表示されないようにする。
##   https://stackoverflow.com/questions/35446893/c-sharp-set-default-download-directory-chrome-webdriver
$Options.AddUserProfilePreference("download.default_directory", $STR_DOWNLOAD_DIRECTORY);
$Options.AddUserProfilePreference("download.prompt_for_download", $false);
$Options.AddUserProfilePreference("disable-popup-blocking", "true");

##  拡張機能の自動更新を停止
$Options.AddArgument("useAutomationExtention=false")
##  「Edgeは自動テストソフトウェアによって制御されています」を非表示にする
$Options.AddExcludedArgument("enable-automation")
## ウィンドウサイズを最大にする
$Options.AddArgument("--start-maximized")

## EdgeDriverを生成する
$edgeDriver = New-Object OpenQA.Selenium.Edge.EdgeDriver($edgeDriverDirPath , $Options)

## Manage().Timeouts().ImplicitWaitを一度設定しておけば以降の
## FindElementやFindElementsで要素を取得するときに、毎回待機が行われるようになります。
## この処理が追加されたためWebDriverWaitを呼び出すときのExpectedConditionsクラスが廃止されている
$edgeDriver.Manage().Timeouts().ImplicitWait = (New-TimeSpan -Seconds 10)

# ◆ 郵便番号検索画面を操作する
##     郵便番号個別検索のケース
#$edgeDriver.Url = "https://trackings.post.japanpost.jp/services/srv/search/input"
#$x = $edgeDriver.FindElement([OpenQA.Selenium.By]::Name("requestNo1"))
#$x.SendKeys("123456789012")
## クリック
#$x = $edgeDriver.FindElement([OpenQA.Selenium.By]::Name("search"))
#$x.Click();

## 郵便番号連続のケース(最大100件ずつ検索ができる)
$edgeDriver.Url = "https://trackings.post.japanpost.jp/services/srv/sequenceNoSearch/input"

$x = $edgeDriver.FindElement([OpenQA.Selenium.By]::Name("requestNo"))
$x.SendKeys("123456789012") ## 調べたい番号の開始

$x = $edgeDriver.FindElement([OpenQA.Selenium.By]::Name("count"))
$x.SendKeys("1")

$x = $edgeDriver.FindElement([OpenQA.Selenium.By]::Name("sequenceNoSearch"))
$x.Click();

## Edgeの開発者ツールでCSVダウンロードボタンを見たところ、ダウンロード用のサブウィンドウを呼び出すJavaScriptを実行していることがわかったため、JavaScriptを呼び出す
$edgeDriver.ExecuteScript("openSubWindowForPost('openSequenceNoSearchCsvCreate', 'sequenceNoSearchCsv', 540, 380)");

# ◆ CSVダウンロード画面を操作する
## CSVをダウンロードするためのサブ画面が呼び出されるのでWebDriverで操作する画面を切り替える必要がある
$CurrentWindowHandle = $edgeDriver.CurrentWindowHandle; # 現在のウィンドウハンドルの番号を退避
$WindowHandles = $edgeDriver.WindowHandles;             # WebDriverが操作できるすべてのウィンドウハンドルを取得する

## ダウンロード前のファイル名を退避
$arrFileitemsBefore = Get-ChildItem $STR_DOWNLOAD_DIRECTORY -File
$intFileCount = 0;
if( $arrFileitemsBefore -eq $null){ $intFileCount = 0;}
elseif ( $arrFileitemsBefore -is [array]){ $intFileCount = $arrFileitemsBefore.Length; }
else { $intFileCount = 1; }

## ダウンロード画面にDriverの操作画面を切り替えて、ダウンロードボタンを押下する
foreach ($handle in $WindowHandles){
    if ($edgeDriver.SwitchTo().Window($handle).Title -like "*CSVダウンロード*"){
        # ダウンロードボタンが取得できるまで繰り返すようにする
        #  このサイトの場合は、「CSVファイル作成を完了しました。」という文字が取れるまで待機してもよい課と思われる
        $x = $edgeDriver.FindElement([OpenQA.Selenium.By]::Id("download"))
        $x.Click();
        break;
    }
}

## 現在のファイル名を取得して比較して、存在しないファイル名があればダウンロードしたファイルと判断する
$strNewFileName = "";
$intLoopCount = 1 ;
$blnCompleteDonload = $false;

# ダウンロードが完了するまで待機する
# EdgeやChromeの場合、ダウンロード中かどうかは拡張子が「.crdownload」というファイルが作成されるので拡張子が変わるまで待機する
# ただしタイムアウトとして最大待ち時間は設定する
do {
    # ダウンロード処理・待機時間後のファイル名を退避
    $arrFileitemsAfter = Get-ChildItem $STR_DOWNLOAD_DIRECTORY -File

    foreach($item in $arrFileitemsAfter){
        $result = $arrFileitemsBefore | Where-Object Name -eq $item.Name ;
        if($result -eq $null){
            $strNewFileName = $item.Name;
            if ($item.Extension -ne ".crdownload") {
                $blnCompleteDonload = $true;
                Write-Host("ダウンロード完了。ファイル名:" + $item.Name)
            }
            break;
        }
    }
    $intLoopCount += 1;
    if( $blnCompleteDonload -eq $true ){ break; }
    else{ Start-Sleep -Seconds $INT_SLEEP_SECONDS_FOR_RETRY_DOWNLOAD_CHECK; }

} while ($intLoopCount -le $INT_MAX_LOOP_COUNT_FOR_RETRY_DOWNLOAD_CHECK)

# ダウンロードが終了したため、オブジェクトの終了をする
$edgeDriver.quit();

Exit;








PowerShellでemlファイルを閲覧する

1.PowerShellでemlファイルを見たい!  メールについてデータ量が多くなってきた関係で.emlファイルとして抽出して、ZIP圧縮しているが、過去のメールを検索する方法がなくて困っていた。ネットのフリーソフトを使うことも考えたが、処理速度を気にしなければPowerShe...