宇宙仮面の
C# Programming

 
  • twitter
  • Blog
  • Twitter
  • Facebook
  • uchukamen
  • C# on Windows 8
  • チュートリアル
  • 開発環境
  • WPF and ASP.NET
  • Windows Forms
  • SQL Server

PowerShell

開発環境: PowerShell V1.0

PowerShell とは

PowerShell とは、システム管理に役立つように設計されたスクリプト言語です。Windows Server 2008からは、標準で搭載されるようです。基本的には、.Net Framework をベースとしたスクリプト言語であり、Unix のShell のようなコマンドラインベースでのスクリプトが書ける。また、パイプで出力をつなぐこともできる。しかし、Unix のShell はテキストベースで扱われるのに対して、PowerShell オブジェクトでの受け渡しが可能となっている。このため、かなり柔軟なデータを扱うことができる様になっている。

個人的な感想としては、確かに Object をパイプで渡せて、.NET Framework も使える、Com も使える、スクリプトとしても実行できる、C# に似ているとなると、結構いい感じに聞こえますが、現在の欠点として、無償の開発環境がない(有料のものはある)ので、手間がかかる。Visual Studio の インテリセンスに慣れてしまった身としては、とても辛いです。また、文法も似ているようで、違うので、とまどいます。たとえば、function Add (int x, int y) { x + y} に対して、 Add 1 2 で、3 が帰るが、Add(1, 2) では 1 2 が帰る。エラーならエラーとして欲しい。また、オブジェクトに $x.abc みたいな存在しない名前を指定してもエラーにならない。どうしてそうなっているのかよくわからないけれど、バグの原因になるのでやめて欲しい。それから、elseif も else if だとエラーになる。文字列のエスケープも C# とちょっとお作法が違う。また、Excel.Application との連動に関しても、Openが使えないという問題があります。ということで、ちょっとしたものを作ろうと思っても、初めてPowerShell を触る人は、かえって慣れている言語で開発した方が速かったりするかもしれません。業務で使うなら、もうすこし、こなれるのを待った方がいいというのが正直な感想です。

インストール

PowerShell は、Microsoft よりダウンロードが可能であり、現在りりーすされているバージョンは、V1.0です。

V2.0 CTP もダウンロード可能です。V2.0 CTPでは、簡単なエディタがついているので、そちらを使った方が楽かもしれません。ただし、ヘルプは英語です。また、そのままではヘルプが表示されないので、ヘルプファイルのディレクトリ名をja-JPに変更する必要があります。
このあたりの注意は、Shigeya Tanabe's blog あたりを参照するとよいでしょう。

変数

変数は、$x = "abc" という形です。型宣言はありませんが、型は自動的に判別され、型付きのオブジェクトとして扱われます。

文字列のエスケープ 

 ダブルクオート内のダブルクオート
$t="!""#$%&'()=-+*~^\"           "を2つ重ねる。

シングルクオート内のシングルクオート
$t='!"#$%&''()=-+*~^\'            'を2つ重ねる。 ''

文字のエスケープ

