01〜09 は読んで理解するための資料でした。この章は書いて確かめるためのものです。勉強会の各回で 35 分のハンズオン時間を想定しています。
| 項目 | 内容 | いつまでに |
|---|---|---|
| 作業フォルダ | C:\work\ps-lesson\ | 第1回 |
| VS Code | PowerShell 拡張機能を入れておく(03.4) | 第1回 |
| PowerShell 7 | winget install --id Microsoft.PowerShell | 第2回 |
| 実行ポリシー | Set-ExecutionPolicy -Scope CurrentUser RemoteSigned | 第2回 |
| サンプルログ | 下記のスクリプトで生成 | 第5回 |
# 課題5 用のログを生成する(そのまま実行してください)
$dir = 'C:\work\ps-lesson\logs'
New-Item -ItemType Directory -Force -Path $dir | Out-Null
$levels = 'INFO', 'INFO', 'INFO', 'WARN', 'ERROR'
$msgs = 'ユーザーがログインしました user=tanaka',
'応答が遅延しています ms=1520',
'接続に失敗しました host=db01',
'バッチが完了しました count=120',
'タイムアウト host=api02'
$lines = foreach ($i in 1..200) {
$d = (Get-Date).AddDays(-(Get-Random -Minimum 0 -Maximum 10))
'{0} {1:HH:mm:ss} {2,-5} {3}' -f $d.ToString('yyyy-MM-dd'), $d,
($levels | Get-Random), ($msgs | Get-Random)
}
$lines | Sort-Object | Set-Content (Join-Path $dir 'app.log') -Encoding UTF8
Write-Host "$($lines.Count) 行のログを作成しました"
Get-Member。何が取れるか分かれば道が開ける-WhatIf を付けてから。作業フォルダの外は触らないGet-Process | Sort-Object ... | Select-Object ... を一気に書いて動かないと、どこが悪いのか分かりません。Get-Process だけ実行 → 結果を見る → | Sort-Object ... を足す → また結果を見る、と1段ずつ伸ばすと、間違えた瞬間に気づけます。PowerShell の対話環境はこの進め方に向いています。
同じファイルが、保存形式と実行するバージョンによって文字化けしたりしなかったりすることを、自分の目で確認します。
hello.ps1 を作り、Write-Host "こんにちは、PowerShell" と1行書くpowershell.exe -File .\hello.ps1 で実行する 5.1pwsh.exe -File .\hello.ps1 で実行する pwsh 7| 保存形式 | 5.1 での表示 | 7 での表示 |
|---|---|---|
| UTF-8(BOM なし) | 文字化け | 正常 |
| UTF-8(BOM 付き) | 正常 | 正常 |
# 先頭3バイトを見て BOM の有無を確認する
Get-Content .\hello.ps1 -AsByteStream -TotalCount 3 # pwsh 7
# → 239 187 191 (EF BB BF)なら BOM 付き
ダブルクリックで動く形を作ります。02.4 の内容をそのまま手を動かして確認します。
greet.ps1 を作る。引数 -Name を受け取り、時刻に応じて挨拶を変えるgreet.cmd を作る。Shift-JIS + CRLF で保存するgreet.cmd をダブルクリックするpause で画面が閉じずに残るRestricted に戻し、ランチャー経由なら動くことを確認するgreet.cmd に引数を渡せるようにする(%* を使う)Get-Member で自分で調べて解きます。「メモリ使用量のプロパティ名」はあえて教えません。
Get-Process | Get-Member で、メモリに関係するプロパティを探すプロセス メモリMB Id
-------- -------- --
Memory Compression 1081.50 3316
claude 433.50 49708
Discord 295.60 39604
explorer 289.60 30292
vmmemWSL 263.10 43988
処理を関数に切り出し、結果を文字化けしない CSV に出します。
Get-FileReport という関数を作る(引数: -Path、-Filter)Export-Csv でファイルに出す名前 フォルダ サイズMB 更新日
---- -------- -------- ------
05-パイプライン.html docs 0.05 2026-08-23 08:26
02-ファイル形式.html docs 0.05 2026-08-22 23:19
...
Group-Object)ここまでの総合課題です。実務でそのまま使える1本を完成させます。
| 要件 | 関連章 |
|---|---|
| 引数でログのパスと集計日数を受け取る(日数の既定は 7) | 06.2 |
| ファイルが存在しない場合、分かりやすいメッセージを出して終了コード 1 で終わる | 08.2 |
| 日付 × レベルで件数を集計して表示する | 05.4 |
| ERROR が1件以上あれば、詳細を赤字で出す | 08.4 |
| 正常終了時は終了コード 0 を返す | 08.3 |
-Verbose で処理の進行が見える | 06.2・08.4 |
$LASTEXITCODE が成功時 0、失敗時 1 になるGet-Process |
Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 -Property @{ N='プロセス'; E={ $_.ProcessName } },
@{ N='メモリMB'; E={ [math]::Round($_.WorkingSet64 / 1MB, 1) } },
Id |
Format-Table -AutoSize
WS(M) という表示用の名前で、実際のプロパティ名ではありません(05.1)。Get-Process | Get-Member -MemberType Property で一覧すると WorkingSet64 が見つかります。Select-Object -First 5 の位置Select-Object -First 5 を Sort-Object の前に書くと、「最初の5件を取ってから並べ替える」ことになり答えが変わります。function Get-FileReport {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$Path,
[string]$Filter = '*'
)
Get-ChildItem -LiteralPath $Path -Recurse -File -Filter $Filter |
ForEach-Object {
[PSCustomObject]@{
名前 = $_.Name
フォルダ = $_.Directory.Name
サイズMB = [math]::Round($_.Length / 1MB, 3)
更新日 = $_.LastWriteTime.ToString('yyyy-MM-dd HH:mm')
}
}
}
$report = Get-FileReport -Path 'C:\work\ps-lesson' -Filter '*.html'
$report | Sort-Object サイズMB -Descending | Format-Table -AutoSize
$report | Export-Csv -LiteralPath '.\report.csv' -Encoding UTF8 -NoTypeInformation
[PSCustomObject] の使い分け[PSCustomObject]@{ } のほうが読みやすいので、上の解答ではそちらを使いました。Select-Object の計算プロパティ(@{ N=...; E={...} })は、既存の列に1〜2列足すときに向いています。
-Encoding UTF8 はBOM なしで出力されます(07.3)。Excel は BOM なし UTF-8 の CSV を ANSI と誤認することがあります。-Encoding utf8BOM を指定するか、Excel 側で[データ]→[テキストから]でエンコーディングを指定して読み込んでください。
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$LogPath,
[int]$Days = 7
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
try {
if (-not (Test-Path -LiteralPath $LogPath -PathType Leaf)) {
throw "ログファイルが見つかりません: $LogPath"
}
$since = (Get-Date).AddDays(-$Days).Date
$pattern = '^(?<date>\d{4}-\d{2}-\d{2})\s+\S+\s+(?<level>\w+)\s+(?<msg>.*)$'
Write-Verbose "対象: $LogPath ($($since.ToString('yyyy-MM-dd')) 以降)"
$records = Get-Content -LiteralPath $LogPath -Encoding UTF8 |
ForEach-Object {
if ($_ -match $pattern) {
[PSCustomObject]@{
日付 = [datetime]$matches.date
レベル = $matches.level
内容 = $matches.msg
}
}
} | Where-Object { $_.日付 -ge $since }
Write-Verbose "解析できた行: $(@($records).Count) 件"
$records |
Group-Object { $_.日付.ToString('yyyy-MM-dd') }, { $_.レベル } |
ForEach-Object {
[PSCustomObject]@{
日付 = $_.Group[0].日付.ToString('yyyy-MM-dd')
レベル = $_.Group[0].レベル
件数 = $_.Count
}
} | Sort-Object 日付, レベル | Format-Table -AutoSize
$errors = @($records | Where-Object レベル -eq 'ERROR')
if ($errors.Count -gt 0) {
Write-Host "ERROR が $($errors.Count) 件あります:" -ForegroundColor Red
$errors | ForEach-Object { Write-Host " $($_.日付.ToString('yyyy-MM-dd')) $($_.内容)" }
}
exit 0
}
catch {
Write-Host "エラー: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}
try/catch に入らないTest-Path はエラーを出さず False を返すだけなので、自分で throw する必要があります。-ErrorAction Stop か、上の解答のように先頭で $ErrorActionPreference = 'Stop' を書きます(08.2)。
$records.Count がエラーになる$records は $null になり、.Count が使えません(06.3)。@($records).Count と@() で包むと、0件でも1件でも安全に数えられます。Set-StrictMode -Version Latest を入れていると、この種のミスがその場でエラーになるので早く気づけます。
| 状況 | やること |
|---|---|
| 何が返るか分からない | | Get-Member |
| 中身の値を見たい | | Select-Object -First 1 | Format-List * |
| コマンドが思い出せない | Get-Command *keyword* |
| 使い方が分からない | Get-Help <コマンド> -Examples |
| 0 件しか返らない | 07.2 の3つの理由を確認する |
| 文字化けする | 読み書き両方の -Encoding を確認する |
| エラーが捕まらない | -ErrorAction Stop を付ける |
| 部 | 章 | 到達点 |
|---|---|---|
| 第1部 基礎編 | 01〜05 | 読んで分かる — PowerShell の考え方と文法が理解できる |
| 第2部 実践編 | 06〜09 | 書けるようになる — 自分で関数を作り、実務スクリプトが書ける |
| 第3部 演習 | 10 | 一人で作れる — 課題を通じて手が動くようになる |
Get-Member と Get-Help -Examples。この資料に載っていないコマンドも、同じ手順で扱えます-WhatIf と Write-Verbose を付けてから使う。事故を防ぎ、後から追えるようになりますGet-Command)→ 使い方を見る(Get-Help -Examples)→ 1件だけ動かす → 中身を見る(Get-Member)