【PowerShell】Windows環境での自作プログラム集 (サンプル集)

Windows環境での PowerShell の単体コード集

大半は、挙動を見るために作成するなどした比較的小さなコード集。
行頭に「#!/usr/bin/env pwsh」があるものは、Linux 環境で作成し、動作確認したものです。


1章 標準API
 1.1 実行時の注意点
 1.2 コピー処理
   mkdir_foler_with_date.ps1
 1.3 ディレクトリ作成
   copy_item.ps1
 1.4 コピー、削除、親ディレクトリ名取得、ディレクトリ作成、7z解凍
   cp_rm_mkdir_7z_dirname.ps1
 1.5 ディレクトリごと削除する
   rmdir.ps1
 1.6 スクリプトの引数を受け取る
   args1.ps1


2章 VB.NET との連携
 2.1 [Microsoft.VisualBasic.Interaction]
   appActive.ps1

3章 .NET Framework との連携
 3.1 キーコード送信
   sendKey.ps1
 3.2 メール送信
   sendMail.ps1

4章 SQLite3 との連携
 4.1 SQLite3 操作
   sqlite3_get_body.ps1
 4.2 SQLite3 操作(replace)
   sqlite3_replace_details.ps1
 4.3 SQLite3 クエリ結果を HTML に変換する
   sqlite3_html_ext.ps1

5章 XML との連携
 5.1 XML 操作 (読み書き)
   xml_rw.ps1

6章 Excel との連携
 6.1 Excel 操作
   excel1.ps1

7章 文字列の操作
 7.1 空白区切りで文字列を切り出す
   wc1.ps1
 7.2 文字列から指定した文字の位置をポイントする(C言語の strstr 相当)
   IndexOf1.ps1

8章 ファイルの操作
 8.1 一行ずつ読み出す
   read_oneline1.ps1
   read_oneline2.ps1

 
 

1.1

 
実行ポリシーを変更する

デフォルト状態では、PowerShell を実行すると次のように「Execution_Policies」といったログが出てしまい実行できない。

