はじめに
初めまして。今年4月から次世代デジタル基盤開発事業部に籍を置くことになった I と申します。 こちらでは、Webプログラミングを中心に携わっております。
業務としての開発のみならず、作業や業務の効率化を開発者の視点で推進できるように試行錯誤しております。
PowerShellで便利なコマンドを簡単に作ってみるための手順などを書いていこうと思います。
目次
- はじめに
- 目次
- Windowsの強力な味方、PowerShell
- PowerShell コマンドの開発を Visual Studio Code で実践する
- 関数の簡単な解説
- まとめ
- おまけ1: モジュールの再インポートを便利にするコマンドレット
- おまけ2: Linux風のコマンドで入力できる関数一覧
Windowsの強力な味方、PowerShell
さて、Windowsで作業されている皆さんにとって、PowerShellというCLI(とシェルスクリプト実行環境)を目にする機会は多いと思われます。
もともと、Windowsには コマンドプロンプト(cmd) というMS-DOS譲りのCLIが存在していましたが、Windows 7の頃にMicrosoftが用意したものが起源です。
PowerShellには、コマンドプロンプトには無い、魅力的な機能が備わっていました。
1. コマンドレットの存在
PowerShellでは、コマンドのことを コマンドレット と呼んでいます。 *1 コマンドレットは基本的に以下のもので構成されています。
- 実行ファイル
- バッチファイル
- PowerShell関数(スクリプトで記述)
- .NETアセンブリ
また、出力される結果も、従来のシェルでは文字列(かその集合)でしたが、PowerShellではオブジェクトとして出力されます。
2. パイプライン処理
PowerShellでは、関数とコマンドの区別がありません。コマンドライン単体で呼びだせますし、関数内でも呼び出せます。
コマンドレットが処理した結果を、パイプ( |
)を使用して別のコマンドレットに引き継げます。
コマンドプロンプトや bash
などでもできますが、PowerShellでは文字列ではなくオブジェクトを渡しています。
例えば、コマンドプロンプトでいう dir
に相当する Get-childItem
コマンドを使用して、その結果を見てみます。
PS C:\Users\sample\Documents> Get-childItem . Directory: C:\Users\sample\Documents Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 2023/04/04 17:34 dir1 d---- 2023/04/14 13:08 dir2 d---- 2023/04/20 9:38 dir3 -a--- 2023/04/04 18:16 348699 file1.txt -a--- 2023/04/04 18:16 18617 file2.txt
このような出力だとピンと来ないと思われます。でも実は、これらはJavascriptでいう Object
のような構成です。
仮に、JSON出力で出力してみた場合を想定すると、以下のようなイメージになります。
[ { Mode: 'd----', LastWriteTime: '2023/04/04 17:34', Name: 'dir1' }, { Mode: 'd----', LastWriteTime: '2023/04/14 13:08', Name: 'dir2' }, { Mode: 'd----', LastWriteTime: '2023/04/20 9:38', Name: 'dir3' }, { Mode: '-a---', LastWriteTime: '2023/04/04 18:16', Length: 348699, Name: 'file1.txt' }, { Mode: '-a---', LastWriteTime: '2023/04/04 18:16', Length: 18617, Name: 'file2.txt' }, ]
これを、パイプを使ってオブジェクトをフィルタリングしてみます。
Get-childItem . | Where-Object {$_.Mode -eq "d----" }
すると、結果は Mode: 'd----'
のもの(つまりディレクトリ)だけがフィルタリングされます。
Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 2023/04/04 17:34 dir1 d---- 2023/04/14 13:08 dir2 d---- 2023/04/20 9:38 dir3
JSON化イメージではこうなります。
[ { Mode: 'd----', LastWriteTime: '2023/04/04 17:34', Name: 'dir1' }, { Mode: 'd----', LastWriteTime: '2023/04/14 13:08', Name: 'dir2' }, { Mode: 'd----', LastWriteTime: '2023/04/20 9:38', Name: 'dir3' }, ]
もちろん、さらにパイプを繋げてさらにフィルタリングできますし、変数にこのリストを紐づけできます。
$directories = Get-childItem . | Where-Object {$_.Mode -eq "d----" }
3. 関数が書きやすい
関数の書き方がCライクになり、構造がわかりやすくなっています。
例として、Linuxでの rm -irf
をPowerShellで書いてみます。
function Rmirf { Remove-Item -Recurse -Force $args };
このスクリプトをPowerShellにわかりやすい場所に保存しておけば、以下のコマンドで rm -irf
を実行できます。
Rmirf ./dir1
4. オープンソース版がある
現在、PowerShellの最新バージョンは7です。とはいえ、Windows標準のPowerShellは5なのです。これは一体どういうことなのでしょうか。
実は、Powershellは現在2系統に分かれており、Windows標準のものとは別に、PowerShell Core というものが存在しています。 こちらは、Githubで公開されているオープンソースソフトウェアで、マルチプラットフォームでの利用が可能です*2
Windows標準のほうは最新版が2017年ですので、筆者は更新が活発なPowerShell Core 7(Windows(x64)用)を愛用しています。本記事でも、こちらのバージョンを前提に記載いたします。
5. モジュールをまとめたサイトがある
PowerShellのモジュールは便利で、外部の人が作ったモジュールを取り込めます。
しかし、配布している場所がバラバラだと利用者の管理が不便になります。
そこでお勧めのサイトは、マイクロソフトが運営している PowerShell Gallery
です。
ここから使えそうなモジュールを調べて、Install-Module
コマンドで取り込めます。
PowerShell コマンドの開発を Visual Studio Code で実践する
ここからは、実際にPowerShellのコマンドレットを開発していきます。 直接関数を書いてもよいのですが、取り回しがきく モジュール 形式で開発していきます。
モジュールに関しては、Microsoftによる解説を読んでいただければ幸いですが、本記事では、そこまで難しく考える必要はございません。 使う関数をまとめたもの という認識で問題ないと思います。
手順を読むにあたって
今回はモジュールを作る例として、何らかのインストールを簡単にする EasyInstall
というモジュールを作ろうとしていると想定しています。
Visual Studio Codeの拡張を導入する
Visual Studio Codeには、PowerShellモジュール開発に便利な拡張機能がMicrosoftによって導入されています。 とにかく、この拡張を 必ず インストールすることをお願いいたします。
モジュールを配置するディレクトリの準備
PowerShellのスクリプトなどを格納するディレクトリは大体決まっております。 対象のディレクトリは複数あり、PowerShell本体は、それらのディレクトリを検索してモジュールなどを取り込んでおります。 なので、開発しているスクリプトを保存しておくときは、そこに置いておいたほうが無難です。
開発にお勧めのディレクトリは以下の場所です。
c:\Users\(ユーザー名)\Documents\PowerShell\Modules
ディレクトリが存在していない場合はあらかじめ作成をお願いいたします。
モジュールのディレクトリを作成
上記ディレクトリを作成しましたら、その直下にモジュール名を記したディレクトリを作成します。 ここで注意すべきなのは、作成するスクリプトファイル名と同じディレクトリ名にしておくことです。
EasyInstall
で例えると、ディレクトリ名は EasyInstall
となり、モジュールファイル名は EasyInstall.psm1
となります。
最終的なモジュールファイルのパスは、
c:\Users\(ユーザー名)\Documents\PowerShell\Modules\EasyInstall\EasyInstall.psm1
となります。
こうしておかないと、実際にそのモジュールを取り込もうとする時にエラーが発生します。
ディレクトリを作成したら、そのディレクトリのフルパスをメモしておいてください(クリップボードでもOK)。 このパスは後々使うことになります。
PowerShell拡張を起動
ここまでできたら、再びVisual Studio Codeの出番です。PowerShell拡張を起動します。
モジュール作成には Plaster
というツールを使用します。
まずは、 Ctrl+Shift+P
キーを押して、拡張メニューを開きます。
リストに PowerShell: Create New Project from Plaster Template
というものがありますので、それを選択します。
モジュール環境の構築
選択すると、モジュールを構築する際に、どのテンプレートを使うのかを指示されます。
ここでは、New PowerShell Manifest Module
を選択します。
次に、モジュールのパス名を促されるので、先にメモしておいたパスを記入します。 今回の例では、次のパスを入力します。
c:\Users\(ユーザー名)\Documents\PowerShell\Modules\EasyInstall
記入して Enter
すると、ターミナルが開いてPlasterが起動し、モジュール環境を自動的に構築してくれます。
ここからはターミナルでの作業となります。
モジュールの設定を記述
ここからはモジュールの設定となりますが、重要な設定は以下の二つのみです。
(1)モジュール名
先ほど決めたモジュール名です。 今回の例では EasyInstall
ですね。
(2)開発環境
[C]Visual Studio Code
を選びます。
あとはエンターキーで省略してください(まず使いません)。
設定が完了したら、VSCodeの新しいウインドウが開きます。
関数の実装
続いて、コマンドレットとして実行させたい関数を実装していきます。
非常に単純な例として、標準出力に Hoge
と表示させるだけの PrintHoge
というコマンドを作ります。
EasyInstall.psm1
を開き、# Implement your module commands in this script.
の下で以下のコードを記述します。
function PrintHoge { Write-Output "Hoge"; }
また、最後の行を編集して、明示的に PrintHoge
をエクスポートするようにしておきます。
Export-ModuleMember -Function PrintHoge
書き終えたら保存してコンソールに移ります。
モジュールのインストール
ここまで来たらあとは関数をコマンドとして呼び出すだけです。
Import-Module EasyInstall
なにも出力されなければインポートは成功です。
モジュールのインポート確認
このままコマンドを実行してもよいのですが、やはり「本当に入ってるの?」と疑問を呈されるのはごもっともです。
Get-Module
コマンドでインポートされているかどうかが確認できます。
Get-Module -ListAvailable -Name EasyInstall
コマンドの実行
最後に、PrintHoge
と打ち込めばコンソールに Hoge
と表示されます。
お疲れさまでした。
関数の簡単な解説
…と、このまま終わってしまっては先へ進めなくなるので、簡単ですが関数の内容をざっくりと解説します。
関数の引数
関数の引数を指定するときは Param
命令を使います。
引数に値を渡すと省略時にその値を使用します。
ここらへんはJavascriptなどと同じですね。
function Grep { Param ($regex, $dir = ".") Get-ChildItem $dir | Select-String -Pattern $regex }
ただし、可変長引数を扱う際は $args
グローバル変数と配列展開を使用します。
function RmIrf { Remove-Item -Recurse -Force $args };
Paramsと組み合わせて、複雑な引数構成を実装できます。
変数について
PowerShellでの変数は接頭辞に $
を付けておきます。
基本的に変数はオブジェクトだと認識していただけたらOKです。
例えば、以下のコードのようにすれば、モジュール一覧を変数として保持できます。
Javascript的に説明すると Objectの配列 です。
$modules = Get-Module -ListAvailable
例として、Windows標準PowerShellに入っているモジュールをPowerShell Coreで検索した結果を示してみます。
Directory: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules ModuleType Version PreRelease Name PSEdition ExportedCommands ---------- ------- ---------- ---- --------- ---------------- Manifest 1.0.0.0 AppBackgroundTask Core,Desk {Disable-AppBackgroundTaskDiagnosticLog, Enable-AppBackgroundTaskDiagnosticLog, Set-AppBackgroundTaskResourcePo… Script 1.0.0.0 AssignedAccess Core,Desk {Clear-AssignedAccess, Get-AssignedAccess, Set-AssignedAccess} Manifest 1.0.0.0 BitLocker Core,Desk {Unlock-BitLocker, Suspend-BitLocker, Resume-BitLocker, Remove-BitLockerKeyProtector…} Script 2.0.0.0 BitsTransfer Core,Desk {Add-BitsFile, Complete-BitsTransfer, Get-BitsTransfer, Remove-BitsTransfer…} Manifest 1.0.0.0 BranchCache Core,Desk {Add-BCDataCacheExtension, Clear-BCCache, Disable-BC, Disable-BCDowngrading…} (略)
そして、改めてフィルタリングもできます。 先にも書きましたが、モジュール一覧を指定の名前でフィルタリングする場合は以下のように記述します。
$moduleNames = $modules | Where-Object {$_.Name -eq $moduleName}
一覧のName属性が、指定の名前と同じものだけを抽出して、新しい配列を作っているイメージです。
比較演算子は bash
などを嗜んでいらっしゃればすぐに理解できると思います。
ちょっと例を示してみます*3。
$modules
に紐づけた内容から、バージョンが 1.0.0.0
のものだけを抽出してみます。
$modules = Get-Module -ListAvailable $modules | Where-Object {$_.Version -eq "1.0.0.0"}
すると、結果はこのようになります。実際に Version
のカラムが 1.0.0.0
のものばかりになっているのがお分かりになると思います。
PS C:\Users\***\source> $modules | Where-Object {$_.Version -eq "1.0.0.0"} Directory: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules ModuleType Version PreRelease Name PSEdition ExportedCommands ---------- ------- ---------- ---- --------- ---------------- Manifest 1.0.0.0 AppBackgroundTask Core,Desk {Disable-AppBackgroundTaskDiagnosticLog, Enable-AppBackgroundTaskDiagnosticLog, Set-AppBackgroundTaskResourcePo… Script 1.0.0.0 AssignedAccess Core,Desk {Clear-AssignedAccess, Get-AssignedAccess, Set-AssignedAccess} Manifest 1.0.0.0 BitLocker Core,Desk {Unlock-BitLocker, Suspend-BitLocker, Resume-BitLocker, Remove-BitLockerKeyProtector…} (略)
まとめ
今回は、PowerShell Coreを使用して便利なコマンドを作ってみました。 標準で用意されているコマンドレットを組み合わせたりすることでもっと作業が捗ることを願います。
おまけ1: モジュールの再インポートを便利にするコマンドレット
さて、ここまでモジュールのインポートまで実践してみましたが、一つ面倒くさいことがあります。それは、モジュールを再インポートする場合、いったんそのモジュールを明示的に解除する必要があることです。
というのも、一旦実装してみてエラーがあった時や、思ったような結果にならずに修正したい時、未知の要素を盛り込みたくて試行錯誤したい時に地味に操作が面倒くさくなります*4。
コマンドを一発打つだけで一括で処理できればうれしいので、それをやってくれるコマンドレットを作ってみました。
# 指定のモジュールを(再)インポート # すでにインポートされていれば、いったん解除してインポート function ReImportModule { Param ($moduleName) $modules = Get-Module -ListAvailable $moduleNames = $modules | Where-Object {$_.Name -eq $moduleName} # モジュールが存在していればまず削除 If($modulenames.Count -eq 0) { Write-Output "This module is not imported. Import only : ${moduleName}" } Else { Remove-Module $moduleName Write-Output "Removed Module : ${moduleName}" } Import-Module $moduleName Write-Output "Imported Module : ${moduleName}" }
これを一旦モジュール化してインポートしておけば、あとはコマンドを1回打つだけで再インストールができるようになります。
ReImportModule EasyShell
おまけ2: Linux風のコマンドで入力できる関数一覧
ls -la
function LsLa { Get-ChildItem -Force }
rm -rf
function RmRf { Remove-Item -Recurse -Confirm $args };
rm -irf
function RmIrf { Remove-Item -Recurse -Force $args };
grep
function Grep { Param ($regex, $dir = ".") Get-ChildItem $dir | Select-String -Pattern $regex }
grep -v
function GrepV { Param ($regex, $dir = ".") Get-ChildItem $dir | Select-String -NotMatch $regex }
grep -r
function GrepR { Param ($regex, $dir = ".") Get-ChildItem -Recurse $dir | Select-String -Pattern $regex }
grep -rv
function GrepRV { Param ($regex, $dir = ".") Get-ChildItem -Recurse $dir | Select-String -NotMatch $regex }