[PowerShell] PS1 ファイル実行時にとる引数をコマンドレットみたいにタブ補完したい!

みなさんごきげんよう。ういこです。花粉はビシバシ飛んでいるのに寒くて仕方ないアンビバレンツな日々ですが皆様いかがお過ごしでしょうか。
今日は、「コマンドレットみたいにボクの PS1 ファイルの引数もタブ補完させる方法」をご紹介します。対象は PowerShell v1.0 – 2.0 共通です。

【今日のお題】 タブ補完出来たっていいじゃないか、PS1 ファイルだって PowerShell だもの。 by ういこう

a. PowerShell のタブ補完の素晴らしさに思いを馳せる
PowerShell がすてきだな、と思わせてくれる瞬間は、何といっても「あのすさまじく長いこともあるコマンドレットの名前をタブでガッツリ補完してくれる」というあの至れり尽くせりな感じ」 であると私は思います。正直「コマンドレット名…なんでこんなに長いのさ…」と思うなが~いコマンドレット名もいっぱいありますが、大丈夫!ちょっと打ち込んで、タブを打てばどこかの黒い執事さん張りに「あなたのお探しのコマンドレットはこれでございましょうか」くらいの勢いで提案してくれます。 素敵!
ありがとう PowerShell!これでうろ覚えでも使えるよ!ちなみにカレントディレクトリにあるファイルも補完してくれちゃったりします。これはコマンド プロンプトと同じですね。

例 コマンドレットを探してみよう (Get-ADAccountAuthorizationGroup)
get-a まで入力して、おもむろにタブキーを押そう

image

候補を出してくれますよ!”Get-ADAccountAuthorizationGroup”、な、ながっ!!

image

※ すぐに結果だけほしいの…というあなたには、あるいは get-help get-a* のように、ワイルドカードで一気に出しちゃうという手もお勧めです

image

さて。それで、タブ補完って、実はコマンドレット名だけじゃなくて、そのコマンドレットの引数まで出してくれるんですよ
これがまた特に switch と呼ばれる、変数がほしいわけじゃなくて、処理を分岐させる判断のために使うタイプの引数とかに威力を発揮。なが~い引数名いちいち打ってられないじゃないですか。だってタイプミスしたらまた長いの打ち直しだし。
上記のコマンドレットを例にとると、いくつか引数を取りますが、いちいち Get-Help で見るの面倒。もしくはうろ覚えで機能は覚えてるけど引数名超覚えてないという方も大丈夫。- (ハイフン) を打って、タブを打つと、引数名まで保管してくれます。

image

いやほんと便利ですよね。

b. PS1 (PowerShell スクリプト ファイル) にも引数を取るつくりの場合、タブ補完って使えないのかな?
コマンドレットがバリ便利ということはいやってほどわかりますが、それを組み合わせてスクリプト ファイルにした場合、引数を取ることってありえますね。そうした場合、PS1 実行時に使う引数名をコマンドレットみたいに補完出来たらいいなぁと思うことがあるのではないでしょうか。
こうした時、どうすればいいか?答えは、「Param ステートメントを使う」です。

<ポイント>
・PS1 ファイル (スクリプト ファイル) で PowerShell コマンドレットと同様に引数名を公開させるためには、param ステートメントを利用します。これにより、- キー入力後続けてタブ キーを押すことでパラメータ名の補完が実施されます。
・param ステートメントは PS1 ファイルの先頭に記載する必要があります。ただしコメント (# で続く文字列) が前に来るぶんには問題ありません。

例) Param( [string]$computer=$env:computerName, [switch]$disk, [switch]$processor, [switch]$memory, [switch]$network, [switch]$video, [switch]$all )

引数名にアクセスできるようにするためには、引数名を PS1 ファイルの先頭に param ステートメントを定義し、その中で指定します。構文は Param(<…>) です。<…> に引数名を指定します。必ずかっこ内に入れてください。この際既定値を与えておくことも可能です。引数が明示的にユーザに与えられなかった場合設定しておいた既定値が使用されます。
上記の例では、第一引数 $computer に PowerShell は Environment プロバイダを通じ、$env 変数からシステムの環境変数に関する情報を取得して初期値としてセットしています。

[string]$computer=$env:computerName,

初期値として実行コンピュータ名が入ります。これは実行時に上書きし、例えばリモート コンピュータ名を入力すれば、リモート コンピュータ名が $computer 変数に入りますが、実行時に指定しなければ、ローカル コンピュータの値がそのまま使われる動作になります。

次の、二個目以降の [switch] がついている引数は、スイッチ パラメータ、つまり値を指定しない、処理分岐用の引数などに用いられるタイプの引数です。

[switch]$disk,
[switch]$processor,
[switch]$memory,
[switch]$network,
[switch]$video,
[switch]$all

タブ押下時に呼び出される順番は?
基本的に、宣言された順に候補が補完されます。上記の例の場合、以下のようになります。

タブ 1 回目 : .\<PS1 ファイル名>.ps1 –computer

image

タブ 2 回目 : .\<PS1 ファイル名>.ps1 –disk

image

タブ 3 回目 : .\<PS1 ファイル名>.ps1 -processor

image

簡単に挙動を試す方法
一番挙動を簡単に試すには、上記 -a. 項の Param ステートメントだけをメモ帳に張り付け、拡張子 PS1 ファイルで保存し、PowerShell 上で動かすことです。中身がないので、実行しても何も起こりませんが、タブ補完の挙動だけみることができます。

<確認手順>
1. 以下をメモ帳にコピー
####### ここから
Param(
[string]$computer=$env:computerName,
[switch]$disk,
[switch]$processor,
[switch]$memory,
[switch]$network,
[switch]$video,
[switch]$all
)
####### ここまで

