PowerShellで情報を管理する話

# ご挨拶 & はじめに
はじめまして、さくらインターネットで主にVPS周りの運用を担当している荒井と申します。
酔った勢いのままにアカウント登録し、qiita初投稿！！酔った勢いは大事だなー:innocent:

突然ですが鬼滅の刃が大好きです！アニメ版も好きですが漫画から入りました。
最新18巻も泣けるし、アニメ版の19話「ヒノカミ」でも泣ける良作、一度見ていただければ！

そしてこれを皆さんが読んでいるときには親知らず抜いた後でヒーコラ痛がっているでしょう・・・
みなさんは良いクリスマスイブを・・・

ということで変な暴露をしたところで中身に入らせてください。

# Windows環境からメモリアラートが届く
弊社の提供しているサービスである「さくらのVPS for Windows」では、一部のホストにおいて
Windows + Hyper-V 環境でVMを管理/提供しています。
そのホストの数台から、メモリの監視しきい値を超えたよーというメールが。。。

原因をタスクマネージャー上から確認したところ、以下の傾向が目につきました。
1. 稼働しているVMが多い
2. やたらとPowerShellのプロセスがある
3. コマンドラインを見ると、グラフ描画のためのコマンドを実行している

どうやらさくらのVPSで提供しているコントロールパネルの、VMが使用しているリソース情報を収集する
スクリプトが悪さをしていそう。

これもさくらで作成したスクリプトなんですが、スクリプト内部で実行してるコマンドを確認したところ
**VMの描画に必要な情報を、VM毎に毎回**取得していました。
例えばこんな感じ

```
$VMId   = (Get-VM "$vm").VMid
$NicId = (Get-VMNetworkAdapter "$vm" -Name NIC1).AdapterId
```
この情報の取得が重なってしまって、まー返ってこない返ってこない :innocent::innocent::innocent:


#### いきなりちょっとしたTips
タスクマネージャーで[詳細]タブを選択し、項目のところを右クリックすると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545026/a996ac10-d12e-2806-c5a3-21612348d4a5.png)
な感じで列の選択ができます。
そこから[コマンドライン]をチェックすると、実行されているコマンドラインの詳細が見えます。
同じPowerShellでも何を実行しているか？が判断できるようになるのでぜひお試しあれ～

もしくはPowerShellでも・・・

```
Get-WmiObject Win32_Process | ? Name -eq powershell.exe | Select-Object CommandLine,Processid
```
Get-WmiObjectからWindowsのプロセスを取得、プロセス名がpowershell.exeのものを検索し、
そのオブジェクトのプロパティであるCommandLineとProcessidを取得する、という処理になってます。
※表示が切れちゃうときはパイプから`Format-List`に渡したりすると良いかもです。

# 結局問題はどこにあったの？？
問題点は下記に集約されていました。

・ 数分に一回データを取得していた
・ VM数が多いため、データの取得に時間がかかっていた
・ 結果としてプロセスが滞留し、PowerShellで利用するメモリ(一つ数十MB)が増加していた
・ 今まではVM数がそこまでではなかったため、問題が顕在化しなかった

そのため、これに対するアプローチを考えて修正を行った内容が以下になります。

# 必要な情報の取得回数を少なくする
そもそもPowerShellって同じようなコマンドレットが多いんですよね。。。 返ってくる速度も早くないし。。。
なので、まとめて情報を取得できそうな部分はまとめて取っておくことにしました。
※変数の命名規則とかは適当ですのでご容赦ください :bow: :bow:

```powershell
Get-VM | Get-Member
Get-VMNetworkAdapter -VMName * | Get-Member
```
Get-Memberを利用して、Get-VMXXXで個別に情報を取得してた項目がプロパティから取得可能か確認し、

```powershell
$VM_obj = Get-VM
$VM_Nw_obj = Get-VMNetworkAdapter -VMName *
```
で一度全てのオブジェクト情報を変数として管理しました。
こうすることにより、VM毎に毎回取得していた情報を、まとめて管理する事ができました。

ただし、これは**取得した時点と情報を利用する時点で差異が発生しないもの**にのみ有効ですので、そこだけ注意です！