> .\a.ps1
このシステムではスクリプトの実行が無効になっているため、
ファイル C:\work\a.ps1 を読み込むことができません。詳細については、
「about_Execution_Policies」(http://go.microsoft.com/fwlink/?LinkID=135170) 
を参照してください。

 
そこで、次の対策を採る必要がある。
 
恒久的に PowerShell を実行できるようにする
次の A, B という2通りの方法がある。

A-1. コマンドプロンプトあるいはバッチファイルから以下を実行して PowerShell を管理者モードで起動させる
参考

@powershell -NoProfile -ExecutionPolicy unrestricted -Command "Start-Process powershell.exe -Verb runas"

 
A-2. PowerShell が実行できるように制限を変更する

Set-ExecutionPolicy RemoteSigned

 
B. 一時的に PowerShell を実行できるようにする

参考

> powershell -ExecutionPolicy RemoteSigned .\test.ps1

より詳しい設定については、こちらを参照。
 
 

1.2

 
日時をディレクトリ名に付与して、新規ディレクトリを作成する。
また、そのときのパス情報を result.txt に出力する。

 

mkdir_foler_with_date.ps1
$now     = (Get-Date -UFormat "%Y-%m-%d-%H-%M-%S")
$target  = "\\PC_WORK_1\share\$now"
$output  = "result.txt"

mkdir $target

if( Test-Path $output )
{
  Remove-Item $output
}

Add-Content -path $output -value $target -encoding String

 
 

1.3

 
result.txt に書かれたパスを取り出して、そのパスにデータをコピーする。

result.txt の記述例

c:\work\temp

 

copy_item.ps1
$output = (Get-Content result.txt)
Copy-Item -r "C:\User\neko\Download\data" $output

 
 

1.4

 
"c:\User\neko\Download\*.7z" を "c:\User\neko\work\." にコピーして展開するプログラム。
ここにエラーチェックを加えただけである。
なお、7z.exe は事前にインストールし, %PATH% を通しておくこと。
 

cp_rm_mkdir_7z_dirname.ps1
# 定数
set-variable -name REPOS -value "c:\User\neko\Download" -option constant
set-variable -name WORK  -value "c:\User\neko\work"     -option constant

# Linux の dirname コマンド相当の処理をする
$dirname = (Split-Path -Parent $WORK)
if( !(Test-Path $dirname) )
{
  exit
}

# $WORK が存在しなければ作成する
if( -not (Test-Path $WORK))
{
  New-Item -ItemType Directory $WORK 2>&1
  exit
}

# *.7z をコピーし、展開する
cd $WORK
Remove-Item *.7z
Copy-Item -Path $REPOS/*.7z -Destination .

7z x -y *.7z

 
 

1.5

 
anydir というディレクトリごと削除する
 

rmdir.ps1
Remove-Item -Recurse -Force .\anydir

 
 

1.6

 

args1.ps1
#!/usr/bin/env pwsh

# 引数に a を入力した場合
Write-Host "argc   : " $Args.Length     #=> 1
Write-Host "argv[0]: " $Args[0]         #=> a

 
 

2.1

 
nvim プロセスが存在しているか判定し、nvim プロセスが存在していれば nvim プロセスをアクティブ(=マウスでクリックして選択した状態)にする

参考にしたサイト

以下のように nvim が起動しており、SendKey クラスを使って nvim のツールバーを操作する。

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
      0     0.00      14.11       0.15    5410 033 nvim

 

appActive.ps1
add-type -AssemblyName microsoft.VisualBasic
add-type -AssemblyName System.Windows.Forms

# nvim プロセスが存在しているか判定する
$ps = Get-Process | Where-Object { $_.Name -eq "nvim"}
Write-Host $ps

if( $ps -eq $null )
{
  return $FALSE
}
else
{
  # nvim プロセスが存在していれば、nvim プロセスをアクティブ(=マウスでクリックして選択した状態) にする。
  [Microsoft.VisualBasic.Interaction]::AppActivate($ps.ID);
  Start-Sleep 5
  return $TRUE
}

 
 

3.1

 
nvim に対して、 Alt キーを送信して、右カーソルを2回送信して、Enter を 2回送信してメニュを選択する。
% が Alt を表す。

 

sendKey.ps1
add-type -AssemblyName microsoft.VisualBasic
add-type -AssemblyName System.Windows.Forms

# nvim プロセスが存在しているか判定する
$ps = Get-Process | Where-Object { $_.Name -eq "nvim"}
Write-Host $ps

if( $ps -eq $null )
{
  return $FALSE
}
else
{
  # nvim プロセスが存在していれば、nvim プロセスをアクティブ(=マウスでクリックして選択した状態) にする。
  [Microsoft.VisualBasic.Interaction]::AppActivate($ps.ID);
  Start-Sleep 5
}

# nvim に対して、 Alt キーを送信して、右カーソルを2回送信して、Enter を 2回送信してメニュを選択する。
# % が Alt を表す。
Start-Sleep -s 2
add-type -AssemblyName System.Windows.Forms

Start-Sleep -s 2
[System.Windows.Forms.SendKeys]::SendWait("%")

Start-Sleep -s 1
[System.Windows.Forms.SendKeys]::SendWait("{RIGHT}")

Start-Sleep -s 1
[System.Windows.Forms.SendKeys]::SendWait("{RIGHT}")

Start-Sleep -s 1
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")

Start-Sleep -s 1
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")

return $TRUE

 
 

3.2

 
メールを送信する。なお、メールの件名は subject.txt、本文は body.txt に書いている。
 

sendMail.ps1
$to       = "wanchango@example.com"
$subject  = Get-Content subject.txt
$body     = Get-Content body.txt -Raw
$smtpHost = "my.smtp.server.com"
$from     = "nekochango@example.com"

$email = New-Object System.Net.Mail.MailMessage

$email.To.Add($to)
$email.From       = $from
$email.Subject   = $subject
$email.Body       = $body

$client = New-Object System.Net.Mail.SmtpClient $smtpHost
$client.UseDefaultCredentials = $true
$client.Send($email)

 
 

4.1

 
SQLite3 のデータベースファイルからデータを取り出す。

  • データベースファイル名は output.db とする
  • テーブル名は OTBL とする
  • 「No」「Title」「Result」の情報が取り出せなかったら、エラーメッセージを表示して終了する
  • SQL コマンドは予め sql_cmd_summary.txt に定義しておく
  • 出力形式は csv とする
  • sqlite3.exe はこちらの手順で入手しておく

 

sql_cmd_body.txt
.mode csv
select No, Title, Result from OTBL;

 

sqlite3_get_body.ps1
$DB = 'output.db'
$line = '.read sql_cmd_body.txt' | .\sqlite3.exe $DB
$len  = $line.Lenght
if ($len -gt 1)
{
  Write-Output '[DEBUG] OK'
  Write-Output $line
}
else
{
  Write-Error "[DEBUG] NG"
  Write-Error "Could not get result from $DB"
  exit
}

 
 

4.2

 
SQLite3 のデータベースファイルの内容を更新(編集)する。

  • データベースファイル名は output.db とする
  • テーブル名は OTBL とする
  • 「Details」レコードの "http://www" を "https://www" に変更する。OTBL は上書き更新する。
  • SQL コマンドは予め sql_cmd_replace.txt に定義しておく
  • sqlite3.exe はこちらの手順で入手しておく


 

sql_cmd_replace.txt
update OTBL set Details = replace ( Details, 'http://www', 'https://www' ); 

 

sqlite3_replace_details.ps1
$DB = 'output.db'
'.read sql_cmd_replace.txt'  | .\sqlite3.exe $DB

 
 

4.3

 
【SQLite】SQLite3 データを HTML で出力する (Windows10 環境)【SW】 - 4f938672-cb1c-4c5a-8233-192c4ec901df のコードに以下の変更を追加したものである。

  • 入力ファイルの妥当性チェック
  • 出力ファイルは Result.html 固定にした

 

sqlite3_html_ext.ps1
function    main()
{
    param( [string] $DB )

    if( [string]::IsNullOrEmpty($DB) )
    {
      Write-Host "Error: `$DB was empty"
      exit 2
    }

    if( !(Test-Path $DB))
    {
      Write-Host "Error: could not found $DB"
      exit 3
    }
}

if ( $args[0] -eq $null )
{
  Write-Host 'Error: Could not found DB'
  exit
}
elseif( !(Test-Path $args[0]) )
{
  Write-Host "Error: Could not found $args[0]"
  exit
}

$DB = $args[0]

$output = 'Result.html'

if ( !(Test-Path $output) )
{
  New-Item -type file $output > $null
}

'<html><body>' | Out-File -Append $output

foreach ( $i in ( sqlite3 $DB ".tables" ).Split() )
{
    if( [string]::IsNullOrEmpty($i) )
    {
        continue
    }
    "<h2>$i</h2>"      | Out-File -Append $output
    "<table border=1>" | Out-File -Append $output
    "<tr>"             | Out-File -Append $output

    foreach ( $j in ( sqlite3 $DB "PRAGMA table_info($i);" ))
    {
        if( [string]::IsNullOrEmpty($j) )
        {
            continue
        }
        $j = $j.Split('|')[1]
        "<th>$j</th>" | Out-File -Append $output
    }
    "</tr>" | Out-File -Append $output

    sqlite3 -html $DB "select * from $i ;" | Out-File -Append $output
    "</table>" | Out-File -Append $output
}
"</body></html>" | Out-File -Append $output

 
 

5.1

 
以下の 1 〜 3 の処理をするプログラム。

 
実行形式

> .\xml_rw.ps1 sample.xml

 
処理内容

1. 引数に指定した sample.xml を読み出す。
2. 1番目の要素 (配列なので[0]) の TAG と REV タグの内容を「DOG」「44」で書き換える。
3. そして sample.xml.[日時] という形式でファイル出力する。

 
sample.xml

<?xml version="1.0" encoding="UTF-8"?>
<CATS>
  <Cat type="MIKE">
    <TARGET>mike-robo</TARGET>
    <REV>10</REV>
  </Cat>
  <Cat type="SHIRO">
    <TARGET>shiro-robo</TARGET>
    <REV>20</REV>
  </Cat>
  <Cat type="Hachiware">
    <TARGET>hachi-robo</TARGET>
    <REV>30</REV>
  </Cat>
</CATS>

 
sample.xml.20180904-002428

<?xml version="1.0" encoding="UTF-8"?>
<CATS>
  <Cat type="MIKE">
    <TARGET>DOG-robot</TARGET>
    <REV>44</REV>
  </Cat>
  <Cat type="SHIRO">
    <TARGET>shiro-robo</TARGET>
    <REV>20</REV>
  </Cat>
  <Cat type="Hachiware">
    <TARGET>hachi-robo</TARGET>
    <REV>30</REV>
  </Cat>
</CATS>

 

xml_rw.ps1
class   XmlOprClass
{
  $m_XML         = ''       # XML インスタンス
  $m_InputFile   = ''       # 入力ファイルである XML のパス
  $m_Idx         = -1       # Cats[i] のインデックス
  $m_Target      = ''       # タグ <TARGET>
  $m_Rev         = ''       # タグ <REV>

  # コンストラクタ
  XmlOprClass([string] $f)
  {
    $filename         = (Get-Item $f).FullName
    $this.m_XML       = [xml] (Get-Content $filename)
    $this.m_InputFile = $filename
  }

  [void] fn_load_one([int]$i)
  {
    if( $i -lt 3 ) 
    {
      $this.m_Idx     = $i
      $this.m_Target  = $this.m_XML.Cats.Cat[$i].TARGET
      $this.m_Rev     = $this.m_XML.Cats.Cat[$i].REV
    }
  }

  [void] fn_change([string]$target, [int]$rev)
  {
    $this.m_Target = $target
    $this.m_Rev    = $rev
  }

  [void] fn_update()
  {
    $now    = (Get-Date -UFormat ".%Y%m%d-%H%M%S")
    $i  	= $this.m_Idx
    if( ( $i -ge 0 ) -And ( $i -lt 3 ) )
    {
      $ofile = $this.m_InputFile + $now
      $this.m_XML.Cats.Cat[$i].TARGET   = $this.m_Target
      $this.m_XML.Cats.Cat[$i].REV      = $this.m_Rev
#     write-host "[DEBUG] $this.m_XML.Cats.Cat[$i].TARGET"
#     write-host "[DEBUG] $this.m_XML.Cats.Cat[$i].REV"
      $this.m_XML.save($ofile)
      Write-Host "[SAVE] $ofile"
    }
  }
}

function    main()
{
  param([string] $xmlFile)

  if( [string]::IsNullOrEmpty($xmlFile) )
  {
    Write-Host "Error: `$xmlFile was empty"
    exit 2
  }

  if( !(Test-Path $xmlFile))
  {
    Write-Host "Error: could not found $xmlFile"
    exit 3
  }

  $X = [XmlOprClass]::new( $xmlFile )
  
  $X.fn_load_one(0)             # XML の /Cats/Cat[0] をバッファに書き込む
  $X.fn_change("DOG-robot", 44) # バッファリングしている内容を引数のもので上書きする
  $X.fn_update()                # XML を更新する

  return 0
}


$retval = main $args[0]

if( $retval -eq 0 )
{
  Write-Host "Success"
}
else
{
  Write-Host "Failure"
}

 
 

6.1

 
以下 1, 2, 3 の操作をするプログラムである.
なお、実行していないので文法ミスや実行誤りがあるかも知れない.

1. sample.xlsx の sheet1 シートを開く。

2. B3セルに $n の値が記述されていれば、M3セルに "HIT" と入力する。
  あるいは B3セルが空っぽであれば、"MISS" と入力する。
   
3. B4 から B20 セルまで 2 を繰り返す

 

excel1.ps1
$B = "B3"                        # 始点となるセル
$E = "AZ20"                      # 終点となるセル
$Nums = @(1, 4, 5, 7, 13, 18)    # 探索する番号とする

$excel                = New-Object -ComObject Excel.Application
$excel.Visible        = $false
$excel.DisplayAlerts  = $false

$book     = $excel.Workbooks.Open("sample.xml")
$sheet    = $excel.Worksheets.Item("sheet1")

# 始点となる列・行を取得する
$beginX   = $sheet.Range($B).Column
$beginY   = $sheet.Range($B).Row

# 終点となる列・行を取得する
$endX     = $sheet.Range($E).Column
$endY     = $sheet.Range($E).Row

$M = 13  # M列

# B3 から B4, B5, B6 ... と B20 までを辿って $n と一致する項目であれば、
# M列に結果( "HIT" もしくは "MISS" )を入力する
for( $y = $beginY; $y -le $endY; $y++ )
{
  foreach( $n in $Nums )
  {
    $pos = $sheet.Cells.Item($y, $beginX).text  # 文字列で比較する. 整数値のみであるならば Int でも良い

    if( [string]::IsNullOrEmpty($pos) ) # 対象のセルが空の場合. 文字色を赤にする
    {
      $sheet.Cells.Item($y, $M)                     = "MISS"
      $sheet.Cells.Item($y, $M).font.ColorIndex     = 3
      break
    }
    elseif( $pos -like $n ) # セルの内容が一致した場合. 文字色は黒にする
    {
      $sheet.Cells.Item($y, $M)                     = "HIT"
      $sheet.Cells.Item($y, $M).font.ColorIndex     = 1
      break
    }
  }
}

$excel.ActiveWorkbook.Save()
$book.Close()

 
 

7.1

 
文字列を空白区切りで抽出する。wc, や awk コマンドのデフォルトの挙動である。
 

wc1.ps1
#!/usr/bin/env pwsh

# 空白区切りで抽出し、[] で括って表示する。
# なお、空白文字(文字長0) は表示せずスキップする。

if ( $Args -eq $null )
{
    Write-Error 'Error: Could not found $Args'
}

for ($i = 0; $i -lt $Args.Length; $i++ )
{
    Get-Content $Args[0] | Foreach-Object { # Foreach-Object と { は同一行に書くこと
        $matches = [Regex]::Split($_," ")
        $matchCount = $matches.Length
#       Write-Host "[DEBUG] " $_
#       Write-Host "Count=> $matchCount"
        for( $j = 0; $j -lt $matchCount; $j++ )
        {
            if( $matches[$j].Length -ne 0 )
            {
                $line = '[' + $matches[$j] + ']'
                Write-Host $line
            }
        }
    }
}

 
 

7.2

 
dirname 相当をする (末尾から辿って最初に見つかった / 以降を削除する)
 
例:

http://xxxx/yyyy/zzzz
↓
http://xxxx/yyyy

 

IndexOf1.ps1
#!/usr/bin/env pwsh

$url = 'https://msdn.microsoft.com/ja-jp/library/system.string.indexof(v=vs.110).aspx'

$url.IndexOf("/")   #=> 6

# dirname 相当をする (末尾から辿って最初に見つかった / 以降を削除する)
$url.Substring(0,$url.LastIndexOf("/") + 1)

 
 

7.3

 
regex によりパターンマッチにより文字列を抽出する。
sed 's#http://\(.*\)#\1#g' と同じことだが、取り出したパターンを使える。

http://msdn.microsoft.com/ja-jp/library/system.string.indexof(v=vs.110).aspx
↓
msdn.microsoft.com/ja-jp/library/system.string.indexof(v=vs.110).aspx

 

regex1.ps1
#!/usr/bin/env pwsh

# 使用するパターンマッチ
$regex   = "https://(.*)"

# 解析対象の文字列データ (ヒアドキュメント)
$content = '@
https://msdn.microsoft.com/ja-jp/library/system.string.indexof(v=vs.110).aspx
HTTPS://translate.google.co.jp/?hl=ja
https://yahoo.co.jp
@'

# 大小文字の区別なしでパターンマッチする
$resultingMatches = [Regex]::Matches($content, $regex, "IgnoreCase")

# パターンマッチ判定の結果を1つずつ取り出す
foreach($match in $resultingMatches)
{
    $match.Groups[1].Value
    #=> msdn.microsoft.com/ja-jp/library/system.string.indexof(v=vs.110).aspx
    #   translate.google.co.jp/?hl=ja
    #   yahoo.co.jp
}

 
 

8.1

 
read_oneline1.ps
引数で指定したファイルの内容を一行ずつ読み出す

read_oneline2.ps
引数で指定したファイルの内容を一行ずつ読み出し、空行はスキップする。
 

read_oneline1.ps1
#!/usr/bin/env pwsh

if ( $Args -eq $null )
{
    Write-Error 'Error: Could not found $Args'
}

for ($i = 0; $i -lt $Args.Length; $i++ )
{
    Get-Content $Args[0] | Foreach-Object { # Foreach-Object と { は同一行に書くこと
        $_
    }
}

 

read_oneline2.ps1
#!/usr/bin/env pwsh

if ( $Args -eq $null )
{
    Write-Error 'Error: Could not found $Args'
}

for ($i = 0; $i -lt $Args.Length; $i++ )
{
    Get-Content $Args[0] | Foreach-Object { # Foreach-Object と { は同一行に書くこと
        if( $_.Length -ne 0 )
        {
            $_
        }
    }
}