改行は `n   逆アポストロフィ
`0  `a   `b  `f  `n  `r  `t  `v

シングルクオートと、ダブルクオートの違い

ダブルクオート内の変数は評価される。
$a="test"
"$a"
test

シングルクオート内の変数は評価されない。

$a="test"
'$a'
$a 

変数の削除

$matches=$null 

というように、$null を代入するか、次のように Remove-Variable コマンドレットにより削除します。

Remove-Variable x           

注意  Remove-Variable $x とすると、"Remove-Variable : 名前 'x' の変数が見つかりません。" というエラーになる。
$x ではなく、x。 

変数のスコープ

$global:x="abc"           #すべてのスコープからアクセスできる
$script:x="abc"            #スクリプトファイル内のすべてのスコープからアクセスできる
$local:x="abc"             # 現在のスコープと、その子の内部からのみアクセスできる
$private:x="abc"          # 現在のスコープからのみアクセスできる

指定しない場合は、private

制御構造

foreach文

Foreach-Objectを使う場合

$a="123","234","324","456","567"
foreach-object -InputObject $a -Process {if ($_ -eq "324") {write-output "Equal" $_} }
$a | foreach-object -Process {if ($_ -eq "324") {write-output "Equal" $_} }

foreach を使う場合

$a="123","234","324","456","567"
foreach ($line in $a) {...}
ともかける。

ファイルを1行ごとに処理

$filename="./test.txt"
$regex =[regex] "test string"

foreach ($line in $(Get-Content $filename))
{
    $matches = $regex.Matches($line);
    if($matches.Count -gt 0)
    {
        write-output "====================================="
        write-output $line
        write-output "------------------------------------------------------------------"
        $matches[0].Value
    }
}

if 文

注意  else if ではなく、 elseif

else と if の間のスペースがあると
"'else' キーワードの後にステートメント ブロックが存在しません。"というエラーになる。
if ()
{
}
elseif ()
{
}
else
{

演算子

Where-Object

演算子 説明
-eq 等しい
-ne 等しくない
-gt より大きい
-ge 以上
-lt より小さい
-le 以下
-like ワイルドカードによる比較
-notlike ワイルドカードによる比較
-contains 含む  "abc", "cde", 123 -contains "cde"
-notcontains 含まない
-match 正規表現による比較
-nomatch 正規表現による比較
-and 論理積
-or 論理和
-not 論理否定
! 論理否定

大文字と小文字は区別しない。大文字と小文字を区別する場合は、演算子名の前に文字cを付けて、たとえば -ceq のようにします。

-like演算子

ワイルドカードによる曖昧検索を行います。曖昧検索にヒットした場合はTrueを返します。

-match,  -notmatch 演算子

正規表現による検索を行います。マッチした場合はTrueを返します。ただし、あとで述べる様にバグがあるようなので注意。

関数(function)

function Add($x, $y)
{
return ($x+$y)
}

Add 1 2
3

Add(1,2)  はダメ!!
1
2
1と2が返されてしまう。紛らわしい!コーディング時のエラーのもとになる。

関数のスコープ

add.ps1というファイル
function global:GlobalAdd($x, $y)
{
    return $x+$y
}

function script:ScriptAdd($x, $y)
{
    return $x+$y
}

./add.ps1

GlobalAdd 1 2
3
ScriptAdd 1 2
スクリプトのスコープで定義された関数なので、スクリプトの外では存在しない。"用語 'ScriptAdd' は、コマンドレット、関数、操作可能なプログラム、またはスクリプト ファイルとして認識されません。用語を確認し、再試行してください。"というエラーになる。

スクリプトファイル

'$args[0] : ' + $args[0]
'$args[1] : ' + $args[1]
'$args[2] : ' + $args[2]
'$argn : ' + $args.Count

ここで、"$args[0] : " + $args[0] とダブルクオートにすると、$argsが評価されてしまうので、注意。

スクリプトファイルの拡張子

拡張子は、「ps1」。例: test.ps1

システム情報の取得

プロセス情報

Get-Process | Get-Member
Get-Process | Get-Member -MemberType Properties
Get-Process -Name powershell | Format-List
Get-Process | Out-Host -Paging | Format-List

WMI

Get-WmiObject -Class Win32_LogicalDisk

Get-WmiObject -Class Win32_LogicalDisk | Select-Object -Property Name,FreeSpace| Get-Member

イベントログ

New-Object -TypeName System.Diagnostics.EventLog

サービス情報

Get-Service
Get-Service | get-member

良く使うコマンドレット

ソート

ls | sort -Descending
ls | sort-object -Descending

ファインド

ls | Where-Object -FilterScript {$_.Name -like "test*"}
これよりは、
ls | FindStr /I "test*"
のほうが楽。

エイリアス

Get-Command -CommandType Alias

スリープ

Sleep -m 500          # 500ミリ秒スリープ
Sleep -s 5               # 5秒スリープ

Sleep は、Start-Sleep コマンドレットのエイリアス

Com Object の利用

Word を使う

ワードのページ数を取得する方法

http://www.microsoft.com/japan/technet/scriptcenter/resources/qanda/sept06/hey0907.mspx

$w=new-object -com word.application
$w.visible = $true
$objDoc = $w.documents.open("
.\test.doc")
$objDoc.ComputeStatistics(2)
$objDoc.Close()
$w.Quit() 

Excel を使う

$e=new-object -com excel.application
$e.visible = $true
$oBook = $e.WorkBooks.OpenXML(".\test.xls")
$oBook.Close($false, , )
$e.Quit()
Remove-Variable oBookRemove-Variable e

# $oBook = $e.WorkBooks.Open("test.xls")
# だと、
# Exception calling "Add" with "0" argument(s): "ライブラリの形式が古いか、または種類が無効です。 (HRESULT からの例外: 0x80028018 (TYPE_E_INVDATAREAD)) "
#
というエラーになってしまう。
この原因は、ExcelCultureを正しくハンドリングできないため。
http://support.microsoft.com/kb/320369/ja

PowerPoint を使う

$p=new-object -com powerpoint.application
$p.visible = 1   # $true
ではなく、1
$presSet = $p.Presentations
$pptPres = $p.Presentations.Open("
.\test.ppt")$pptPres.Slides.Count   # ページ数
$pptPres.Close()
Remove-Variable pptPres
$p.Quit()
Remove-Variable p

インターネットエクスプローラーを使う

$ie = New-Object -ComObject InternetExplorer.Application
$ie.Visible = $true
$ie.Navigate("http://www.microsoft.com/technet/scriptcenter/default.mspx")
# Navigateでは、非同期に処理が行われるので、待つ必要がある。
while($ie.Busy)
{
    Sleep -m 500
}
$str = $ie.Document.Body.InnerHTML  # ロードしたHTML
# $str にすべてのHTMLがStringで入ってしまう。
# $str=$ie.Document.Body.InnerHTML.Replace("<br>", "`n") 
# $strArray=$str.split("`n")  # というように、配列ばらすと、
# foreach ($line in $strArray) { ... 処理 ... }  #  というように処理がしやすくなる。

