Powershellで.NETを扱う
C#の記事を参考にしてPowershellで.Netを扱いたい場合の覚え書きです。
環境
PS/workspaces>$PSVersionTable.PSVersionMajorMinorPatchPreReleaseLabelBuildLabel----------------------------------------702
実行エンジン… .NET Core 3.1.5
参考:v7.0.2 Release of Powershell
入門
その前に:インテリセンス(補完)機能
Powershellのコマンド名はとにかく長い。そして、.NET
のクラスの名前空間なども長い。
ただし、インテリセンス(補完)機能は割と強力。
名前空間が分からないクラスを探すときなども便利。
もちろんコマンドレットでもインテリセンスを使える。
Windowsの場合はCtrl+Spacebar
で最初から使用可能。
Linuxの場合はデフォルトでは設定されてないので$profile
等に設定コマンドを記述して使えるようにする。
# 基本的にコマンドが割り当てられてないキーなら何処でも良い。Set-PSReadLineKeyHandler-ChordAlt+q-FunctionMenuComplete
※この記事はPowershellの補完機能を前提にしています。
Get-Member
数値
PS/workspaces>17|gm# Get-Memberのエイリアス
実行結果
TypeName:System.Int32NameMemberTypeDefinition------------------------CompareToMethodintCompareTo(System.Objectvalue),intCompareTo(intvalue),intIComparable.CompareTo(System.Objectobj),intIComparable[int].CompareTo(intother)EqualsMethodboolEquals(System.Objectobj),boolEquals(intobj),boolIEquatable[int].Equals(intother)GetHashCodeMethodintGetHashCode()GetTypeMethodtypeGetType()GetTypeCodeMethodSystem.TypeCodeGetTypeCode(),System.TypeCodeIConvertible.GetTypeCode()ToBooleanMethodboolIConvertible.ToBoolean(System.IFormatProviderprovider)ToByteMethodbyteIConvertible.ToByte(System.IFormatProviderprovider)ToCharMethodcharIConvertible.ToChar(System.IFormatProviderprovider)ToDateTimeMethoddatetimeIConvertible.ToDateTime(System.IFormatProviderprovider)ToDecimalMethoddecimalIConvertible.ToDecimal(System.IFormatProviderprovider)ToDoubleMethoddoubleIConvertible.ToDouble(System.IFormatProviderprovider)ToInt16MethodshortIConvertible.ToInt16(System.IFormatProviderprovider)ToInt32MethodintIConvertible.ToInt32(System.IFormatProviderprovider)ToInt64MethodlongIConvertible.ToInt64(System.IFormatProviderprovider)ToSByteMethodsbyteIConvertible.ToSByte(System.IFormatProviderprovider)ToSingleMethodfloatIConvertible.ToSingle(System.IFormatProviderprovider)ToStringMethodstringToString(),stringToString(stringformat),stringToString(System.IFormatProviderprovider),stringToString(stringformat,System.IFormatProviderprovider),stri…ToTypeMethodSystem.ObjectIConvertible.ToType(typeconversionType,System.IFormatProviderprovider)ToUInt16MethodushortIConvertible.ToUInt16(System.IFormatProviderprovider)ToUInt32MethoduintIConvertible.ToUInt32(System.IFormatProviderprovider)ToUInt64MethodulongIConvertible.ToUInt64(System.IFormatProviderprovider)TryFormatMethodboolTryFormat(System.Span[char]destination,[ref]intcharsWritten,System.ReadOnlySpan[char]format,System.IFormatProviderprovider)
文字列
PS/workspaces>'Foo'|gm
実行結果
TypeName:System.Int32NameMemberTypeDefinition------------------------CompareToMethodintCompareTo(System.Objectvalue),intCompareTo(intvalue),intIComparable.CompareTo(System.Objectobj),intIComparable[int].CompareTo(intother)EqualsMethodboolEquals(System.Objectobj),boolEquals(intobj),boolIEquatable[int].Equals(intother)GetHashCodeMethodintGetHashCode()GetTypeMethodtypeGetType()GetTypeCodeMethodSystem.TypeCodeGetTypeCode(),System.TypeCodeIConvertible.GetTypeCode()ToBooleanMethodboolIConvertible.ToBoolean(System.IFormatProviderprovider)ToByteMethodbyteIConvertible.ToByte(System.IFormatProviderprovider)ToCharMethodcharIConvertible.ToChar(System.IFormatProviderprovider)ToDateTimeMethoddatetimeIConvertible.ToDateTime(System.IFormatProviderprovider)ToDecimalMethoddecimalIConvertible.ToDecimal(System.IFormatProviderprovider)ToDoubleMethoddoubleIConvertible.ToDouble(System.IFormatProviderprovider)ToInt16MethodshortIConvertible.ToInt16(System.IFormatProviderprovider)ToInt32MethodintIConvertible.ToInt32(System.IFormatProviderprovider)ToInt64MethodlongIConvertible.ToInt64(System.IFormatProviderprovider)ToSByteMethodsbyteIConvertible.ToSByte(System.IFormatProviderprovider)ToSingleMethodfloatIConvertible.ToSingle(System.IFormatProviderprovider)ToStringMethodstringToString(),stringToString(stringformat),stringToString(System.IFormatProviderprovider),stringToString(stringformat,System.IFormatProviderprovider),stri…ToTypeMethodSystem.ObjectIConvertible.ToType(typeconversionType,System.IFormatProviderprovider)ToUInt16MethodushortIConvertible.ToUInt16(System.IFormatProviderprovider)ToUInt32MethoduintIConvertible.ToUInt32(System.IFormatProviderprovider)ToUInt64MethodulongIConvertible.ToUInt64(System.IFormatProviderprovider)TryFormatMethodboolTryFormat(System.Span[char]destination,[ref]intcharsWritten,System.ReadOnlySpan[char]format,System.IFormatProviderprovider)
メソッド実行
PS/workspaces>'Foo'.ToUpper()FOO
キャスト
幾つか方法がある。
PS/workspaces>'Foo'-as[char[]]FooPS/workspaces>[char[]]'Foo'Foo
多段キャスト
この場合はas
演算子を使った方がわかりやすい気がする。
PS/workspaces>[int[]][char[]]'Foo'70111111PS/workspaces>'Foo'-as[char[]]-as[int[]]70111111
組み合わせる
※Foreach()
はコレクションで使用可能なPowershell固有の特殊メソッド。
他にはClear()
やWhere()
がある。
Powershell7では差が縮まったがForeach-Object
よりもパフォーマンスに優れる。
配列について知りたかったことのすべて
Methods of arrays
PS/workspaces>'Foo'-as[char[]]-as[Byte[]]|ForEach-Object{$_+10-as[char]}|Join-StringPyyPS/workspaces>('Foo'-as[char[]]-as[Byte[]]).ForEach{$_+10-as[char]}-join''Pyy
オブジェクトを生成する方法
Powershellでオブジェクトを扱う方法は幾つもある。
公式の解説はこちら。
About Object Creation
この記事で扱うのは以下の通り。
- 静的メソッド
new()
でクラスのコンストラクタを実行する - 連想配列からキャストする
New-Object
を使う
この記事では詳しく扱わないがSystem.Activator
のCreateInstance()
を使う方法もある。
PS>$list=[System.Activator]::CreateInstance([System.Collections.Generic.List[int]])PS>$list.Count0PS>$list.AddRange([int[]]@(1..10))PS>$list[4..7]5678PS>$list=[System.Activator]::CreateInstance([System.Collections.Generic.List[int]],[int[]]@(1..5))PS>$list12345
静的メソッドnew()
でコンストラクタを呼び出す
Powershell5以降、クラスのコンストラクタは静的メソッドnew()
で呼び出せる。
C#のnew
に相当する。コンストラクタ有無はGetConstructors().Count
で確認可能。
PS>[string].GetConstructors().Count9PS>[string]::newOverloadDefinitions-------------------stringnew(char[]value)stringnew(char[]value,intstartIndex,intlength)stringnew(System.Char*,System.Private.CoreLib,Version=4.0.0.0,Culture=neutral,PublicKeyToken=7cec85d7bea7798evalue)stringnew(System.Char*,System.Private.CoreLib,Version=4.0.0.0,Culture=neutral,PublicKeyToken=7cec85d7bea7798evalue,intstartIndex,intlength)stringnew(System.SByte*,System.Private.CoreLib,Version=4.0.0.0,Culture=neutral,PublicKeyToken=7cec85d7bea7798evalue)stringnew(System.SByte*,System.Private.CoreLib,Version=4.0.0.0,Culture=neutral,PublicKeyToken=7cec85d7bea7798evalue,intstartIndex,intlength)stringnew(System.SByte*,System.Private.CoreLib,Version=4.0.0.0,Culture=neutral,PublicKeyToken=7cec85d7bea7798evalue,intstartIndex,intlength,System.Text.Encodingenc)stringnew(charc,intcount)stringnew(System.ReadOnlySpan[char]value)
そのため、new()
のPSMethod
のName
プロパティは.NET IL (中間言語)のコンストラクタメソッドである.ctor
となっている。
PS>[string]::new|Get-MemberTypeName:System.Management.Automation.PSMethodNameMemberTypeDefinition------------------------CopyMethodSystem.Management.Automation.PSMemberInfoCopy()EqualsMethodboolEquals(System.Objectobj)GetHashCodeMethodintGetHashCode()GetTypeMethodtypeGetType()InvokeMethodSystem.ObjectInvoke(ParamsSystem.Object[]arguments)ToStringMethodstringToString()IsInstancePropertyboolIsInstance{get;}MemberTypePropertySystem.Management.Automation.PSMemberTypesMemberType{get;}NamePropertystringName{get;}OverloadDefinitionsPropertySystem.Collections.ObjectModel.Collection[string]OverloadDefinitions{get;}TypeNameOfValuePropertystringTypeNameOfValue{get;}ValuePropertySystem.ObjectValue{get;set;}PS>[string]::new.name.ctor
※通常、メソッド名とName
プロパティは一致している。
PS>[string]::Compare.nameComparePS>[string]::Concat.nameConcat
暗黙の型変換があるのでメソッドの引数はある程度柔軟に記述出来る。
PS>'Bar'.GetType()IsPublicIsSerialNameBaseType----------------------------TrueTrueStringSystem.ObjectPS>[char[]]'Bar'BarPS>[string]::new([char[]]'Bar')BarPS>[string]::new('Bar')Bar
連想配列からキャストする方法
引数なしコンストラクタがある場合、連想配列からインスタンスを生成出来る。
この方法を使うとそのクラスのプロパティを補完入力できる。
プロパティを複数設定する場合は改行するか;
で区切る。
改行だけでも動作はするが、補完入力は;
で区切る時のみ有効になる。
New-Object
を使う方法
次のような場面で使用する。
- Comobjectを扱う場合...面倒な部分をラップしてくれる。
- 引数ありコンストラクタとプロパティの設定を同時に行う場合
# @を書き忘れるとエラーになるPS❯$WshShell=New-Object-ComObjectWScript.Shell-Property{CurrentDirectory="D:\"}New-Object:Cannotbindparameter'Property'.Cannotconvertthe"CurrentDirectory="D:\""valueoftype"System.Management.Automation.ScriptBlock"totype"System.Collections.IDictionary".⨯PS❯$WshShell=New-Object-ComObjectWScript.Shell-Property@{CurrentDirectory="D:\"}PS❯$WshShell.CurrentDirectoryD:\
usingについて
公式ドキュメント:about_Using - PowerShell | Microsoft Docs
Powershellのusingは3つの使い方がある。
usingnamespace<.NET-namespace># 指定したnamecpaceを省略出来るようになるusingmodule<module-name># Powershellモジュールで定義されたクラスを利用出来るようにするusingassembly<.NET-assembly-path># 指定したアセンブリのクラスを継承したクラスを作成するために使う
using assembly
はclass
構文で使われる。
Genericクラスの書き方
PS>usingnamespaceSystem.Collections.GenericPS>$list=[List[int]]::new()PS>$list.add(12)PS>$list12PS>$dic=[Dictionary[string,System.Diagnostics.Process]]::new()PS>$dic.Count0PS>Get-Process|ForEach-Object{$dic.TryAdd($_.ProcessName,$_)>$null}PS>$dic.Count140
Genericメソッドの書き方
PowershellではサポートしていないのでMethodInfo.MakeGenericMethod(Type[])
を利用する。
参考
MethodInfo.MakeGenericMethod(Type[]) メソッド (System.Reflection) | Microsoft Docs
vors/GenericMethods.ps1
PS>$OfTypeInt=[System.Linq.Enumerable].GetMethods().where{$_.IsGenericMethod-and$_.name-eq'Oftype'}.MakeGenericMethod([int])PS>$OfTypeInt.Invoke($null,(,@(1,2,'a')))12PS>$OfTypeInt.Invoke($null,@(1,2,'a'))MethodInvocationException:Exceptioncalling"Invoke"with"2"argument(s):"Parameter count mismatch."
実践~MSDNの記事を利用する~
お題はForm
の記事のサンプルコード
Form クラス (System.Windows.Forms) | Microsoft Docs
その1:そのままPowershellに置き換える
new()
とusing namespace
でほぼ公式通りに記述することが可能。
usingnamespaceSystem.DrawingusingnamespaceSystem.Windows.FormsAdd-Type-AssemblyNameSystem.Windows.Forms# コンストラクタ# Form作成$form1=[Form]::new()# ボタン作成$button1=[Button]::new()$button2=[Button]::new()# ボタンの設定# サンプルコードとの違い…DialogResultは明示的に設定$button1.Text="OK"$button1.DialogResult=[DialogResult]::OK$button1.Location=[Point]::new(10,10)$button2.Text="Cancel"$button2.DialogResult=[DialogResult]::Cancel$button2.Location=[Point]::new($button1.Left,$button1.Height+$button1.Top+10)# Formの設定$form1.Text="My Dialog Box"$form1.HelpButton=$true$form1.FormBorderStyle=[FormBorderStyle]::FixedDialog$form1.MaximizeBox=$false$form1.MinimizeBox=$false# 生成したボタンを設定$form1.AcceptButton=$button1$form1.CancelButton=$button2# 表示位置$form1.StartPosition=[FormStartPosition]::CenterScreen# 合体$form1.Controls.Add($button1)$form1.Controls.Add($button2)# 表示$form1.ShowDialog()
その2:Powershellで書きやすいように書く
連想配列を使ったオブジェクト生成を利用するとプロパティ設定をまとめやすい。AddRange()
が使える場合は書き換えを検討する。
usingnamespaceSystem.DrawingusingnamespaceSystem.Windows.FormsAdd-Type-AssemblyNameSystem.Windows.Forms$button1=[Button]@{Text="OK";DialogResult=[DialogResult]::OK;Location=[Point]::new(10,20);}$button2=[Button]@{Text="Cancel";DialogResult=[DialogResult]::Cancel;Location=[Point]@{X=$button1.Left;Y=$button1.Height+$button1.Top+10;};}$form1=[Form]@{Text="My Dialog Box";HelpButton=$true;FormBorderStyle=[FormBorderStyle]::FixedDialog;MaximizeBox=$false;MinimizeBox=$false;StartPosition=[FormStartPosition]::CenterScreen;AcceptButton=$button1;CancelButton=$button2;}$form1.Controls.AddRange(@($button1,$button2))$form1.ShowDialog()
インテリセンスが効かなくなるが;
が無くても動作する。
usingnamespaceSystem.DrawingusingnamespaceSystem.Windows.FormsAdd-Type-AssemblyNameSystem.Windows.Forms$button1=[Button]@{Text="OK"DialogResult=[DialogResult]::OKLocation=[Point]::new(10,20)}$button2=[Button]@{Text="Cancel"DialogResult=[DialogResult]::CancelLocation=[Point]@{X=$button1.LeftY=$button1.Height+$button1.Top+10}}$form1=[Form]@{Text="My Dialog Box"HelpButton=$trueFormBorderStyle=[FormBorderStyle]::FixedDialogMaximizeBox=$falseMinimizeBox=$falseStartPosition=[FormStartPosition]::CenterScreenAcceptButton=$button1CancelButton=$button2}$form1.Controls.AddRange(@($button1,$button2))$form1.ShowDialog()
Powershellだと難しいこと
次のような場合は再現は難しくなったり面倒になったりする。
IL、リフレクションを使えば大体のことは出来るが手間がかかる。
- ラムダ式…Powershellにはラムダ式はないため。なお、Linqは静的メソッドで利用可能。
- 自動生成コードが仕事をしている場合。WPFなどが該当。裏で自動生成されている箇所も手動で処理する必要がある。そういう部分をラップするのもPowershellモジュールの役目。
参考
High Performance PowerShell with LINQ
PowershellでLinqするためのレシピ。個人的にSum()
をよく使う。(Measure-Object
よりも簡潔に書けるから)
終わりに
- Powershellの生命線は補完機能だと思っている。
- 機会があったら
Register-ArgumentCompleter
について掘り下げてみたい。
公式ドキュメント:Register-ArgumentCompleter (Microsoft.PowerShell.Core) - PowerShell | Microsoft Docs
次回があったら書きたいこと
イベント、BackgroundJob
といった非同期の処理。
鍵を握るのは[scriptblock]
と型変換。
※他の有力候補
- VScodeでPowershellの開発コンテナを立ち上げる
- VScodeでPowershellのハイブリッドモジュールを作成する
- PowershellとWPFとXAML、あとバインド