PowerShellで単画面、複数画面を作成したいときのそれぞれの記述方法について

ケース1.1画面のみを作成するケース

1画面の作成だけで良い場合については、下記のようなソースコードを作成する。 個人的なイメージとしては全てをVB.NETでいうモジュールに記載しているイメージになる。 やろうと思えば画面ごとにPowerShellのファイルを分けて外部ファイルの読み込みを指定することで作成することもできるが結局は実行段階で1ファイルにまとめられてしまうことになるので、変数名を全て一意にしないとうまくいかない。 


 Menu.ps1で「$frm」という変数を定義し処理を記載し、frm01_0100.ps1で「$frm」を定義すると後から読み込み指定したファイルの内容が優先される。結局一つのファイルにすべて記載しているの変わらない。


 バッチ処理やCSVファイルの加工など複数画面にしなくてよいケースであれば下記のソースで書くほうがわかりやすくてよいと思われる。

複数の画面ファイルを作成するためには、普通のWindowsフォームを作成する時と同じように、Formを継承した独自クラスを作成したい場合は2.複数画面を作成するケースを参考にクラスを作成するとよい。



#アセンブリの読み込み
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

# 各パスを取得
# デバッグ用(Main.ps1で定義済)
# $CurrentDir  = Split-Path $MyInvocation.MyCommand.path
# $ScriptName = $MyInvocation.MyCommand.path
# $Basename    = $MyInvocation.MyCommand.Name

$CurrentDir  = Split-Path $MyInvocation.MyCommand.path
$ScriptName = $MyInvocation.MyCommand.path
$Basename    = $MyInvocation.MyCommand.Name

# 外部ファイルの読み込み。Powershellで外部ファイルをⅢ書するときには呼び出す前に指定しておく必要がある

#   共通関数
$CommonFile = $CurrentDir + "\Common\Common.ps1" 
.$CommonFile

#   データベースアクセス
$DbaFile = $CurrentDir + "\Common\DataBaseAccess.ps1" 
.$DbaFile

#   データベースアクセスサンプルソース
$DbaFormFile = $CurrentDir + "\020_frmDataBaseAccess.ps1" 
.$DbaFormFile

####  ↓ 画面固有の定義 ↓

#フォームの設定
$frmMenu = New-Object System.Windows.Forms.Form
$frmMenu.Size = New-Object System.Drawing.Size(800,600)
$frmMenu.Font=New-Object System.Drawing.Font("MS ゴシック", 10.5)
$frmMenu.Text = "メニュー画面"

#ボタンの設定
$btnShowSubForm = New-Object System.Windows.Forms.Button
$btnShowSubForm.Location = New-Object System.Drawing.Point(60,50)
$btnShowSubForm.Size = New-Object System.Drawing.Size(175,30)
$btnShowSubForm.Text = "データベーステスト"
$btnShowSubForm.Add_Click({
    OpenfrmDataBaseAccess($txtDbAccessPath.Text)
})
$frmMenu.Controls.Add($btnShowSubForm)


#ボタンの設定
$txtDbAccessPath = New-Object System.Windows.Forms.TextBox
$txtDbAccessPath.Location= New-Object System.Drawing.Point(10,50)
$txtDbAccessPath.Size = New-Object System.Drawing.Size(175,30)
$txtDbAccessPath.Text = ""
$frmMenu.Controls.Add($txtDbAccessPath)

#フォームの表示
$frmMenu.Add_Shown({$frmMenu.Activate()})
function OpenMainForm {
    [void] $frmMenu.ShowDialog()
}

# デバッグ用
OpenMainForm

2.複数画面を作成するケース

一覧画面と詳細画面を作成するケースなど複数の画面を連携させる場合にはどうしてもファイルを分けて作成するほうがよいケースが多い。その場合には通常のVisualStudioでプロジェクトを作成するように「Form」クラスを継承した 独自フォームを作成するのがよい。 書き方に慣れる必要はあるが、慣れてしまえばVisualStudioで開発するようにプログラムを作成することができる。

独自クラスも作成できるので、始めに色々と作っておくことで使いまわすことも容易にできる

例)
<プロジェクトの構成>

  • Main.ps1・・・後述するが、System.Windows.Formsのアセンブリロードエラー対策として必要
    • 0000_frmMenu.ps1・・・メニュー想定のフォーム
    • 0100_frmDataBaseAccess.ps1・・・データベースアクセステスト用フォーム
    • DataBaseAccess.ps1・・・データベース(sqlite3)へのアクセス用クラス。

Main.ps1

    Add-Type -AssemblyName System.Drawing;
    Add-Type -AssemblyName System.Windows.Forms;

    $CurrentDir  = Split-Path $MyInvocation.MyCommand.path;
    $ScriptName = $MyInvocation.MyCommand.path;
    $Basename    = $MyInvocation.MyCommand.Name;

    #   データベースアクセス
    $MenuFile = $CurrentDir + "\0000_frmMenu.ps1"; 
    .$MenuFile;

    [Application]::EnableVisualStyles();
    $form = [frmMenu]::new();
    $form.StartPosition="CenterScreen";   # 列挙体は文字列で指定する必要がある
    $form.ShowDialog();