2. メモ帳を拡張子 ps1 で保存 (例 C:\temp\Tabtest.ps1)
3. PowerShell を起動
4. .\<ファイル名>.ps1 の後、"-" を入力し、タブキーを押す

例)
PS C:\temp> .\Tabtest.ps1 -computer

5. もう一度そのままタブキーを押すと、-computer から
-disk に引数が変わって補完されることを確認

例)
PS C:\temp> .\Tabtest.ps1 -disk

c. Scripting Guy のコマンドレットをちょっとだけ改造したサンプル
このサンプルでは、WMI を使ってコンピュータの情報を出すようにしています。以下の表の中の内容をメモ帳にコピーし、拡張子 ps1 で実行してみてください。実行前に - を打ってタブを打って、ちゃんと引数が変わることを確認していただけると思います。

-computer … コンピュータ名、省略時はローカルの情報を出す。指定した場合はリモート コンピュータの情報を確認しに行く
-disk … ディスク情報を取得します。
-processor … CPU の情報を取得します。
-memory … 搭載メモリの情報を取得します。
-network … NIC (ネットワークアダプタ) の情報を取得します。全部。
-video … ディスプレイ デバイスの情報を取得します。接続モニタ全部。
-all … 上記の -disk ~ -video まで全部まとめてとってきます。

################################################## # Param ステートメント開始。 # [switch] が頭に付けられているのは、「スイッチ パラメータ」です。 # 値を指定しない、処理分岐用の引数などに用いられます。 # 通常の引数には [string] [int] など型を指定しておくことをお勧めします。 # # 注意 ※ # ・Param ステートメントはスクリプトの先頭に来る必要があります。 # ・コメントは Param ステートメントより前に記述することができます。 # ・関数の引数は "Param(" 以降に記述し、各引数は "," で区切ります。 # ・最後の引数の後には "," は使用せず、")" で閉じます。 ################################################## Param( [string]$computer=$env:computerName, [switch]$disk, [switch]$processor, [switch]$memory, [switch]$network, [switch]$video, [switch]$all ) # Param ステートメントここまで ###################

# エントリで使用できる関数を事前定義します。 Function Get-Disk($computer) { Write-Host "-------------------------------------" Write-Host "- ドライブのサイズ情報" Write-Host "-------------------------------------" $logicalDisk=(Get-WMIobject Win32_LogicalDisk -filter "DriveType=3") foreach ($disk in $logicalDisk) { Write-Host $disk.Deviceid "Drive Size = " $disk.Size } } Function Get-Processor($computer) { Write-Host "-------------------------------------" Write-Host "- CPU 情報" Write-Host "-------------------------------------" $processors = Get-WmiObject -class Win32_Processor -computername $computer foreach ($processor in $processors) { Write-Host "CPU Name = " $processor.Name "コア数 :" $processor.NumberOfLogicalProcessors } } Function Get-Memory($computer) { Write-Host "-------------------------------------" Write-Host "- 搭載メモリ情報" Write-Host "-------------------------------------" $memories = Get-WmiObject -class Win32_PhysicalMemory -computername $computer foreach ($memory in $memories) { Write-Host "メモリサイズ (Byte) " $memory.Capacity "メモリ区分 :" $memory.Description }

} Function Get-Network($computer) { Write-Host "-------------------------------------" Write-Host "- ネットワーク アダプタ情報" Write-Host "-------------------------------------" $adapters = Get-WmiObject -class Win32_NetworkAdapter -computername $computer foreach ($adapter in $adapters) { Write-Host $adapter.NetConnectionID $adapter.Description } } Function Get-Video($computer) { Write-Host "-------------------------------------" Write-Host "- ビデオカード情報" Write-Host "-------------------------------------" $videoControllers = Get-WmiObject -class Win32_VideoController -computername $computer foreach ($videoController in $videoControllers) { Write-Host $videoController.Description } }

############################################################### # *** スイッチ パラメータを解釈し、処理分岐をさせる関数 *** ############################################################### Function Get-CommandLineOptions { if($all) { Get-Disk($computer) Get-Processor($computer) Get-Memory($computer) Get-Network($computer) Get-Video($computer) exit } if($disk){Get-Disk($computer)} if($processor){Get-Processor($computer)} if($memory){Get-Memory($computer)} if($network){Get-Network($computer)} if($video){Get-Video($computer)} }

################################################################ # *** スクリプトのエントリ ポイントはここから *** ################################################################ # 実行環境チェック #"…現在ご利用の環境が仮想マシンか実機か確認しております。少々お待ちください…" #if((Get-WmiObject Win32_ComputerSystem).model -ne "virtual machine") #{ # $response = Read-Host -prompt "この環境は仮想マシンではありません。処理を続行しますか? <y / n>" # if ($response -eq "n") { exit } #} #else #{ # $response = Read-Host -prompt "この環境は仮想マシンです。バックアップ取得を先にされる場合は n を、処理を続行する場合は y を押してください。 <y / n>" # if ($response -eq "n") { exit } #} # スイッチ パラメータの分岐と実行呼び出し Get-CommandLineOptions

d. 参考情報
作成したスクリプトをテストする方法はありますか
https://technet.microsoft.com/ja-jp/scriptcenter/ee517335

Windows PowerShell スクリプトの処理速度を上げる方法はありますか
https://technet.microsoft.com/ja-jp/scriptcenter/ee532494

Windows PowerShell: スクリプトでコマンドレットを記述する
https://technet.microsoft.com/ja-jp/magazine/ff677563.aspx

では皆様ごきげんよう。

ういこう@PowerShell を腐女子的に見た場合(以下自粛)