1
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PowerShell備忘録

Last updated at Posted at 2021-05-06

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

モジュールのインポート

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

データ型を変換する

#関数をつかう
$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

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を複数書いた

ループ処理する

以下は特定の2つの配列変数に含まれる値に絞ってそれをファイル名にして出力させるために入れ子にした

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
}

並列処理

1つずつのループではなく時短で並列でやりたい場合には
バージョン古い方(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コマンドというのがあってループは不要で便利です。

特定の値を出す

イコールの場合

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

Where-Objectは条件の定義で絞る、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

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

文字コード指定

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を取り込みと出力時にデフォルトを指定して文字化けしなくしてる。
文字コード解説
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データへのコンバート

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

#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

1
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?