WVDのVM料金を節約する

弊社で一般職、管理職向けにVDIとしてWVDを導入する動きがあってその運用管理の手伝いをすることになりました。

真っ先に私のタスクになったのはVMの節約
調べると
https://docs.microsoft.com/ja-jp/azure/automation/automation-solution-vm-management
とか
https://365cloud.jp/wvd%E3%82%92%E6%A5%AD%E5%8B%99%E6%99%82%E9%96%93%E4%B8%AD%E3%81%AE%E3%81%BF%E8%B5%B7%E5%8B%95%E3%81%95%E3%81%9B%E3%82%8B%EF%BC%88%E3%82%B9%E3%82%B1%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E8%87%AA/
とか
出てきたのでこれは楽勝と思っていたのですが、
実行するとこんなエラーが

``` powershell

Add-AzureRmMetricAlertRule : Exception type: ErrorResponseException, Message: You cannot create or modify classic metric alerts for this subscription as this subscription X-x-x-x-x is being migrated or has been migrated to use new metric alerts. Learn more - aka.ms/alertclassicretirement, Code: BadRequest, Status code:BadRequest, Reason phrase: Bad Request At line:192 char:34 + ... Add-AzureRmMetricAlertRule -Name $NewAlertName ` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : CloseError: (:) [Add-AzureRmMetricAlertRule], PSInvalidOperationException + FullyQualifiedErrorId : Microsoft.Azure.Commands.Insights.Alerts.AddAzureRmMetricAlertRuleCommand

```

昨年クラシックアラートが廃止になった影響がでました。
上で紹介した公式ページの説明だと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80457/b3d44bef-8358-fea7-8239-8b49e3407dc1.png)

私が着手したの3月入ってから、目と鼻の差で実現不可能に、
しかし、最新のAzモジュールに差し替えて、Poweshellも書き換えれば実行できるはず
ということでいろいろ調べましたが、結局公式のAzurePowershellのリファレンスを読み漁ってトライ&エラーで設定したので差し替えたスクリプトを残しておこうと思います。

今回はWVDのVMの節約のために導入しましたが、通常のVMの節約でも使用できるので、興味がある方は設定してみてください。
また、2020年3月18日時点の情報ですので、今後該当のソリューションが更新された場合は動作の保証はできませんのでご了承ください。

# 手順

まずは
[Azure Automation でのピーク時間外 VM 起動/停止ソリューション](https://docs.microsoft.com/ja-jp/azure/automation/automation-solution-vm-management)
の作業を行います。
必要に応じて
[365クラウド](https://365cloud.jp/wvd%E3%82%92%E6%A5%AD%E5%8B%99%E6%99%82%E9%96%93%E4%B8%AD%E3%81%AE%E3%81%BF%E8%B5%B7%E5%8B%95%E3%81%95%E3%81%9B%E3%82%8B%EF%BC%88%E3%82%B9%E3%82%B1%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E8%87%AA/)の記事の設定に変更してもOKです。

## 変更点１
通常の状態の設定が完了した後は、
まず、Azモジュールを追加します。
[Az モジュールをインポートする](https://docs.microsoft.com/ja-jp/azure/automation/az-modules#import-the-az-modules)
を参考にして、

追加するモジュールは
Az.Accounts
Az.Automation
Az.Compute
Az.Monitor
Az.Resources
だけです。

## 変更点2
使用するRUNBOOKのxx-AzureRMxxをxx-Azxxに書き換えてパブリッシュします。
私が使用したのは365クラウドの記事を参考にしたので定時起動、夜使用していなかったら停止かつクラシックは必要ないので、

定時起動用の
ScheduledStartStop_Parent
ScheduledStartStop_Child
と
使用していなかったら停止用の
AutoStop_CreateAlert_Parent
AutoStop_CreateAlert_Child
AutoStop_VM_Child
のRUNBOOKを変更しました。

## 変更点3

そして今回の修正の肝となるクラシックアラートの廃止の部分を変更します。
対象RUNBOOKは、
AutoStop_CreateAlert_Child
です。

``` 変更後のAutoStop_CreateAlert_Child.ps1

param(
    $VMObject,
    [string]$AlertAction,
    [string]$WebhookUri
)