0000_frmMenu.ps1

事前にsqlite3.dllのダウンロードとテスト用のデータベースを作成しておく

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

    #アセンブリの読み込み
    Add-Type -AssemblyName System.Drawing;
    Add-Type -AssemblyName System.Windows.Forms;

    #[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    #[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")


    $CurrentDir  = Split-Path $MyInvocation.MyCommand.path;
    $ScriptName = $MyInvocation.MyCommand.path;
    $Basename    = $MyInvocation.MyCommand.Name;

    # 外部ファイルの読み込み。Powershellで外部ファイルをⅢ書するときには呼び出す前に指定しておく必要がある

    #   データベースアクセス
    $DbaFile = $CurrentDir + "\0100_frmDataBaseAccess.ps1"; 
    .$DbaFile;
    
    Class frmMenu : System.Windows.Forms.Form {

        $txtFilePath = [TextBox]@{
            Location = [Point]::new(10,10);
            AutoSize=$False;
            Size=[Size]::new(700,25);
            Text = "C:\Users\UserName\Desktop\DataBase\NewProject.sqlite3";
        }
    
        $btnDBA = [Button]@{
            Location = [Point]::new(720,10);
            Size=[Size]::new(250,25);
            Text = “データベースアクセス”;
        }
    
        frmMenu(){
            $this.Size = [Size]::new(900, 700);
            $this.btnDBA.add_Click([Delegate]::CreateDelegate([EventHandler], $this, “btnDBA_Click”));
            $this.Controls.add($this.btnDBA);
            $this.Controls.add($this.txtFilePath);
            $this.txtFilePath.Text = "C:\Users\UserName\Desktop\DataBase\NewProject.sqlite3";
            $this.Font=New-Object System.Drawing.Font("MS ゴシック", 10.5)
        }
    
        btnDBA_Click([object] $sender, [EventArgs] $e){
            # インスタンス化(外部ファイルの参照設定を「.$DbaFile;」で処理済み)
            
            [string]$strdbPath=$this.txtFilePath.Text;
            $frmDba = New-Object frmDataBaseAccess($strdbPath)

            $frmDba.Owner=$this;
            $frmDba.StartPosition = "CenterParent"
            # メソッドの実行
            $frmDba.ShowDialog();

        }
    }

0100_frmDataBaseAccess.ps1

    #アセンブリの読み込み
    using namespace System.Windows.Forms;
    using namespace System.Drawing;
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing

    #   データベースアクセス
    $DbaFile = $CurrentDir + "\DataBaseAccess.ps1"; 
    .$DbaFile;

    Class frmDataBaseAccess :System.Windows.Forms.Form{
        [string]$fstrDBFilePath ;

        $btnRefresh = [Button]@{
            Location = [Point]::new(10,10);
            Size=[Size]::new(150,25);
            Text = “データ取得”;
        }

        $btnInsData = [Button]@{
            Location = [Point]::new(180,10);
            Size=[Size]::new(150,25);
            Text = “データ追加”;
        }

        $dgv = [DataGridView]@{
            Location = [Point]::new(10,70);
            Size=[Size]::new(800,600);
            AutoGenerateColumns=$True;
            ReadOnly=$True;

        }
        
        frmDataBaseAccess(){
            $this.Size = [Size]::new(900, 700);

            $this.bs.add_Click([Delegate]::CreateDelegate([EventHandler], $this, “Click”));
            $this.Controls.add($this.bs);
            $this.Controls.add($this.dgv);
        }
        
        frmDataBaseAccess([string]$strDBFilePath){
            $this.fstrDBFilePath = $strDBFilePath;

            $this.Size = [Size]::new(900, 700);
            $this.btnRefresh.add_Click([Delegate]::CreateDelegate([EventHandler], $this, “btnRefresh_Click”));
            $this.btnInsData.add_Click([Delegate]::CreateDelegate([EventHandler], $this, “btnInsData_Click”));
            $this.Controls.add($this.btnRefresh);
            $this.Controls.add($this.btnInsData);
            $this.Controls.add($this.dgv);
        }

        btnRefresh_Click([object] $sender, [EventArgs] $e){
        
            $dba = New-Object clsDataBaseAccess($this.fstrDBFilePath);

            $dt = $dba.GetData("SELECT * FROM t1");
            [MessageBox]::Show(“frmDataBaseAccess Clicked!”);
            
            $this.dgv.DataSource=$dt;

        }

        btnInsData_Click([object] $sender, [EventArgs] $e){
        
            $dba = New-Object clsDataBaseAccess($this.fstrDBFilePath);

            $dt = $dba.GetData("INSERT INTO t1(name,ostype) VALUES('Windows11','Win11')");

            [MessageBox]::Show(“frmDataBaseAccess btnInsData Clicked!”);
            

        }
    }

