PowerShell備忘録



Windows縛り的な状況のときに便利だけど即忘れるので思い出す脳内コストを削減するために備忘録を。
順不同
### helpみる
```
 Get-Help -Name Import-CSV -Full
```
[https://docs.microsoft.com/ja-jp/powershell/scripting/learn/ps101/02-help-system?view=powershell-7.1](https://docs.microsoft.com/ja-jp/powershell/scripting/learn/ps101/02-help-system?view=powershell-7.1)


### モジュールのインポート

```
Import-Module Az.Accounts
Import-Module Az.Resources
Import-Module Az.Compute
Import-Module Az.Automation

```
※AuzureAutomationのImportはメニューから別途するしコードに書く必要がないが書いとかないと何使ってたか分からなくなってモジュールアップデートの影響範囲を調べづらくなるので書いたほうがいいかなという感想です。

### 変数をセットする

```
# String
$maResourceGroupname = "komitet-rg"
# array
$metric_obj = @()
$metric_obj = @('a','b')
# custom object
$ins_counts = [PSCustomObject]@{ステータス = "完了"; 件数 = "$stat_comp";},
[PSCustomObject]@{ステータス = "受付済"; 件数 = "$stat_l2";},
[PSCustomObject]@{ステータス = "新規"; 件数 = "$stat_new";},	
[PSCustomObject]@{ステータス = "対応中"; 件数 = "$stat_wip";}
# json
$ParamsJson = '[
    {
      "project": "hoge",
      "checkServices": [
        "AzureFrontDoor.Backend",
        "AzureTrafficManager"
      ]
    },
    {
      "project": "piyo",
      "checkServices": [
        "AzureActiveDirectoryDomainServices",
        "DataFactoryManagement",
        "AzureMonitor.Core"
      ]
    }
]'
```

### 変数の中身を見る

```
$metric_obj
Write-output $metric_obj
echo $metric_obj
```
どれでも出る。
一行だけ見る場合

```
# select-objectの-First 1オプション
$Error[0] -split "`r`n" | Select-Object -First 1
# 配列なら添え字使う
($Error[0] -split "`r`n")[0]
```
headとtail風

```
Get-Process | Select-Object -Property ProcessName, Description, StartTime -First 5
Get-Process | Select-Object -Property ProcessName, Description, StartTime -Last 5
```
[https://qiita.com/miyamiya/items/059430d5499f0fe336ff](https://qiita.com/miyamiya/items/059430d5499f0fe336ff)

### データ型を変換する

```
#関数をつかう
$yesterday = ((Get-Date).AddDays(-1)).ToString("yyyy-MM-dd");
#冒頭に型を足す
$storage_space_free_mb = ([int]$total - [int]$used)
[pscustomobject]@{TIME = $timegenerated; storage_space_free_mb = $storage_space_free_mb; storage_useage_percent = $storage_useage_percent;} | Write-Output
#変換コマンドをつかう
$diffdatafd = $diffresultfd|ConvertTo-Json|ConvertFrom-Json
$msgtm = $diffdatatm.InputObject.value|Out-String
$computer_selected|Select-String $metric |convertfrom-csv -Header $CsvHeader|Export-Csv -NoTypeInformation $outfile
	
```

### 日付を扱った変数

```
$yesterday = ((Get-Date).AddDays(-1)).ToString("yyyy-MM-dd");
$yesterday_start = (((Get-Date).AddDays(-1)).ToString("yyyy-MM-dd") + "T00:00:00+09:00");
$yesterday_end = (((Get-Date).AddDays(-1)).ToString("yyyy-MM-dd") + "T23:59:59+09:00");
$lastmonth = ((Get-Date).AddMonths(-1)).ToString("yyyyMM");
```
特定の期間でデータ出したいときにstart/endを定義。

日付を比較して一定期間を出す

```
$insobj_lm = $insobj|Where-Object {[Datetime]$_.registered_date -gt [Datetime]$startdate}|Where-Object {[Datetime]$_.registered_date -lt [Datetime]$enddate}
```
[https://qiita.com/Q11Q/items/2c8ff4fcd0141df3eacb](https://qiita.com/Q11Q/items/2c8ff4fcd0141df3eacb)

Timezoneの表示をJSTに変換したい場合

```
$now.ToUniversalTime()
2021年4月26日 3:46:56
$newtime = $now.ToUniversalTime()
$strCurrentTimeZoneId = "Tokyo Standard Time"
$TZ = [System.TimeZoneInfo]::FindSystemTimeZoneById($strCurrentTimeZoneId)
$LocalTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($newtime, $TZ)
$LocalTime
2021年4月26日 12:46:56
```
`(Get-WmiObject win32_timezone).StandardName`だと二バイト文字で失敗するので`Get-TimeZone -ListAvailable`で目的のIDを見つけて直指定。
### 作業ディレクトリを定義

```
#現在のディレクトリから作業ディレクトリを定義
$savedir = (Convert-path .\$yesterday)
#ホームディレクトリから定義
$savedir = (Convert-path ~/$yesterday)
```

その他ファイル操作
https://qiita.com/mima_ita/items/ae31f3a19389e69b307f
### 文字列を連結しファイル名にする

```
$outfile = Join-Path $savedir ($yesterday + "-$computer-$metric" + ".csv")
```
他、-joinなど
https://www.tekizai.net/entry/powershell_string_concat_1
### ファイル名一括置換

`ls | Rename-Item -NewName { $_.Name -replace '置換対象文字列','置換後文字列'}`
https://qiita.com/hyakuson/items/9e8e239d4ba45b595486

```
> $datestr="2021-04-29"
> $prefix="[mypj]_"
> ls
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2021/04/30     18:40          22573 2021-04-29-hoge01-cpu_percent.csv
-a----       2021/04/30     18:40          26057 2021-04-29-hoge01-dwu_consumption_percent.csv
-a----       2021/04/30     18:40          23525 2021-04-29-hoge01-memory_usage_percent.csv


> ls | Rename-Item -NewName { $_.Name -replace "${datestr}-","${prefix}" -replace ".csv","_${datestr}.csv"}
> ls
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2021/04/30     18:40          22573 [mypj]_hoge01-cpu_percent_2021-04-29.csv
-a----       2021/04/30     18:40          26057 [mypj]_hoge01-dwu_consumption_percent_2021-04-29.csv
-a----       2021/04/30     18:40          23525 [mypj]_hoge01-memory_usage_percent_2021-04-29.csv
```
後方参照＋正規表現だとうまくいかずに`-replace`を複数書いた
### ループ処理する
以下は特定の２つの配列変数に含まれる値に絞ってそれをファイル名にして出力させるために入れ子にした

```
foreach ( $num in $computers_suffix )
{
    $computer = ($computers_prefix + $num)
    $computer_selected = $csv_obj|convertto-csv|Select-String $computer
    foreach ( $metric in $metrics )
    {
        $outfile = Join-Path $savedir ($yesterday + "-$computer-$metric" + ".csv")
        $computer_selected|Select-String $metric |convertfrom-csv -Header $CsvHeader|Export-Csv -NoTypeInformation $outfile
    }
}
```
以下は一行ずつ読み込んで処理した値をCSVに出力するときに-Appendで1行ずつ追記している

```
$metric_data = az monitor metrics list --resource $sa_rid_log --metrics UsedCapacity --interval 1h --aggregation Average --start-time $yesterday_start --end-time $yesterday_end --query "value[0].timeseries[0].data[*].{TIME:timeStamp,UsedCapacity:average}" -o json
$metric_obj = @()
$metric_obj = $metric_data|ConvertFrom-Json
$outfile = Join-Path $savedir ($yesterday + "-log-UsedCapacity" + ".csv")
$metric_obj|Export-Csv  -NoTypeInformation $outfile
foreach ( $metric in $metric_obj )
{
    $limitbyte = 5629499534213120
    $used = $metric.UsedCapacity
    $free = ($limitbyte - $used)
    $outfile = Join-Path $savedir ($yesterday + "-log-FreeCapacity" + ".csv")
    $metric_obj2 = [pscustomobject]@{TIME = $metric.TIME; freeCapacity = $free;} | Write-Output
    $metric_obj2|Export-Csv -NoTypeInformation -Append $outfile
}
```
### 並列処理
１つずつのループではなく時短で並列でやりたい場合には
バージョン古い方(5.1)だと`Workflow`でかこんでforeachに -parallelオプションをつける。
https://trend-desk.com/archives/1372
https://docs.microsoft.com/ja-jp/powershell/module/psworkflow/about/about_parallel?view=powershell-5.1
```
PS C:\Users\komiyay> Workflow CallTestWorkflow()
>> {
>>   $computers_suffix=@('01','02','03','04','05','06','07','08','09','10')
>>   $computers_prefix="mytestvm"
>>   foreach -parallel ( $num in $computers_suffix )
>>   {
>>       $computer = ($computers_prefix + $num)
>>       Write-Output "$((Get-Date).ToString("MM/dd_HH:mm:ss")) $computer"
>>       Start-Sleep -Seconds 1
>>   }
>> }
PS C:\Users\komiyay> CallTestWorkflow
04/18_15:57:03 mytestvm10
04/18_15:57:03 mytestvm09
04/18_15:57:03 mytestvm08
04/18_15:57:03 mytestvm07
04/18_15:57:03 mytestvm06
04/18_15:57:03 mytestvm05
04/18_15:57:03 mytestvm04
04/18_15:57:03 mytestvm03
04/18_15:57:03 mytestvm02
04/18_15:57:03 mytestvm01
```
※数多いと並列じゃないと処理時間がかさんで所定の時間に間に合わないかもというケースにおススメ。

バージョン新しめであれば（PowerShell 7 Preview.3から）以下のように`ForEach-Object -Parallel`がつかえる
CloudShellではつかえたがローカルPCで使えなかったりした。Automationだと新しくて7.1なのでバージョン足らないかも）
https://dev.classmethod.jp/articles/about-powershell-foreach-object-parallel/
```
PS /home/komiya> $computers_suffix=@('01','02','03','04','05','06','07','08','09','10')
PS /home/komiya> $computers_prefix="mytestvm"
PS /home/komiya> echo $computers_suffix | ForEach-Object -Parallel {
>>     
>>     Write-Output "$((Get-Date).ToString("MM/dd_HH:mm:ss")) $using:computers_prefix$_"
>>     Start-Sleep -Seconds 2 
>> }
04/18_04:12:19 mytestvm01
04/18_04:12:19 mytestvm02
04/18_04:12:19 mytestvm03
04/18_04:12:19 mytestvm04
04/18_04:12:19 mytestvm05
04/18_04:12:21 mytestvm06
04/18_04:12:21 mytestvm07
04/18_04:12:21 mytestvm08
04/18_04:12:21 mytestvm09
04/18_04:12:21 mytestvm10
PS /home/komiya> 
```
並列じゃない場合との比較（↑↓出力結果の秒数のところに注目してください）
```
PS C:\Users\komiyay> $computers_suffix=@('01','02','03','04','05','06','07','08','09','10')
PS C:\Users\komiyay> $computers_prefix="mytestvm"
PS C:\Users\komiyay> foreach ( $num in $computers_suffix )
>> {
>>     $computer = ($computers_prefix + $num)
>>     Write-Output "$((Get-Date).ToString("MM/dd_HH:mm:ss")) $computer"
>>     Start-Sleep -Seconds 1
>> }
04/18_13:04:19 mytestvm01
04/18_13:04:20 mytestvm02
04/18_13:04:21 mytestvm03
04/18_13:04:22 mytestvm04
04/18_13:04:23 mytestvm05
04/18_13:04:24 mytestvm06
04/18_13:04:25 mytestvm07
04/18_13:04:26 mytestvm08
04/18_13:04:27 mytestvm09
04/18_13:04:28 mytestvm10
```

### 四則演算

```
$storage_space_free_mb = ([int]$total - [int]$used)
```
### キーが一致する場合の列の追加処理（SQLのJOINライクな処理）

```
$metric_obj_mi3 = foreach ( $row in $metric_obj_mi1 )
{
    $timegenerated = $row.TIME
    $total = $row.reserved_storage_mb
    $used = $metric_obj_mi2 | Where-Object {$_.TIME -eq $timegenerated} | Select-Object -ExpandProperty storage_space_used_mb
    $storage_space_free_mb = ([int]$total - [int]$used)
    [pscustomobject]@{TIME = $timegenerated; storage_space_free_mb = $storage_space_free_mb; storage_useage_percent = $storage_useage_percent;} | Write-Output
}
```
余談でLinuxでやる場合[joinコマンド](https://qiita.com/smallpalace/items/10b3063176f81c99fdbf#join)というのがあってループは不要で便利です。

### 特定の値を出す
イコールの場合

```
$used = $metric_obj_mi2 | Where-Object {$_.TIME -eq $timegenerated} | Select-Object -ExpandProperty storage_space_used_mb
```

[Where-Object](https://docs.microsoft.com/ja-jp/powershell/scripting/samples/removing-objects-from-the-pipeline--where-object-?view=powershell-7.1)は条件の定義で絞る、Select-Objectは特定のカラムで絞る、Select-Stringはgrepのようなもの（たぶん操作したい対象の型によって使い分けたほうが効率がよさそうでSelect-StringをObjectに対してつかうと出力の型がStringになりそう）

部分一致検索

```
$insobj1_az = $insobj1_lm|Where-Object {$_.content_ja -like "*Azure*"}
```

カウントして表作ってファイルに出力

```
$insobj1_az2 = Import-Csv -encoding default -Path $outfile3 
$stat_comp = ($insobj1_az2 |Where-Object status_name_ja -eq "完了").Count
$stat_l2 = ($insobj1_az2 |Where-Object status_name_ja -eq "受付済").Count
$stat_new = ($insobj1_az2 |Where-Object status_name_ja -eq "新規").Count
$stat_wip = ($insobj1_az2 |Where-Object status_name_ja -eq "対応中").Count

$ins_counts = [PSCustomObject]@{ステータス = "完了"; 件数 = "$stat_comp";},
[PSCustomObject]@{ステータス = "受付済"; 件数 = "$stat_l2";},
[PSCustomObject]@{ステータス = "新規"; 件数 = "$stat_new";},	
[PSCustomObject]@{ステータス = "対応中"; 件数 = "$stat_wip";}

$savePath_inscounts = Join-Path $workdir "i-filename2-incident_counts.csv"
$ins_counts |Export-Csv -NoTypeInformation -encoding default $savePath_inscounts
```
https://stackoverflow.com/questions/11526285/how-to-count-objects-in-powershell
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/import-csv?view=powershell-7.1
https://qiita.com/nijinagome/items/8daf6d116892eee14cfc
### 変数の中身を置換する

```
$metric2 = $metric.Replace('/','Per').Replace(' ','')

```

### SortしてUniqにする
```
$insobj1_azid = $insobj1_az|select-object incident_mng_nm|sort incident_mng_nm -Unique
```
[Sort-Object_Tips](https://www.vwnet.jp/Windows/PowerShell/2017032901/Sort-Object_Tips.htm)
### URLからファイルをとってくる

```
$DownloadUri = 'https://www.microsoft.com/en-us/download/confirmation.aspx?id=56519'
$DownloadPage = Invoke-WebRequest -Uri $DownloadUri -UseBasicParsing
$JsonFileUri = ($DownloadPage.RawContent.Split('"') -like 'https://*ServiceTags_Public*')[0]
$Response = Invoke-WebRequest -Uri $JsonFileUri -UseBasicParsing
```

### トライキャッチで存在を確認しエラー処理

```
try{
    $ResponseCheck = Invoke-WebRequest -Uri $DownloadUri -UseBasicParsing
}catch{
    $ErrorMsg = "Automation [ Check_Azure_IP_Range ] 実行中に IP-Range ページが見つかりませんでした。URLを確認してください。<br>$DownloadUri"
    PostTeams $TeamsUrl $ErrorMsg
    throw "URLは無効です。実行を中止します。"
}
```
[https://docs.microsoft.com/ja-jp/powershell/scripting/learn/deep-dives/everything-about-exceptions?view=powershell-7.1](https://docs.microsoft.com/ja-jp/powershell/scripting/learn/deep-dives/everything-about-exceptions?view=powershell-7.1)

### 文字コード指定
Teamsに送り付けるとき↓

```
$enc = [System.Text.Encoding]::GetEncoding('ISO-8859-1')
$utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($msg)
$body = ConvertTo-JSON @{
text = $enc.GetString($utf8Bytes);
```
CSVがSJISだったとき↓

```
$OutputEncoding = [Console]::OutputEncoding
$insobj21 = Import-Csv -encoding default -Path $importPath4| Where-Object {$_.incident_mng_nm -eq $insid}
$insobj21|Export-Csv -NoTypeInformation -encoding default -Append $outfile3
```
※SJISを明示的に指定するのが指定できる文字コードの中に入ってなくて無理だったのでコンソールの文字コードを指定してCSVを取り込みと出力時にデフォルトを指定して文字化けしなくしてる。
[文字コード解説](https://www.fenet.jp/infla/column/engineer/powershell%E3%81%AE%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E8%A7%A3%E8%AA%AC%EF%BC%81%E6%96%87%E5%AD%97%E5%8C%96%E3%81%91%E3%81%A7%E5%9B%B0%E3%82%89%E3%81%AA/#BOM%E7%84%A1%E3%81%97UTF-8%E3%82%84Shift_JIS%E3%81%A7%E5%87%BA%E5%8A%9B)
http://taeisheauton4programming.blogspot.com/2018/07/powershell-6shiftjiscsvimport-csv.html
### エクセルからCSVにする
エクセルから単にCSVにする

```
$workdir = (Convert-path ~/$lastmonth)
mv ~/Downloads/i-filename1.xlsx $workdir/
cd $workdir
$tmp2 = "$workdir/i-filename1.xlsx"
$savePath = Join-Path $workdir "i-filename1.csv"
$objExcel = New-Object -ComObject Excel.Application
$objworkbook=$objExcel.Workbooks.Open($tmp2)
$objworkbook.SaveAs($savepath,6)  ##6がCSVらしい
$objworkbook.Close($false)
```
[PowerShellによるExcelデータからCSVデータへのコンバート](https://faq.mypage.otsuka-shokai.co.jp/app/answers/detail/a_id/300853/~/%5B%E3%81%9F%E3%82%88%E3%82%8C%E3%83%BC%E3%82%8B%E3%81%8A%E5%BD%B9%E7%AB%8B%E3%81%A1%E6%83%85%E5%A0%B1%5Dpowershell%E3%81%AB%E3%82%88%E3%82%8Bexcel%E3%83%87%E3%83%BC%E3%82%BF%E3%81%8B%E3%82%89csv%E3%83%87%E3%83%BC%E3%82%BF%E3%81%B8%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%90%E3%83%BC%E3%83%88)

エクセルのままカラム一個消す

```
#mkdir ~/$lastmonth
$workdir = (Convert-path ~/$lastmonth)
cd $workdir
$tmp = "$workdir/w-filename2.xlsx"
$savePath = Join-Path $workdir "w-filename2.csv"
$objExcel = New-Object -ComObject Excel.Application
$objworkbook=$objExcel.Workbooks.Open($tmp)
$sheet = $objExcel.Worksheets.Item(1)
$sheet.Columns.item(44).Delete()    ##カラム名が重複する不要な行削除
$objworkbook.SaveAs($savepath,6)
$objworkbook.Close($false)
```
※CSVインポートするときにカラム名が重複してるとエラーで読めない。
https://stackoverflow.com/questions/45999262/powershell-import-csv-without-headers-and-where-object/45999933
https://letspowershell.blogspot.com/2015/06/powershellexcel_55.html
カラム名重複してないならヘッダ消すのも可能
`$list | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1 | Set-Content "no_title_csv.csv"`
https://teratail.com/questions/272241