#-----Function to generate unique alert name-----
function Generate-AlertName 
{
    param (
        [string] $OldAlertName , 
        [string] $VMName
    )
     
    [string[]] $AlertSplit = $OldAlertName -split "-"
    [int] $Number =$AlertSplit[$AlertSplit.Length-1]
    $Number++
    $Newalertname = "Alert-$($VMName)-$Number"
    return $Newalertname
}

# ------------------Execution Entry point ---------------------

[string] $FailureMessage = "Failed to execute the command"
[int] $RetryCount = 3 
[int] $TimeoutInSecs = 20
$RetryFlag = $true
$Attempt = 1
do
{
    Write-Output "Logging into Azure subscription using Az cmdlets..."
    #-----L O G I N - A U T H E N T I C A T I O N-----
    $connectionName = "AzureRunAsConnection"
    
    try
    {
        # Get the connection "AzureRunAsConnection "
        $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName         

        Add-AzAccount `
            -ServicePrincipal `
            -TenantId $servicePrincipalConnection.TenantId `
            -ApplicationId $servicePrincipalConnection.ApplicationId `
            -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint 
        
        Write-Output "Successfully logged into Azure subscription using Az cmdlets..."

        $RetryFlag = $false
    }
    catch 
    {
        if (!$servicePrincipalConnection)
        {
            $ErrorMessage = "Connection $connectionName not found."

            $RetryFlag = $false

            throw $ErrorMessage
        }

        if ($Attempt -gt $RetryCount) 
        {
            Write-Output "$FailureMessage! Total retry attempts: $RetryCount"

            Write-Output "[Error Message] $($_.exception.message) `n"

            $RetryFlag = $false
        }
        else 
        {
            Write-Output "[$Attempt/$RetryCount] $FailureMessage. Retrying in $TimeoutInSecs seconds..."

            Start-Sleep -Seconds $TimeoutInSecs

            $Attempt = $Attempt + 1
        }   
    }
}
while($RetryFlag)

#---------Read all the input variables---------------
$SubId = Get-AutomationVariable -Name 'Internal_AzureSubscriptionId'

#-----Prepare the inputs for alert attributes-----
$threshold = Get-AutomationVariable -Name 'External_AutoStop_Threshold'
$metricName = Get-AutomationVariable -Name 'External_AutoStop_MetricName'
$timeWindow = Get-AutomationVariable -Name 'External_AutoStop_TimeWindow'
$condition = Get-AutomationVariable -Name 'External_AutoStop_Condition' # Other valid values are LessThanOrEqual, GreaterThan, GreaterThanOrEqual
$description = Get-AutomationVariable -Name 'External_AutoStop_Description'
$timeAggregationOperator = Get-AutomationVariable -Name 'External_AutoStop_TimeAggregationOperator'
$webhookUri = Get-AutomationVariable -Name 'Internal_AutoSnooze_WebhookUri'

