Powershell」カテゴリーアーカイブ

タスクスケジューラーを使ってPowershellをサイレント実行する

コンソールウィンドウを一瞬も出さずにPowershellを実行したいとき、今まではvbs経由で実行する方法を採用していました。しかしながらvbsも非推奨機能となってしまったので、もう少し長い目で見て使えそうな方法を調べていたところ、タスクスケジューラ―が使えそうなことがわかりました。

タスクスケジューラ―を使う方法

タスクスケジューラ―でPowershellスクリプトをサイレント実行する場合、まず全般タブのセキュリティオプションで”ユーザーがログオンしているかどうかにかかわらず実行する”を選択しておきます。

トリガーについては任意で設定します。今回私がサイレント実行したかったのは常駐型のスクリプトだったので、PCのスタートアップ時に実行させることにしました。

操作の部分で実行したいスクリプトを設定します。プログラム/スクリプトの欄は”powershell.exe”でOKです。引数の追加の部分でPowershellスクリプトを指定します。例えば、”-ExecutionPolicy Bypass -File “C:\hogehoge\Script.ps1″”などとしておきます。スクリプトの場所を指定すると同時に、実行ポリシーも設定しておくのがポイントになります。

これで存在感を出すことなくPowershellスクリプトが実行できます。定時でバックアップ用のスクリプトを実行したり、特定のフォルダの清掃をしたりとサイレント実行が使えるといろいろとアイデアが広がります。

Powershellでフォルダを監視する

フォルダを監視して、動きがあったときに処理をするというスクリプトを作りたかったので調べていたところ、.NET APIの中にちょうどいいものがありました。

FileSystemWatcher クラス

[Microsoft Learn]

これはPowershellから呼び出すことができ、特定フォルダ内のファイルの作成や削除・変更などに応じてイベントを発生させることができます。実際にテストで作ってみたのが以下のスクリプトです。

<Powershell>
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\hogehoge"   # 監視フォルダ
$watcher.Filter = "*.*"            # 拡張子はここで指定する
$watcher.EnableRaisingEvents = $true

Register-ObjectEvent $watcher Created -Action {
    $path = $Event.SourceEventArgs.FullPath
    $time = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
    Write-Host "[$time] ファイル作成: $path"
}

Write-Host "$path を監視中です。Ctrl+Cで終了"
while ($true) { Start-Sleep 1 }

実行するとコンソールが開きっぱなしになり、対象のフォルダに何かファイルが作られるとコンソールにメッセージを表示して教えてくれます。見せ方はもうちょっとスマートにできそうな気はするものの、構造の骨子はこれでよさそうな感じです。

Powershellで大かっこを含むパスを処理する

年末の大掃除ということでPCのフォルダの整理をしていました。容量の大きなフォルダを削除しようと思い、Powershellでフォルダごとに再帰的に容量を計算して一覧表示するスクリプトを作りました。スクリプト作りはChatGPTに助けてもらいました。

#Powershell
$size = Get-ChildItem $_.FullName -Recurse -File -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
$sizeMB = [math]::Round($size.Sum / 1MB, 2)

おおむね想定通りに動作したのですが、明らかに容量がゼロではないのに容量がゼロとして認識されてしまうフォルダがあることがわかりました。それらのフォルダの共通点としては、フォルダ名に大かっこ [] が含まれていることでした。

私はフォルダの作成日やメモ書きの際に大かっこをフォルダ名に使うことがあるのですが、調べてみると大かっこはPowershellの処理ではワイルドカードの扱いになってしまいます。

about_Wildcards

[Microsoft Learn]

文字の範囲[a-z]や特定文字に一致[abc]という条件の際に使用されます。

上記のナレッジの下のほうに書いてありますが、いくつかの方法でワイルドカード文字をエスケープして、普通の文字のように取り扱うことが可能です。

今回は-Literalpathパラメータを追加してエスケープすることにしました。

#Powershell
$size = Get-ChildItem -LiteralPath $_.FullName -Recurse -File -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
$sizeMB = [math]::Round($size.Sum / 1MB, 2)

これはパラメータを追加するだけなので簡単です。ただ、そもそものところでプログラムがワイルドカードとして認識するような文字をフォルダ名に使う習慣をやめたほうがいいのかもしれません。

VS CodeでPowershellスクリプトを文字コード指定で出力する

Powershellの開発環境として使われていたPowershell ISEは今は非推奨となっているそうで、MicrosoftのVisual Studio Code(VS Code)が標準の開発環境となっています。

しかしVS Codeで日本語を含むスクリプトを書いてデバッグすると、内容が文字化けして動作しない場合があります。これはVS Codeの標準文字コードがUTF-8なのに対し、Powershellが実行する際にはBOM付きUTF-8で実行されるのが原因のようです。

Microsoftも問題を認識しているらしく、VS CodeとPowershellの文字コードに関してだけの情報を指南してくれています。

VS Code と PowerShell でのファイルのエンコードの概要