DataBaseAccess.ps1

データベースを操作するためのクラス

    # System.Data.sqlite.dllのロード
    $CurrentDir  = Split-Path $MyInvocation.MyCommand.path

    $sqliteDllPath = $CurrentDir + "\System.Data.sqlite.dll" 

    $asm = [System.Reflection.Assembly]::LoadFile($sqliteDllPath)
    #$asm = [System.Reflection.Assembly]::LoadFile("C:\Users\UseName\Desktop\PowerShell\Exp2Solution\System.Data.SQLite.dll")

    Class clsDataBaseAccess {

        $strDBFilePath="";

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

        }

        clsDataBaseAccess([string]$strDBPath){
            $this.strDBFilePath=$strDBPath;
        }

        ############################################################
        # SQLを実行しデータを取得する。戻り値はDataTable
        ############################################################
        [System.Data.DataTable]GetData([string]$strSQL){
            # SQLiteへの接続およびSQLステートメント発行用のSystem.Data.SQLite.SQLiteCommandの生成
            $sqliteConnection = New-Object System.Data.SQLite.SQLiteConnection;
            $sqliteConnection.ConnectionString = "Data Source = " + $this.strDBFilePath;
            $sqlcmd = New-Object System.Data.SQLite.SQLiteCommand;
            $sqlcmd.Connection = $sqliteConnection;
            $sqliteConnection.Open()

            # SELECT実行および表示
            $sqlcmd.CommandText = $strSQL
            $rs =  $sqlcmd.ExecuteReader()
            
            # Datatableにデータを取り込む
            $dataTable = New-Object System.Data.Datatable
            $dataTable.Load($rs)
            $dataTable | Format-Table

            # SQLiteの切断
            $sqlcmd.Dispose()
            $sqliteConnection.Close()

            return $dataTable
        }

        ############################################################
        # SQLを実行しデータを登録・更新・削除する
        # SQL インジェクション対策を実施していないので注意すること
        ############################################################
        ExecuteSQL_InsUpdDel([string]$strSQL){
            # SQLiteへの接続およびSQLステートメント発行用のSystem.Data.SQLite.SQLiteCommandの生成
            $sqliteConnection = New-Object System.Data.SQLite.SQLiteConnection;
            $sqliteConnection.ConnectionString = "Data Source = " + $this.strDBFilePath;
            $sqlcmd = New-Object System.Data.SQLite.SQLiteCommand;
            $sqlcmd.Connection = $sqliteConnection;
            $sqliteConnection.Open()

            # INSERT,UPDATE,DELETE 実行および表示(SQLインジェクション対策なし)
            $sqlcmd.CommandText = $strSQL
            $ret =  $sqlcmd.ExecuteNonQuery()

            # SQLiteの切断
            $sqlcmd.Dispose()
            $sqliteConnection.Close()

        }
    
        
    }

アセンブリのエラーについて

PowerShell ISEでデバッグしているときには個別のファイルでエラーが出ないが、右クリックでPowerShellで実行などを指定すると下記のようなメッセージが出るのはアセンブリのロードができていないことが原因。 そのため対策としては事前にMain.ps1でアセンブリのロードを記述し、Main.ps1でアセンブリのロードが完了してから、0000_frmMenu.ps1、0100_frmDataBaseAccess.ps1を読み込むようにしておく


--------------------------------------------------
発生場所 C:\Users\UserName\Desktop\PowerShell\Exp2Solution\0000_frmMenu.ps1:26 文字:17
+ Class frmMenu : System.Windows.Forms.Form {
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~
型 [System.Windows.Forms.Form] が見つかりません。
発生場所 C:\Users\UserName\Desktop\PowerShell\Exp2Solution\0000_frmMenu.ps1:27 文字:12
+     $bs = [Button]@{
+            ~~~~~~
型 [Button] が見つかりません。
発生場所 C:\Users\UserName\Desktop\PowerShell\Exp2Solution\0000_frmMenu.ps1:40 文字:10
+         [MessageBox]::Show(“Clicked!”);
+          ~~~~~~~~~~
型 [MessageBox] が見つかりません。
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : TypeNotFound
-------------------------
ソースコードに書いてあっても失敗するのは、読み込みが遅いからと思われる。
事前にmain.ps1などの別ファイルで「 Add-Type -AssemblyName System.Drawing;
Add-Type -AssemblyName System.Windows.Forms;」を記述し
Main.ps1 ← 主要なアセンブリをロードしたうえでfrmMenu.ps1を外部ファイルから参照して実行をする
VSCodeでデバッグするときにはターミナルで一度上記のアセンブリのAdd-Typeを実行しておく


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

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