$ie.Document.Body.InnerText  # ロードしたText

終了処理
$ie.Document = $null 
$ie.Quit
Remove-Variable ie

注意:  連続してNavigateしていく場合、$ie.Document = $null  をしないと、際限なくメモリを食っていく。
$ie.Document = $null をしておくと、一応物理メモリの上限程度で GCが働く模様。

ただし、Remove-Variable を行っても、ie のプロセスは生き残る。
また、Navigateで消費されたメモリも解放されない。IEで連続してページをロードしていくと、どんどんメモリを消費してしまう。したがって、$ie.Visible = $false の場合は、IEのプロセスに注意する必要がある。

.NET Objectの利用

$o = New-Object -TypeName System.Diagnostics.EventLog -ArgumentList Application

静的なクラスの参照

静的なクラスを参照するには、[]で囲む。
例  [System.Threading.Thread]

静的なクラスの利用

[System.Threading.Thread]::Sleep(10000)   
静的なメソッド、プロパティであることは :: を指定する必要がある。

静的な列挙型

[System.Windows.Forms.WebBrowserReadyState]::complete
ただし、これを標準の状態で実行すると、
New-Object : 型 [System.Windows.Forms.WebBrowser] が見つかりません。この型を含むアセンブリが読み込まれていることを確認してください。
発生場所 行:1 文字:11
+ New-Object <<<< -TypeName System.Windows.Forms.WebBrowser
というエラーになってしまう。

なぜかというと、
[System.AppDomain]::CurrentDomain.GetAssemblies()
で確認すると、System.Windows.Formsのアセンブリーが標準の状態でロードされていない。

そこで、
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
により、System.Windows.Forms アセンブリーをロードする。
静的なクラスを調べる

[System.Environment] | Get-Member -Static

ロードされているアセンブリーを調べる

[System.AppDomain]::CurrentDomain.GetAssemblies()

アセンブリーのロード

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

正規表現

-match は正確な正規表現でのマッチができない。

次の2つの処理で、を処理した場合。

$test=$test -match "a(b|c)"
$test="ab, ac, bc"
$matches

Name Value
---- -----
1 b
0 ab

結果は、ab とb

一方、.NET Framework の 正規表現を使用すると・・・

$regex =[regex] "a(b|c)"
$test="ab, ac, bc"
$matches = $regex.matches($test)
$matches

結果は、ab と ac

Groups : {ab, b}
Success : True
Captures : {ab}
Index : 0
Length : 2
Value : ab

Groups : {ac, c}
Success : True
Captures : {ac}
Index : 4
Length : 2
Value : ac

どうも、-match は、Groupの値を間違って返すバグか?

正規表現の例

$regex =[regex] "&(?!amp;)"    # &amp; 以外の&にマッチ

正規表現内の特殊文字のエスケープ

正規表現内の特殊文字      .$ ^ { [ ( | ) * + ? \

これらは、バックスラッシュでエスケープする。
[a-z]のような範囲指定の中で使うには、-もエスケープする。
[a-z\-]