[Microsoft Learn]

これによると、

主に Windows アプリケーションと Windows PowerShell を使用している場合は、BOM ありの UTF-8 または UTF-16 のようなエンコードをお勧めします。

ということなのでやはりBOM付きUTF-8でスクリプトを出力するのが良さそうです。上記リンクのドキュメントにあるとおり、VS Codeの標準エンコードをBOM付きUTF-8にしてしまうのが最も確実ではないかと思います。

VS Codeで他の言語も扱うので一律BOM付きUTF-8になってしまうと困るという場合は、出力したスクリプトを一旦サクラエディタ等のテキストエディタで開き、BOM付きUTF-8にエンコードして保存するのがわかりやすくていいのではないかと思います。VS Codeの設定は少々複雑な感じもあるので、VS Codeを極力シンプルな状態で使いたいという場合はこの方法が良いのではないかと思います。

ネットワークの疎通確認に便利なPowerShellコマンド

最近PowerShellでネットワークの疎通確認に便利なコマンドを一つ覚えました。Test-NetConnectionというコマンドレットです。

Test-NetConnection

[Microsoft Learn]

このコマンドはその名の通り実行したクライアントのネットワーク疎通を確認できるコマンドです。特にオプションを設定せずに実行すると自分のPCがネットワークにつながっているかを確認できます。

特に使い出があると思うのがリモートホストに対する疎通確認です。オプションの”Computername”を使うとリモートホストが指定できます。また、オプションの”Port”を使うとポートを指定しての疎通確認もできるので、特定ホストに対して特定ポートで通信可能かを調べることができます。

例えば、

<PowerShell>
Test-NetCo<>ection -Computername hebodj.net -Port 443

と入力すると当ブログに対してポート443で通信可能かをチェックできるという形です。サーバーを公開するときやゲームの設定チェックなどにも便利に使えると思います。

Powershellの文字コード指定を登録済みコードページで行う

以前Powershell7以降で文字コード指定をする方法をエントリにしました。

Powershell(7.3.6)でShift-JISのCSVファイルを読む

[heboDJ.net]

この時は文字コードの名称で指定を行う方法をまとめましたが、先日関連するドキュメントに目を通していると、Powershell6.2以降では登録済みコードページでも文字コードの指定ができるとのことでした。

about_Character_Encoding

Encoding.CodePage プロパティ

[Microsoft Learn]

これによれば、以下のような指定方法でも文字コードをShift-JISに設定することができるようです。

[Powershell]
#$csvFileは読み込むCSVファイルのファイル名を含むフルパス
Import-Csv -Path $csvFile.FullName -Encoding "shift_jis"
Import-Csv -Path $csvFile.FullName -Encoding 932

OSのデフォルト値とするよりも、こちらの設定方法の方が明示的で後から見たときも分かりやすいそうに感じます。

以前は違ったように記憶しているのですが、現在Powershellで特に文字コードを指定しないとUTF-8が使用されるので、Shift-JISを指定したい場合はこのように明示的に指定しないと文字化けしてしまうことがあります。

PowershellでREST APIを利用する

最近PowershellでREST APIを使う用事がありました。特にPowershellにこだわる必要はなかったのですが、使い慣れているのと、例によってWindowsであればどの端末でも実行できるという点が便利だったのでPowershellを使うことにしました。

要件としては単純にGETメソッドを使って情報を取り出すというだけです。こういう簡単なスクリプトではよくPythonが推奨されるところかと思いますが、Powershellでも割と簡単でした。

コマンドレットとしてはInvoke-Restmethodというものがあるのでそれを使います。

Invoke-RestMethod

[Microsoft Learn]

サンプルとして何のAPIを使おうかと思いましたが、よく考えたらWordpressがREST APIに対応していたので、このブログに対して情報取得を試してみることにしました。

APIで取得したデータはJSON形式で返ってくるということは何となく知っていたのですが、ああいった構造化されたデータをどう処理するのかがピンときませんでした。Powershellの場合は結果を変数に格納すると暗黙的にPSObjectに変換してくれるということで、Powershellに慣れている身からすればこれは結構使いやすく感じました。

試しにこのブログの直近の投稿をAPIで取得し、結果をテーブル形式に変換して表示してみるコードを作ってみました。

<Powershell>
$uri = 'https://hebodj.net/wp-json/wp/v2/posts/'
$Response = Invoke-RestMethod -URI $uri

#メンバー一覧を出力
$Response | Get-Member | Format-Table

これでとりあえずPSObjectにどんなメンバーが格納されているかが分かります。ざっと眺めてみるとこれが投稿のタイトルかな、これが投稿時刻かな、というのが分かってきます。

続いて投稿のタイトルを抜き出して列挙するコードを考えてみます。

<Powershell>
$uri = 'https://hebodj.net/wp-json/wp/v2/posts/'
$Response = Invoke-RestMethod -URI $uri

