本項の解説は totobon の備忘録的要素が強いため、手取り足取りの解説はしていません。すでに C# や C++/CLI といった .net プログラミングの経験がある人に直観的に分かるような解説を目的としています。
また、以下、PowerShell を PS と表記します。
PS では全てのものが .net framework のオブジェクトとして扱われます。例えば、「echo」コマンドは「String」型を返すので、Length プロパティによって文字列の長さを得ることができますし、GetType() メソッドを使えば、オブジェクトに関する情報も得ることができます.。ただし、「cp」や「mv」など「null」を返すコマンドでは、このようなプロパティやメソッドを使うことができません。
PS D:\Works> (echo "hello, world.").Length 13 PS D:\Works> (echo "hello, world.").GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True String System.Object
このようなコマンドを「コマンドレット」と呼びますが、本項ではとくに断りがなければ「コマンド」と呼びます。
PS では大文字と小文字は区別されません。
1行に1文だけであれば、文の終わりを示す区切り記号はいりませんが、C# や C++/CLI などと同様、「;」で文を区切ることができます.。以降、1行に1文の場合でも「;」を付けます。
PS D:\Works> (echo "hello, world.").Length; (echo "hello, world.").GetType(); 13 IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True String System.Object
PS では、「#」から行末までがコメントとして扱われます。
PS では、より強力なスクリプト機能が使用できます。もっとも簡単なスクリプトは単にコマンドを羅列したもの.ですが、より高度な機能として、C# や C++/CLI に類似した制御文や、.net framework のクラスなどが使用できます.。
PS D:\Works> echo "hello," > "test1.txt" PS D:\Works> echo "world." > "test2.txt" PS D:\Works> (cat "test1.txt") + (cat "test2.txt") > "test3.txt" PS D:\Works> cat "test3.txt" hello,world. #3行目では cat が String 型オブジェクトを返すため、「+」による連結を行っています。
PS D:\Works> for([Int32] $i = 0; $i -lt 5; $i++){ echo $i.ToString().PadLeft(3, '0'); }
000
001
002
003
004
スクリプトのもっとも簡単な実行方法は、PS 上に直接入力してしまうやり方です。前述の for 文も、行を分けて入力・実行することができます。入力の終了は、空行で、空行が入力された時点でスクリプトが実行可能であれば実行します.。スクリプトが完全でなければ入力の継続を促すか、エラーを表示します。
PS D:\Works> for([Int32] $i = 0; $i -lt5; $i++){
>> echo $i.ToString().PadLeft(3, '0');
>> }
>>
000
001
002
003
004
もう一つの方法は、スクリプトをファイルに書いて、このファイルを実行する方法です。まずは、.のスクリプトファイルをサンプルとして作っておきます。
for([Int32] $i = 0; $i -lt5; $i++){
echo $i.ToString().PadLeft(3, '0');
}
[Console]::WriteLine("処理が終了しました。Enter を押してください。");
[Console]::ReadLine();
このファイルをスクリプトとして実行する、とっさに思いつく方法は拡張子を「.bat」や「.cmd」として実行する方法ですが、これらの拡張子は旧来のコマンドプロンプトに関連付けされているので、不適当です。
PS スクリプトにつけられる最も一般的な拡張子の一つは「.ps1」 (*1) ですので、これを PowerShell に関連付けましょう (*2) 。PowerShell の場所ですが、通常は「C:\Windows\system32\WindowsPowerShell」の中に、バージョンごとに分けられて入っています。関連づけが終わったら、先ほど作った test.txt の拡張子を .ps1 へ変更するのも忘れないでください。
また、スクリプトを実行する際に注意しないといけないのは、カレントディレクトリにスクリプトファイルがある場合でも、UNIX 系 OS のように「./」としてディレクトリ位置を明示してやらなければならない点です.。
PS D:\Works> ./test
しかし、このままではまだ動きません。実際、実行してみると警告メッセージ.が出て、すぐに終了してしまいます。
スクリプトの実行がシステムで無効になっているため、ファイル D:\Works\test.ps1 を読み込めません。詳細については、「get-help about_signing」と入力してヘルプを 参照してください。
そこで、「Set-ExecutionPolicy」コマンドを、引数「RemoteSigned」付きで実行.し、スクリプトの実行を有効にしておきます
(*3)
。このとき、Vista 以上の OS であれば、PS を管理者として実行
(*4)
します。
PS D:\Works> Set-ExecutionPolicy RemoteSigned
自分で作ったスクリプトは、コマンドのように使うことができます。スクリプトのあるディレクトリにパスを通しておけば、「./」など無しに、スクリプトファイルの名前だけで使用することができます。自分のスクリプト集をどこかのディレクトリに用意して、そこにパスを通しておくとよいでしょう。
パスはシステムのプロパティから環境変数を設定することで永続的に通すことができますが、必要ならば PS の実行ごとに参照.、設定.することもできます。
PS D:\Works> $env:Path
PS D:\Works> $env:Path += ";D:\Works\Scripts"
設定は、「$env:Path」がつまるところ文字列に過ぎないため、それに必要なパスを連結しています。追加するパスの頭に、パスの区切りである「;」を書き忘れないように気をつけましょう。
ここから先は、具体的なサンプルを提示しますので、直観でご理解ください。時間ができたら詳細な説明をつけていきます。
また、ここから先のサンプルコードは、特に断りがなければそのままコピー&ペーストで実行可能です。
.net framework の型やクラスを使用して変数を宣言できます。
[String] $str = "hello, world."; [Int32] $n = 123; echo $str $n;
名前空間は「System.」を省略することができます。
[System.Int32] $n1 = 123; [Int32] $n2 = 456; echo $1; echo $2;
オブジェクトを生成するときは「new-object」を使用します。C# の new や C++/CLI の gcnew に相当する機能です。
[String] $str1 = new-object String("hello,");
echo $str1;
echo "------";
[System.String] $str2 = new-object System.String("world.");
echo $str2;
echo "------";
[Int32] $num = new-object Int32;
echo $num;
[Double] $f = 123.456; echo $f; [Int32] $n1 = $f -as [Int32]; echo $n1; [Int32] $n2 = [Int32]$f; echo $n2;
[Int32] $n1 = 456; [Int32] $n2 = 123; echo ($n1 + $n2); echo ($n1 - $n2); echo ($n1 * $n2); echo ($n1 / $n2); echo ($n1 % $n2); echo "------"; echo $n1; $n1 += 123; echo $n1; echo "------"; echo $n1; $n1 -= 123; echo $n1; echo "------"; echo $n1; echo (++$n1); echo ($n1++); echo $n1; echo "------"; echo $n1; echo (--$n1); echo ($n1--); echo $n1; #除算は商ではなく浮動小数点数となる点に注意。
[String] $str = "hello, world."; [Int32] $n1 = $str.Length; [Int32] $n2 = $str.get_Length(); echo $str; echo $n; $str = $str.ToUpper(); echo $str; $str = "123"; $n = [Int32]::Parse($str); $n += 456; echo $n; #一部、プロパティの実装されていないクラスがあります。 #例えば XmlDocument の InnerText などです。 #そのような場合には get_PropertyName() や #set_PropertyName() を使用します。
[String] $str1 = [Console]::ReadLine(); [String] $str2 = [Console]::ReadLine(); echo "------"; echo $str1 $str2; echo "------"; [Console]::WriteLine($str1 + $str2);
[Console]::Write("hello,`t`#world.`r`n");
[Int32[]] $intArray1 = 1,2; [String[]] $strArray1 = "abc", "def", "ghi"; [Console]::WriteLine($intArray1[0].ToString() + "," + $intArray1[1].ToString()); [Console]::WriteLine($strArray1[0] + "," + $strArray1[1] + "," + $strArray1[2]); [Int32[]] $intArray2 = new-object Int32[](2); $intArray2[0] = 3; $intArray2[1] = 4; [String[]] $strArray2 = new-object String[](3); $strArray2[0] = "jkl"; $strArray2[1] = "mno"; $strArray2[2] = "pqr"; [Console]::WriteLine($intArray2[0].ToString() + "," + $intArray2[1].ToString()); [Console]::WriteLine($strArray2[0] + "," + $strArray2[1] + "," + $strArray2[2]);
変数が一つの値(オブジェクト)である場合は、長さ1の配列とみなしてキャストできますが、配列の場合は、それを一つの値(オブジェクト)と見なすことはできません。
[String] $str = "hello, world.";
[Console]::WriteLine("------ 戻り値 ------");
[Console]::WriteLine($str);
[String[]] $strArray = [String[]]$str;
[Console]::WriteLine("------ 戻り値のリスト ------");
foreach($s in $str){
[Console]::WriteLine($s);
}
[Console]::Write("数1: ");
$str1 = [Console]::ReadLine();
[Console]::Write("数2: ");
$str2 = [Console]::ReadLine();
$n1 = [Int32]::Parse($str1);
$n2 = [Int32]::Parse($str2);
if($n1 -lt $n2){ [Console]::WriteLine($n1.ToString() + "<" + $n2.ToString()); }
if($n1 -le $n2){ [Console]::WriteLine($n1.ToString() + "<=" + $n2.ToString()); }
if($n1 -ge $n2){ [Console]::WriteLine($n1.ToString() + ">=" + $n2.ToString()); }
if($n1 -gt $n2){ [Console]::WriteLine($n1.ToString() + ">" + $n2.ToString()); }
if($n1 -eq $n2){ [Console]::WriteLine($n1.ToString() + "==" + $n2.ToString()); }
if($n1 -ne $n2){ [Console]::WriteLine($n1.ToString() + "!=" + $n2.ToString()); }
[Console]::WriteLine("------");
if($n1 -lt $n2 -or $n1 -eq $n2){ [Console]::WriteLine($n1.ToString() + "<=" + $n2.ToString()); }
if($n1 -gt $n2 -or $n1 -eq $n2){ [Console]::WriteLine($n1.ToString() + ">=" + $n2.ToString()); }
[Console]::WriteLine("------");
if($n1 -le $n2 -and $n1 -ne $n2){ [Console]::WriteLine($n1.ToString() + "<" + $n2.ToString()); }
if($n1 -ge $n2 -and $n1 -ne $n2){ [Console]::WriteLine($n1.ToString() + ">" + $n2.ToString()); }
[Console]::WriteLine("------");
if($n1 -lt 0){
[Console]::WriteLine($n1.ToString() + "<0");
}elseif($n1 -gt 0){
[Console]::WriteLine($n1.ToString() + ">0");
}else{
[Console]::WriteLine($n1.ToString() + "==0");
}
[Console]::WriteLine("------");
if($true){ [Console]::WriteLine("true"); }else{ [Console]::WriteLine("false"); }
if(!$true){ [Console]::WriteLine("true"); }else{ [Console]::WriteLine("false"); }
if($false){ [Console]::WriteLine("true"); }else{ [Console]::WriteLine("false"); }
if(!$false){ [Console]::WriteLine("true"); }else{ [Console]::WriteLine("false"); }
[Console]::WriteLine("------");
[String] $str = "hello, world.";
if($str -is [String]){ [Console]::WriteLine("str==String"); }else{ [Console]::WriteLine("str!=String"); }
if($str -isnot [String]){ [Console]::WriteLine("str!=String"); }else{ [Console]::WriteLine("str==String"); }
[Console]::WriteLine("------");
[Int32] $num = 123;
if($num -is [String]){ [Console]::WriteLine("num==String"); }else{ [Console]::WriteLine("num!=String"); }
if($num -isnot [String]){ [Console]::WriteLine("num!=String"); }else{ [Console]::WriteLine("num==String"); }
[String] $month = [Console]::ReadLine();
$month = $month.ToLower();
switch($month){
"january" { [Console]::WriteLine("1月"); }
"february" { [Console]::WriteLine("2月"); }
"march" { [Console]::WriteLine("3月"); }
"april" { [Console]::WriteLine("4月"); }
"may" { [Console]::WriteLine("5月"); }
"june" { [Console]::WriteLine("6月"); }
"july" { [Console]::WriteLine("7月"); }
"august" { [Console]::WriteLine("8月"); }
"september" { [Console]::WriteLine("9月"); }
"october" { [Console]::WriteLine("10月"); }
"november" { [Console]::WriteLine("11月"); }
"december" { [Console]::WriteLine("12月"); }
default { [Console]::WriteLine("該当なし"); }
}
[Int32[]] $intArray = 1,1,2,3,5,8,13;
[Int32] $i = 0;
while($i -lt $intArray.Length){
[Console]::Write($intArray[$i].ToString() + " ");
$i++;
}
[Console]::Write("`r`n");
[Console]::WriteLine("------");
for([Int32] $i = 0; $i -lt $intArray.Length; $i++){
[Console]::Write($intArray[$i].ToString() + " ");
}
[Console]::Write("`r`n");
[Console]::WriteLine("------");
foreach($n in $intArray){
[Console]::Write($n.ToString() + " ");
}
[Console]::Write("`r`n");
スクリプト内でコマンドレットの結果をオブジェクトとして使うことができます。たとえば、「dir」は「Object」の配列を返し、それらオブジェクトは「DirectoryInfo」か「FileInfo」です。
[Object[]] $list = dir;
for([Int32] $i = 0; $i -lt $list.Length; $i++){
if($list[$i] -is [IO.DirectoryInfo]){
[IO.DirectoryInfo] $di = $list[$i];
[Console]::WriteLine($di.Name + " はディレクトリ。");
}else{
[IO.FileInfo] $fi = $list[$i];
[Console]::WriteLine($fi.Name + " はファイル。");
}
}
function func()
{
echo "hello, world."
echo "------"
echo "hello," echo " world."
echo "------"
echo "hello,"; echo " world.";
}
func
関数に引数を与えるときは
FunctionName arg1 arg2 ...
function func()
{
[Console]::WriteLine("------ func ------");
foreach($str in $args){
[Console]::WriteLine($str);
}
}
func abc def ghi jkl
引数を指定した変数で受け取りたい場合は、以下のようにします。
function func([String] $arg1, [String] $arg2)
{
[Console]::WriteLine("====== func ======");
[Console]::WriteLine("arg1=" + $arg1 + ", arg2=" + $arg2);
[Console]::WriteLine("---他の引数---");
foreach($str in $args){
[Console]::WriteLine($str);
}
}
func abc def ghi jkl;
func ghi -arg2 abc jkl -arg1 def;
func ghi --arg2 abc jkl --arg1 def;
function func([ref] $arg1, [ref] $arg2)
{
$arg1.Value = "hello,";
$arg2.Value = "world.";
}
[String] $str1 = "abc";
[String] $str2 = "def";
[Console]::WriteLine("str1=" + $str1 + ", str2=" + $str2);
func ([ref]$str1) ([ref]$str2);
[Console]::WriteLine("str1=" + $str1 + ", str2=" + $str2);
function func()
{
return "OK";
}
[Console]::WriteLine("====== 戻り値を受け取らない呼び出し ======");
func;
[Console]::WriteLine("====== 戻り値を捨てる呼び出し ======");
[void](func);
[Console]::WriteLine("====== 戻り値を受け取る呼び出し ======");
[String] $ret = func;
[Console]::WriteLine("ret=" + $ret);
関数内部で別のメソッドや関数、コマンドを呼んだときにその戻り値を受け取らないと、それらが全て配列に納められて返されます。
ただし、例えば [Console]::WriteLine による標準出力などは、これによってオブジェクト(文字列)が返されるわけではないので、別扱いです。
function subfunc()
{
return "456";
}
function func()
{
$n = 123;
$n.ToString();
subfunc;
echo "789";
[Console]::WriteLine("999");
return "000";
}
[String[]] $ret = func;
[Console]::WriteLine("------ 戻り値のリスト ------");
foreach($s in $ret){
[Console]::WriteLine($s);
}
関数内部でメソッドを呼んでも、その戻り値を受け取るか、[Void] で破棄すれば、それらは返りません。
また、関数内部で呼んだのが別の関数やコマンドならば、パイプで「Out-Null」に落とせば、それらも返りません。
function subfunc()
{
return "456";
}
function func()
{
$n = 123;
[Int32] $n0 = $n.ToString();
[Void] $n.ToString();
subfunc | Out-Null;
echo "789" | Out-Null;
return "000";
}
[String] $ret = func;
[Console]::WriteLine("------ 戻り値 ------");
[Console]::WriteLine($ret);
関数内部で呼んだ別の関数やコマンドが配列を返す場合に、その関数やコマンドを複数回よんだとしても、関数からの戻り値は配列の配列ではなく、ただの配列になります。
function subfunc()
{
echo "123";
echo "456";
}
function func()
{
subfunc;
subfunc;
}
[String[]] $ret = func;
[Console]::WriteLine("------ 戻り値のリスト ------");
foreach($s in $ret){
[Console]::WriteLine($s);
}
スクリプトファイルは、そのファイル全体で前述したような関数の性質を持ちます。直観的には、ファイル名が関数の名前に相当します。
このとき、スクリプトファイルで引数(パラメータ)を受け取るには、ファイルの最初で param を使います.。もちろん、可変長引数の性質もそのまま引き継いでいます。このスクリプトを「paramtest.ps1」という名前で保存した後、.のようなテストコードを実行してみると、スクリプトの挙動が関数と同じであることが確認できます。
param([String] $arg1, [String] $arg2)
function func([String] $arg1, [String] $arg2)
{
return $arg1 + $arg2;
}
[Console]::WriteLine("====== paramtest ======");
[Console]::WriteLine("arg1=" + $arg1 + ", arg2=" + $arg2);
[Console]::WriteLine("---他の引数---");
foreach($str in $args){
[Console]::WriteLine($str);
}
[Console]::WriteLine("====== func の結果 ======");
[Console]::WriteLine((func $arg1 $arg2));
./paramtest abc def ghi jkl; ./paramtest ghi -arg2 abc jkl -arg1 def; ./paramtest ghi --arg2 abc jkl --arg1 def;
標準で使用できない .net framework のクラスは、アセンブリを読み込むことで使用できます。
[Void][Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Drawing.Rectangle] $rect = new-object System.Drawing.Rectangle(0, 0, 10, 20);
echo $rect.Width;
echo $rect.Height;
例えば C++/CLI や C# ではブロック( { ~ } )を使うことで変数の有効範囲を.のように制御することができます。
この例では、(1)と(2)の内部で宣言した「n」は、その外側(main の直後)で宣言した「n」よりも優先され、かつ、宣言されたブロック内でのみ有効なのに対し、(3)は外側で宣言した「n」がそのまま使用されていることを示しています。
using namespace System;
int main()
{
int n = 1;
{//(1)
int n = 10;
n++;
Console::WriteLine(n);
}
{//(2)
int n = 20;
n++;
Console::WriteLine(n);
}
{//(3)
n++;
}
Console::WriteLine(n);
return 0;
}
これに対し PS では、上記例とは挙動の異なる2種類のブロックが存在し、一つ目はブロックの外側にまったく影響を及ぼさないケースで、二つ目はブロックの外側に、完全に影響を与えるケースです。
まず、一つ目の例を.に載せます。
この例では、ブロックの最初に「&」を置いており、これによってブロックの外側にまったく影響を及ぼさないブロックであることを示します。実行してみると分かりますが、(3)のブロックの中の「$n」は、ブロックの外側に影響を及ぼしません。本項では説明しませんでしたが、PS は JavaScript などと同様、変数の宣言を省略出来ます。従って、(3)内は「&」によってブロック外と独立しているため、この中の「$n」は外とは独立した(新しく宣言された)変数として扱われることになり、最初の(main 直後の)「$n」は最後まで「1」のままとなります。
[Int32] $n = 1;
&{#(1)
[Int32] $n = 10;
$n++;
[Console]::WriteLine($n);
}
&{#(2)
[Int32] $n = 20;
$n++;
[Console]::WriteLine($n);
}
&{#(3)
$n++;
}
次に二つ目の例を.に載せます。
この例では、ブロックの最初に「.」を置いており、これによってブロックの外側に完全に影響を与えるブロックであることを示します。このケースでは、(1)と(2)のブロック内で新たに変数「$n」を宣言しているにもかかわらず、まるでその宣言が無いかのように、ブロック外(main 直後)の変数「$n」が操作されます。このため、最後の出力は(2)内で「20」が代入されてインクリメントされ(「$n」は「21」)、さらに(3)でインクリメントされて「22」となります。
[Int32] $n = 1;
.{#(1)
[Int32] $n = 10;
$n++;
[Console]::WriteLine($n);
}
.{#(2)
[Int32] $n = 20;
$n++;
[Console]::WriteLine($n);
}
.{#(3)
$n++;
}
[Console]::WriteLine($n);