彷徨うITエンジニアの雑記

ITインフラ関連の雑記とか

Powershellメモ1

foreach と Foreach-Objectで ループの抜け方が異なる

$list = @('1', '2', '3', '4')

# foreach の場合は continue で抜ける
foreach($item in $list) {
    if( ($item%2) -eq 0 ) { 
        Write-Output $item
    } else {
        continue
    }
}

# Foreach-Object の場合は return で抜ける
$list | Foreach-Object {
    if( ($_%2) -eq 0) {
        Write-Output $_
    } else {
        return
    }
}

Select-Object の出力を加工したい

以下のようにハッシュでプロパティ名とその値を指定してあげる。
Key名は任意の文字列でOK。
Select-Object Property,@{ Name = 'Label'; Expression = { script } }

> Get-Item .\hoge.txt | `
Select-Object FullName,@{N="mtime"; E={($_.LastWriteTimeUTC).ToString("yyyy-MM-ddTHH:mm:ss+09:00")}}

FullName         mtime
--------         -----
E:\work\hoge.txt 2020-12-01T15:16:08+09:00

危険なコマンドを実行する際に確認のステップを入れる

Read-Hostを入れると良い感じになる。

%{
  $cmd = 'icacls.exe .\'
  Write-Output "CMD: $cmd"

  $null = Read-Host "`rEnter で実行します"
  Invoke-Expression "$cmd"
}

while($true) {
  $cmd = "icacls.exe .\"
  Write-Output "CMD: $cmd"

  $ans = ""
  $ans = Read-Host "`r実行してもいいですか? (yes/no)"
  if("$ans" -eq 'yes') { Invoke-Expression "$cmd"; break }elseif("$ans" -eq 'no') { break }else{ continue }
}

CSV、TSVの作り方・パースの仕方

CSV、TSVを作る

# + 演算子で文字列を結合
> "1" + "`t" + "2" + "`t" + "3"
1       2       3

> # 配列から生成
> $list = @("1", "2", "3")
>
> # デリミタを指定して1個の文字列に結合
> $list -join "`t"
1       2       3

# 指定の要素で結合
> $list[0,2] -join "`t"
1       3

# オブジェクトから生成
> $obj = [pscustomobject]@{ 'col1' = '1'; 'col2' = '2'; 'col3' = '3' }
>
> # オブジェクトを変換。UseQuotes オプションはPowershell7以降
> $obj | ConvertTo-Csv -Delimiter "`t" -UseQuotes Never
col1    col2    col3
1       2       3

CSV、TSVをパースする

> $line = "1" + "`t" + "2" + "`t" + "3"
>
> # split演算子: 文字列を指定のデリミタで配列に変換
> $line -split "`t"
1
2
3
>
> # ConvertFrom-CSV: 文字列を指定のデリミタでオブジェクトに変換
> $line | ConvertFrom-Csv -Delimiter "`t" -Header col1,col2,col3

col1 col2 col3
---- ---- ----
1    2    3

ルックアップ

例えば、Timestamp, Operation, Path のようなファイル操作を記録したTSV形式の監査ログファイルがあり、条件を指定して検索したい。

Where-Object を使用した検索
- 柔軟な検索条件の指定が可能
- ハッシュテーブルを使用したルックアップより遅い

# TSVをオブジェクトに変換
$auditlog = Get-Content -Encoding UTF8 -Path $auditlog.tsv | ConvertFrom-Csv -Delimiter "`t" -Header Timestamp,Operation,Path

# 条件を指定してレコード検索
$auditlog | `
Where-Object { $_.Path -match '.*\\hoge.xlsx' -and [datetime]$_.Timestamp -lt [datetime]"2020/12/1" }

ハッシュテーブルを使用した検索

- 非常に高速なルックアップが可能。ただし、キー名の重複は許可されない
- 以下の例だと、ログ中に同じパス文字列が複数存在した場合、先に読み込んだログは上書きされてしまうため、キー名が重複した場合は値を配列に追加する等の考慮が必要

# キー名=ファイルパス、値=レコード全体、としたハッシュテーブルを作成
$hashtable = @{}
Get-Content -Encoding utf8 -Path $auditlog.tsv | ConvertFrom-Csv -Delimiter  "`t"  -Header Timestamp,Operation,Path | `
foreach { $hashtable[$_.Path] = $_ }

# キー名=検索文字列(完全一致) を指定してルックアップ
$pattern = '\\nas\share\salse\hoge.xlsx'
$matched_logs = $hashtable[$pattern]

# キー名にワイルドカードや正規表現は指定出来ないため、パターンマッチさせる場合は以下のようにアクセスする
$hashtable.Keys | Where-Object {$_ -like "hoge*"} | foreach { $hashtable[$_] }