# 各投稿のタイトルを表示
foreach ($post in $Response) {
    Write-Host $post.title.rendered
}
特に何も指定しないとトップページに表示されているエントリが対象になるようです。

こんな感じでOKです。同じような要領で本文も抜き出すことができるので、APIで今までのエントリの内容をファイルに出力して統計情報を出すなど、面白い使い方ができそうです。

今まであまりWebに関係する技術に縁がなかったのでREST APIも苦手意識があったのですが、Powershell経由であれば割と簡単にハンドリングできそうな気がします。

Windows Powershell ISEがサポートするのはv5.1まで

先日Powershellの開発環境について調べ物をしていたところ、標題の通りPowershell ISEがサポートしているのはv5.1までであることが公式ドキュメントから分かりました。

Windows PowerShell ISE

[Microsoft Learn]

Powershell ISEはWindowsに最初から入っているので気軽に使えて良かったのですが、最新のPowershellには対応していないということです。

また、このことからMicrosoftとしてはPowershell ISEをアクティブな機能開発の対象でないと位置づけているそうです。サポート終了というわけではないようですが、引退が迫っているような状況のようです。

代替としての標準的な開発環境は VS CodeとPowershell用拡張機能の組み合わせということです。確かにVS Codeは非常に高機能ですし、昔のVisual Studioなどと比べても簡単に導入できるようになったのでこちらがメインになるというのも納得です。

Powershell(7.3.6)でShift-JISのCSVファイルを読む

Powershellでスクリプトを作っていると結構困らされるのが文字コードの問題です。CSVを読み込んで加工後に書き出すであるとか、複数のCSVをマージするという作業ではスクリプトがよく使われるところだと思いますが、読み込むCSVと書き出すCSVの文字コードが異なると文字化けが起きる場合があります。

CSVの読み込みにImport-CSV、書き出しにExport-CSVを使う場合、コマンドレットで文字コードを明示的に指定することで問題を回避できます。

Microsoftのドキュメントによれば、Powershell7.1以降であれば以下の内容で文字コードを明示的に指定できるということです。

about_Character_Encoding

[Microsoft Learn]

上記ドキュメントの内容を踏まえてImport-CSVでShift-JISのCSVを読み込む場合、例えば以下のようなコマンドで読み込むことが可能です。

[Powershell]
#$csvFileは読み込むCSVファイルのファイル名を含むフルパス
Import-Csv -Path $csvFile.FullName -Encoding oem

文字エンコードoemというのは”MS-DOS およびコンソール プログラムの既定のエンコードを使用します。”ということですので、日本語Windowsを使っている場合、デフォルトではShift-JISです。

確認する場合はコマンドプロンプトを開き、”chcp”と入力します。”932”という値が返ってきた場合、Shift-JISに設定されています。

書き出す時にも明示的に文字エンコードが指定できるので、例えばUTF-8(BOMなし)にしたい場合は以下のようなコードで出力可能です。

[Powershell]
#$outputFileNameは書き出すCSVファイルのファイル名を含むフルパス
Export-Csv -Path $outputFileName -NoTypeInformation -Encoding utf8NoBOM

少々厄介なのはこの文字エンコードの指定方法がPowershellのバージョンによって異なることです。Powershellで何か作る場合はとりあえず最新版にしておくと、Microsoftのドキュメントと食い違いがなくなって良いと思います。

Windows Terminalで”ユーザー設定の読み込み中にエラーが発生しました”というエラーが表示される問題

Windows11(22H2)に内蔵されているWindows Terminalを起動した時に、毎回”ユーザー設定の読み込み中にエラーが発生しました 設定ファイルに同じGUIDを持つ複数のプロファイルが見つかりました。重複は無視されます…”というエラーが表示されて困っていました。

実用上特に問題はなさそうなのですが、うっとうしかったので原因を調べてみました。Windows Terminalは”動的プロファイル”という仕組みを採用しており、プロファイルを切り替えることで異なるシェルを切り替えて使うことができます。

例えば、Windows PowershellとWindows用LinuxのシェルであるWSLのシェルなどを切り替えて使えます。このシェルにはそれぞれGUID(ユニークな識別子)が振られますが、どうもこれが内部で重複しているということのようです。

Windows Terminalの設定画面左下に”JSONを開く”という項目があり、ここを開くとターミナルの設定がJSON形式で保存されています。GUI上でプロファイルごとの設定変更も可能になっていますが、GUIの設定ではGUIDを編集することはできなさそうでした。

その中にある”profiles”:で始まるブロックにプロファイルの設定が書かれています。確認してみると確かに複数のGUIDに重複があったので、重複がないように片方を変更してみたところエラーは解消しました。

<JSON>
{
    //↓guidを変更して重複がないようにする
    "guid": "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}",
    "hidden": false,
    "name": "PowerShell",
    "source": "Windows.Terminal.PowershellCore"
}

通常重複することはないはずの値だと思うので気になりますが、エラーが解消して何よりでした。