try
{
    Write-Output "Runbook execution started..." 
    $ResourceGroupName =$VMObject.ResourceGroupName
    $Location = $VMObject.Location
    Write-Output "Location...$($VMObject.Location)" 
    Write-Output "getting status..." 

    $VMState = (Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMObject.Name -Status -ErrorAction SilentlyContinue).Statuses.Code[1] 	
    $resourceId = "/subscriptions/$($SubId)/resourceGroups/$ResourceGroupName/providers/Microsoft.Compute/virtualMachines/$($VMObject.Name.Trim())"

    Write-Output "Processing VM ($($VMObject.Name))"
    Write-Output "Current VM state is ($($VMState))"

####変更箇所
    #$actionWebhook = New-AzAlertRuleWebhook -ServiceUri $WebhookUri
    $actionWebhook = Get-AzActionGroup -Name "WebHookToAutomation" -ResourceGroupName $ResourceGroupName
    if($null -eq $act ){
        $webhookReceiver = New-AzActionGroupReceiver -Name 'webhookReceiver' -WebhookReceiver -ServiceUri $WebhookUri
        $actionWebhook = Set-AzActionGroup -Name "WebHookToAutomation" -ResourceGroupName $ResourceGroupName -ShortName "Hook" -Receiver $webhookReceiver
    }
    
    $Criteria = New-AzMetricAlertRuleV2Criteria -MetricName $metricName -TimeAggregation $timeAggregationOperator -Operator $condition -Threshold $threshold
####変更箇所
    $NewAlertName ="Alert-$($VMObject.Name)-1"
                                             
    if($AlertAction -eq "Disable")
    {
####変更箇所
        $ExVMAlerts = Get-AzMetricAlertRuleV2 -ResourceGroup $VMObject.ResourceGroupName -ErrorAction SilentlyContinue
####変更箇所
        if($ExVMAlerts -ne $null)
        {
            Write-Output "Checking for any previous alert(s)..." 
            #Alerts exists so disable alert
            foreach($Alert in $ExVMAlerts)
            {
                                        
                if($Alert.Name.ToLower().Contains($($VMObject.Name.ToLower().Trim())))
                {
                    Write-Output "Previous alert ($($Alert.Name)) found and disabling now..." 

####変更箇所
                    Add-AzMetricAlertRuleV2 `
                        -Name $Alert.Name `
                        -ResourceGroupName $ResourceGroupName `
                        -WindowSize $timeWindow `
                        -Frequency $timeWindow `
                        -TargetResourceScope $resourceId `
                        -TargetResourceType "Microsoft.Compute/virtualMachines" `
                        -TargetResourceRegion $Alert.Location `
                        -Description $description `
                        -Severity 4 `
                        -ActionGroupId $actionWebhook.Id `
                        -Condition $Criteria -DisableRule
####変更箇所

                    Write-Output "Alert ($($Alert.Name)) Disabled for VM $($VMObject.Name)"
                                
                }
            }
                       
        }
    }
    elseif($AlertAction -eq "Create")
    {
        #Getting ResourcegroupName and Location based on VM         
        if (($VMState -eq 'PowerState/running') -or ($VMState -eq 'ReadyRole'))
        {                     
            try
            {   
                Write-Output "Creating alerts..." 
####変更箇所
                $VMAlerts = Get-AzMetricAlertRuleV2 -ResourceGroup $ResourceGroupName -ErrorAction SilentlyContinue
####変更箇所

                #Check if alerts exists and take action
                if($VMAlerts -ne $null)
                {
                    Write-Output "Checking for any previous alert(s)..." 
                    #Alerts exists so delete and re-create the new alert
                    foreach($Alert in $VMAlerts)
                    {
                            
                        if($Alert.Name.ToLower().Contains($($VMObject.Name.ToLower().Trim())))
                        {
                            Write-Output "Previous alert ($($Alert.Name)) found and deleting now..." 
                            #Remove the old alert
####変更箇所
                            Remove-AzMetricAlertRuleV2 -Name $Alert.Name -ResourceGroup $ResourceGroupName
####変更箇所
                
                            #Wait for few seconds to make sure it processed 
                            Do
                            {
                                #Start-Sleep 10    
####変更箇所
                                $GetAlert=Get-AzMetricAlertRuleV2 -ResourceGroup $ResourceGroupName -Name $Alert.Name -ErrorAction SilentlyContinue
####変更箇所
                                    
                            }
                            while($GetAlert -ne $null)
                
                            Write-Output "Generating a new alert with unique name..."
                            #Now generate new unique alert name
                            $NewAlertName = Generate-AlertName -OldAlertName $Alert.Name -VMName $VMObject.Name               
                
                        }
                    }
                }
            
                #Alert does not exist, so create new alert
                Write-Output $NewAlertName                
            
                Write-Output "Adding a new alert to the VM..."
            
####変更箇所
                Add-AzMetricAlertRuleV2 `
                    -Name $NewAlertName `
                    -ResourceGroupName $ResourceGroupName `
                    -WindowSize $timeWindow `
                    -Frequency $timeWindow `
                    -TargetResourceScope $resourceId `
                    -TargetResourceType "Microsoft.Compute/virtualMachines" `
                    -TargetResourceRegion $location `
                    -Description $description `
                    -Severity 4 `
                    -ActionGroupId $actionWebhook.Id `
                    -Condition $Criteria
####変更箇所
                        
                Write-Output  "Alert Created for VM $($VMObject.Name.Trim())"    
            }
            catch
            {
                Write-Output "Error Occurred"   
                Write-Output $_.Exception
            }

        }
        else
        {
            Write-Output " $($VM.Name) is De-allocated"
        }
    }
}
catch
{
    Write-Output "Error Occurred"   
    Write-Output $_.Exception
}  

```

以上が変更点です。