# 他のスクリプトでも利用できるようにする
今回はグラフ描画のスクリプトで事象が発生していましたが、今後他のスクリプトを動かす際にも参照するかもしれません。
なので必要な情報は予め取得しておき、どこからでも参照できるようにしておくことで汎用性を持たせちゃいましょう。
※ここもリアルタイムで必要な情報は除外しています。

取得したデータを管理するのにわざわざSQLとか使いたくないな、、、いいコマンドレットないかな、、、
と調べてたらありました！ `Export-Csv` と `Import-Csv` が！

このコマンドレットのいいところは、CSV形式でファイルに出力できるので管理が楽なのと、
自分に必要な情報をオブジェクト化してexportすることで、利用する際にその形のままimportできることです。
※またまた変数の命名規則とかは適当ですのでご容赦ください :bow: :bow: :bow: 

```PowerShell:getvminfo.ps1
$Csv_File = 'C:\Path\to\vminfo.csv'
$VM_obj = Get-VM XXX*
$VM_Nw_obj = Get-VMNetworkAdapter XXX*
$VM_Info_obj = @()

foreach ($vm in $VM_obj.VMname) {
    $VM_Info_obj += New-Object -TypeName PSObject -Property @{
        VMName = $vm
        VMID = ($VM_obj | Where-Object { $_.VMName -match "$vm" }).VMId.Guid
        Status = ($VM_obj | Where-Object { $_.VMName -match "$vm" }).State
        CPU = ($VM_obj | Where-Object { $_.VMName -match "$vm" }).ProcessorCount
        Mem = ($VM_obj | Where-Object { $_.VMName -match "$vm" }).MemoryStartup / 1048576
        HDD = (($VM_obj | Where-Object { $_.VMName -match "$vm" }).HardDrives.Path | Get-VHD).Size / 1073741824
        BW = ($VM_Nw_obj | Where-Object { $_.VMName -match "$vm" -and $_.Name -match "NIC1"}).BandwidthSetting.MaximumBandwidth / 1000000
        VLAN = ($VM_Nw_obj | Where-Object { $_.VMName -match "$vm" -and $_.Name -match "NIC1"}).VlanSetting.AccessVlanId
        NID = ($VM_Nw_obj | Where-Object { $_.VMName -match "$vm" -and $_.Name -match "NIC1"}).AdapterId
        DVD = ($VM_obj | Where-Object { $_.VMName -match "$vm" }).DVDDrives.Path
    }
}

$VM_Info_obj | Export-Csv -Path $Csv_File -Encoding UTF8
```
っと上記のスクリプトを定期的に実行してVMの状態をCSV形式でexportさせ、

```PowerShell
$VMInfo = Import-Csv 'C:\Path\to\vminfo.csv'
$VMId  = ($VMInfo | Where-Object { $_.VMName -match "$vm" }).VMID
$NicId = ($VMInfo | Where-Object { $_.VMName -match "$vm" }).NID
```
別のスクリプトでこのようにimport、プロパティを指定することで情報を参照しやすくしました。

このコマンドレットのおかげで、CSVファイルのカンマを数えて何番目の値、のような曖昧な指定での管理が
必要なくなるため、今後必要な情報に変更が発生した際も、追加/削除が簡単に行えて楽ちんです！

# 導入してどうなったか
この対応によってグラフ描画スクリプトの動作速度が改善し、以前のようにプロセスが滞留することはなくなりました  :smile: 
結果、その後メモリアラートの姿を見たものはいない・・・状態になり、平穏な毎日を過ごしています。

# おわりに
ちょっと拙いとは思いますがいかがでしょうか？？PowerShellの実行は結構メモリを食っちゃうので、
より効率的に、より管理しやすいようにしていくことが大切だと今回勉強できました :beach_umbrella: 

`Get-Member`や`Export-Csv` 、 `Import-Csv`を利用して、ホストやVMの管理を検討していただく際の一助となれば嬉しいです！

それでは皆さん年末年始は鬼滅の刃をよろしくお願いします！！！(関係ない。。。)