【Azure】Azure Automation を利用して VM を自動起動 / 停止する

# はじめに

利用しているデバイスの調子が悪く、再起動すれば直った！みたいなことは普段でもよくあります。

Azure の仮想マシン (VM) を利用していても同じようなことがあります。

ということで、定期的に再起動する処理を Azure Automation を利用して実装したいと思います。

# 環境
- macOS: `15.0.1`
- PowerShell: `7.4.0`

# 実装にあたって調べたこと

## VM 標準機能で提供されていないの？

設定したスケジュールで VMを自動で停止する機能はあるようですが、自動で起動する設定はないようです。

https://learn.microsoft.com/ja-jp/azure/virtual-machines/auto-shutdown-vm?tabs=portal

## **Start/Stop VMs v2 で実装できる**

公式では Azure Function を利用して自動停止と再起動を実装する方法が紹介されています。

※ Azure Automation を利用する **Start/Stop VMs v1 は非推奨になり、現在新規では利用できないようです。**

この方法だと複数のサブスクリプションにまたがる Azure 仮想マシンを開始または停止することができたり、色々と便利な機能があるようですが、Azure Function をはじめ、ストレージアカウントや Application Insights といった関連リソースが作成されます。

使いこなせばすごく便利なことが多いのだろうと思うのですが、シンプルに再起動(停止と起動)だけを実装したい人（私とか）には、いろんなリソースが作られるのはなんか嫌だな。と感じたので、勉強兼ねても独自でやってみることにしました。

https://learn.microsoft.com/ja-jp/azure/azure-functions/start-stop-vms/overview

# 構成図とコンポーネントの役割

![azure-vm-auto-start-stop-template.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/e2ebc3a7-16f7-8c08-3bcb-02ad67abdf15.png)


| No | リソース | 役割 |
| --- | --- | --- |
| 1 | Azure Automation Account | Runbookの実行を管理する基盤。機能の一部として、ジョブスケジュールを提供し、仮想マシンの起動・停止のプロセスを自動化する。 |
| 2 | Runbook | 仮想マシンの自動起動・停止のロジックを定義したスクリプトを実行する。スケジュールやトリガーに応じてAutomation Accountから実行される。 |
| 3 | System Assigned Identity | 適切な権限を付与し、仮想マシンを操作できるようにする |
| 4 | Virtual Machine | 自動起動・停止の対象となる仮想マシン。 |

※仮想マシンはここでは作成しません。

# リソースの作成

::: note warn
注意
Azure サービスを利用するため、コストが発生する場合があります。
コストに関しては自己責任でお願いします。
:::

## 前提

- Azure アカウントを取得済であること。
- 作業ユーザーに、Automation をはじめとしたリソースを作成するリソースグループスコープで`共同作成者`ロールが割り当てられていること。
- 作業ユーザーに、VM が存在するリソースグループスコープで`所有者`or `ユーザーアクセス管理者` ロールが割り当てられていること。
- ローカルPC に `Azure CLI` がインストールされていること。

## カスタムロールの作成

VM の電源操作を行うにあたって、Automation Account で有効にする マネージドID に対して、適切な権限を割り当てる必要があります。

共同作成者などを利用すれば必要な操作はできますが、できるだけ最小権限でロールを割り当てるのが望ましいです。

そこで、カスタムロールを利用します。VM の電源操作に関するカスタムロール作成手順については下記記事で紹介されているので、参考にしながら作成してください。

https://jpaztech.github.io/blog/vm/rbac-vm-start-stop-restart/

カスタムロールを作成したら、ロールの名前と定義 ID を控えるようにしてください。
後続の作業で利用します。

### ロール名と定義 ID の確認場所

[アクセス制御(IAM)] > [役割] > [<作成したカスタムロール>のビュー] > [JSON]

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/0e2cf369-2341-de71-2cc9-3bc2dcb4396f.png)


## Bicep テンプレート

Automation Account、Runbook を Bicep を利用して作成します。
マネージドID は システム割り当てを利用し、ロール割り当ても Bicep で実施します。
Runbook の種類は、 PowerShell 7.2 にしました。

下記のディレクトリ構造になるように、Bicepファイルを作成してください。

```text: ディレクトリ構成
.(root)
├── modules
│   ├── automationAccounts.bicep
│   ├── jobSchedules.bicep
│   ├── roleAssignments.bicep
│   ├── runbooks.bicep
│   └── schedules.bicep
├── main.bicep
└── main.bicepparam
```

```bicep: ./main.bicep
//
// Bicep template that implements automatic VM start/stop using Azure Automation.
// Created by nownaka.
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Resource deployment region.')
param location string = resourceGroup().location

@description('Application name.')
param appName string
@description('Environment name.')
param environment string
param suffix string?
@description('Base name of the resource. Used if there is no specification for each resource name.')
param resourceBaseName string = join(split(join(concat([appName, environment], empty(suffix) ? [] : [suffix]), '-'), '-'), '-')

@description('Resource name of Azure Automation Account.')
param automationAccountName string = 'aa-${resourceBaseName}'
@description('Identity type of Azure Automation Account')
@allowed(['None', 'SystemAssigned','SystemAssigned, UserAssigned', 'UserAssigned'])
param identityType string
@description('Name ofAccount SKU.')
@allowed([
  'Basic'
  'Free'
])
param automationAccountSkuName string = 'Free'

type scheduleConfig = {
  description: string?
  expiryTime: string?
  frequency: 'Day' | 'Hour' | 'Minute' | 'Month' | 'OneTime' | 'Week'
  interval: int
  @description('format: yyyy-MM-ddTHH:mm:ss+09:00')
  startTime: string
  timeZone: string?
}
@description('The confings for VM-Start Schedules.')
param scheduleConfig_start scheduleConfig
@description('The confings for VM-Stop Schedules.')
param scheduleConfig_stop scheduleConfig

@description('Role definition to assign.')
param roleDifinitions { name: string, id: string }[] = [
  {
    name: '仮装マシン電源オペレーター'
    id: 'cfb29df2-1cb5-43e2-8414-e9eecc6572c3'
  }
]
@description('Name of the resource group containing the VM')
param vmResourceGroupName string = resourceGroup().name

// --------------------------------------------------------------------------------
// Modules
// --------------------------------------------------------------------------------
/* Azure Automation */
// Account
@description('Azure Automation Account.')
module automationAccount './modules/automationAccounts.bicep' = {
  name: 'Deploy-AutomationAccount'
  params: {
    automationAccountLocation: location
    automationAccountName: automationAccountName
    identityTyoe: identityType
    skuName: automationAccountSkuName
  }
}

// Runbooks
@description('The confings for runbooks.')
var _runbookConfigs = [
  {
    name: 'Start-AzureVMs'
    runbookType: 'PowerShell72'
    publishContentLink: {
      uri: uri('https://raw.githubusercontent.com/nownaka/azure-vms-auto-start-stop-template/refs/heads/main/runbook/', 'Start-AzureVMs.ps1')
    }
  }
  {
    name: 'Stop-AzureVMs'
    runbookType: 'PowerShell72'
    publishContentLink: {
      uri: uri('https://raw.githubusercontent.com/nownaka/azure-vms-auto-start-stop-template/refs/heads/main/runbook/', 'Stop-AzureVMs.ps1')
    }
  }
]

@description('Azure Automation Runbook.')
module runbooks './modules/runbooks.bicep' = [for config in _runbookConfigs: {
  name: 'Deploy-Runbook-${config.name}'
  params: {
    automationAccountName: automationAccount.outputs.name
    runbookLocation: location
    runbookName: config.name
    runbookType: config.runbookType
    publishContentLink: config.publishContentLink
  }
}]

// Schedules
@description('The confings for schedules.')
var _scheduleConfigs = [
  {
    name: 'vm-start'
    description: empty(scheduleConfig_start.description) ? '仮想マシンを起動するスケジュール' : scheduleConfig_start.description
    expiryTime: scheduleConfig_start.expiryTime
    frequency: scheduleConfig_start.frequency
    interval: scheduleConfig_start.interval
    startTime: scheduleConfig_start.startTime
    timeZone: empty(scheduleConfig_start.timeZone) ? 'Asia/Tokyo' : scheduleConfig_start.timeZone
  }
  {
    name: 'vm-stop'
    description: empty(scheduleConfig_stop.description) ? '仮想マシンを停止するスケジュール' : scheduleConfig_stop.description
    expiryTime: scheduleConfig_stop.expiryTime
    frequency: scheduleConfig_stop.frequency
    interval: scheduleConfig_stop.interval
    startTime: scheduleConfig_stop.startTime
    timeZone: empty(scheduleConfig_stop.timeZone) ? 'Asia/Tokyo' : scheduleConfig_start.timeZone
  }
]

@description('Azure Automation Schedule.')
module schedules './modules/schedules.bicep' = [for config in _scheduleConfigs: {
  name: 'Deploy-schedule-${config.name}'
  params: {
    automationAccountName: automationAccount.outputs.name
    scheduleDescription: config.description
    expiryTime: config.expiryTime
    frequency: config.frequency
    interval: config.interval
    scheduleName: config.name
    startTime: config.startTime
    timeZone: config.timeZone
  }
}]

// jobSchedules
@description('The confings for jobSchedules.')
var _jobScheduleConfigs = [
  {
    runbookName: _runbookConfigs[0].name
    scheduleName: _scheduleConfigs[0].name
    parameters: {}
  }
  {
    runbookName: _runbookConfigs[1].name
    scheduleName: _scheduleConfigs[1].name
    parameters: {}
  }
]

@description('jobSchedules.')
module jobSchedules './modules/jobSchedules.bicep' = [for config in _jobScheduleConfigs: {
  name: 'Deploy-jobSchedule-${config.runbookName}'
  params: {
    automationAccountName: automationAccount.outputs.name
    runbookName: config.runbookName
    scheduleName: config.scheduleName
    parameters: config.parameters
  }
  dependsOn: [
    runbooks
    schedules
  ]
}]

/* Role Assingnment */
@description('Role Assingnment.')
module roleAssignment_resourceGroup './modules//roleAssignments.bicep' = [ for (roleDifinition , index) in roleDifinitions: if( index <= 1){
  name: 'RoleAssignement-${roleDifinition.name}'
  params: {
    principalId: automationAccount.outputs.identityId
    roleDefinitionId: roleDifinition.id
  }
  scope: resourceGroup(vmResourceGroupName)
}]
```

```bicepparam: ./main.bicepparam
using './main.bicep'

param location = '{The region in which you want to deploy the resource}'
param appName = 'vmautostartstop'
param environment = 'dev' // prd, stg, test , etc... 
param suffix = null

// param automationAccountName = '{automation account name}'
param identityType = 'SystemAssigned'
param automationAccountSkuName = 'Free'
param scheduleConfig_start = {
  frequency: 'Day'
  interval: 1
  startTime: '{The time you want to start the VM according to the schedule}' // exapmple format: yyyy-MM-ddTHH:mm:ss+09:00
}
param scheduleConfig_stop = {
  frequency: 'Day'
  interval: 1
  startTime: '{The time you want to stopt the VM according to the schedule}' // exapmple format: yyyy-MM-ddTHH:mm:ss+09:00
}
param roleDifinitions = [
  {
    name: '{your role difinition name}'
    id: '{your role difinition id}'
  }
]

// param vmResourceGroupName = '{your resource group name where the target VM exists}'

```

```bicep: ./modules/automationAccount.bicep
//
// Reference: https://learn.microsoft.com/ja-jp/azure/templates/microsoft.automation/automationaccounts?pivots=deployment-language-bicep
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Resource name of Azure Automation Account Name.')
param automationAccountName string
@description('Resource deployment region.')
param automationAccountLocation string
@description('Resource tags.')
param tags object = {}

@description('Identity type.')
@allowed(['None', 'SystemAssigned','SystemAssigned, UserAssigned', 'UserAssigned'])
param identityTyoe string?
@description('Sets the identity property for automation account')
param identity { type: 'None' | 'SystemAssigned' | 'SystemAssigned, UserAssigned' | 'UserAssigned' | null, userAssignedIdentities: object? } = {
  type: identityTyoe
}
@description('Indicates whether requests using non-AAD authentication are blocked.')
param disableLocalAuth bool = false
@description('Indicates whether traffic on the non-ARM endpoint (Webhook/Agent) is allowed from the public internet')
param publicNetworkAccess bool = true
@description('The encryption properties for the automation account.')
param encryption { identity: object, keySource: 'Microsoft.Automation' | 'Microsoft.Keyvault', keyVaultProperties: object}?
@description('Name ofAccount SKU.')
@allowed([
  'Basic'
  'Free'
])
param skuName string = 'Free'

// --------------------------------------------------------------------------------
// Resources
// --------------------------------------------------------------------------------
@description('Azure Automation Account.')
resource automationAccount 'Microsoft.Automation/automationAccounts@2023-11-01' = {
  name: automationAccountName
  location: automationAccountLocation
  tags: tags
  identity: identity
  properties: {
    disableLocalAuth: disableLocalAuth
    encryption: encryption
    publicNetworkAccess: publicNetworkAccess
    sku: {
      name: skuName
    }
  }
}

// --------------------------------------------------------------------------------
// Outputs
// --------------------------------------------------------------------------------
@description('Resource Id.')
output resourceId string = automationAccount.id
@description('Resource name')
output name string = automationAccount.name
@description('Identity id of SystemAssigned')
output identityId string? = automationAccount.identity.principalId
```

```bicep: ./modules/schedules.bicep
//
// Reference: https://learn.microsoft.com/ja-jp/azure/templates/microsoft.automation/automationaccounts/runbooks?pivots=deployment-language-bicep
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Resource name of parent Azure Automation Account Name.')
param automationAccountName string

@description('Runbook name.')
param runbookName string
@description('Resource deployment region.')
param runbookLocation string
@description('Resource tags.')
param tags object = {}

@description('The description of the runbook.')
param runbookDescription string?
@description('The type of the runbook.')
@allowed([
  'Graph'
  'GraphPowerShell'
  'GraphPowerShellWorkflow'
  'PowerShell'
  'PowerShell72'
  'PowerShellWorkflow'
  'Python2'
  'Python3'
  'Script'
])
param runbookType string
@description('Verbose log option.')
param logVerbose bool = false
@description('Progress log option.')
param logProgress bool = false
@description('The activity-level tracing options of the runbook.')
param logActivityTrace int = 0
@description('The published runbook content link.')
param publishContentLink object?

// --------------------------------------------------------------------------------
// Resources
// --------------------------------------------------------------------------------
@description('Azure Automation Runbook.')
resource runbook 'Microsoft.Automation/automationAccounts/runbooks@2023-11-01' = {
  name: '${automationAccountName}/${runbookName}'
  location: runbookLocation
  tags: tags
  properties: {
    description: runbookDescription
    runbookType: runbookType
    logVerbose: logVerbose
    logProgress: logProgress
    logActivityTrace: logActivityTrace
    publishContentLink: publishContentLink
  }
}

// --------------------------------------------------------------------------------
// Outputs
// --------------------------------------------------------------------------------
@description('Resource Id.')
output resourceId string = runbook.id
@description('Resource name')
output name string = runbook.name
```

```bicep: ./modules/schedules.bicep
//
// Reference: https://learn.microsoft.com/ja-jp/azure/templates/microsoft.automation/automationaccounts/schedules?pivots=deployment-language-bicep
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Resource name of Azure Automation Account Name.')
param automationAccountName string

@description('Name of the schedule to be set')
param scheduleName string

@description('the AdvancedSchedule.')
param advancedSchedule object?
@description('The description of the schedule.')
param scheduleDescription string?
@description('The end time of the schedule.')
param expiryTime string?
@description('The frequency of the schedule.')
@allowed([
  'Day'
  'Hour'
  'Minute'
  'Month'
  'OneTime'
  'Week' 
])
param frequency string
@description('The interval of the schedule.  Example: yyyy-MM-ddTHH:mm:ss+09:00')
param interval int
@description('The start time of the schedule. Example: yyyy-MM-ddTHH:mm:ss+09:00')
param startTime string
@description(' The time zone of the schedule.')
param timeZone string = 'Asia/Tokyo'

// --------------------------------------------------------------------------------
// Resources
// --------------------------------------------------------------------------------
@description('Azure Automation Schedule.')
resource schedule 'Microsoft.Automation/automationAccounts/schedules@2023-11-01' = {
  name: '${automationAccountName}/${scheduleName}'
  properties: {
    advancedSchedule: advancedSchedule
    description: scheduleDescription
    expiryTime: expiryTime
    frequency: frequency
    interval: any(interval)
    startTime: startTime
    timeZone: timeZone
  }
}

// --------------------------------------------------------------------------------
// Outputs
// --------------------------------------------------------------------------------
@description('Resource Id.')
output resourceId string = schedule.id
@description('Resource name')
output name string = schedule.name
```

```bicep: ./modules/jobSchedules.bicep
//
// Reference: https://learn.microsoft.com/ja-jp/azure/templates/microsoft.automation/automationaccounts/jobschedules?pivots=deployment-language-bicep
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Resource name of Azure Automation Account Name.')
param automationAccountName string

@description('Name of the runbook to which the schedule is associated.')
param runbookName string
@description('Name of the schedule to be linked.')
param scheduleName string

@description('Parameters required for job execution.')
param parameters object = {}
@description(' the hybrid worker group that the scheduled job should run on.')
param runOn string?

// --------------------------------------------------------------------------------
// Variables
// --------------------------------------------------------------------------------
@description('The resource name')
var _jobScheduleName = guid(automationAccountName, runbookName, scheduleName, subscription().subscriptionId, resourceGroup().id)

// --------------------------------------------------------------------------------
// Resources
// --------------------------------------------------------------------------------
@description('Azure Automation JobSchedule.')
resource jobSchedules 'Microsoft.Automation/automationAccounts/jobSchedules@2023-11-01' = {
  name: '${automationAccountName}/${_jobScheduleName}'
  properties: {
    parameters: parameters
    runbook: {
      name: runbookName
    }
    runOn: runOn
    schedule: {
      name: scheduleName
    }
  }
}

// --------------------------------------------------------------------------------
// Outputs
// --------------------------------------------------------------------------------
@description('Resource Id.')
output resourceId string = jobSchedules.id
@description('Resource name')
output name string = jobSchedules.name
```

```bicep: ./modules/roleAssignments.bicep
//
// Reference: https://learn.microsoft.com/ja-jp/azure/templates/microsoft.authorization/roleassignments?pivots=deployment-language-bicep
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Specifies the role definition ID used in the role assignment.')
param roleDefinitionId string
@description('Specifies the principal ID assigned to the role.')
param principalId string

// --------------------------------------------------------------------------------
// Variables
// --------------------------------------------------------------------------------
@description('The resource name')
var roleAssignmentName = guid(principalId, roleDefinitionId, resourceGroup().id)

// --------------------------------------------------------------------------------
// Resources
// --------------------------------------------------------------------------------
@description('Role Assignments.')
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: roleAssignmentName
  properties: {
    roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
    principalId: principalId
  }
}

// --------------------------------------------------------------------------------
// Outputs
// --------------------------------------------------------------------------------
@description('Resource name of Role Assignments.')
output name string = roleAssignment.name
@description('Resource Group name of Role Assignments.')
output resourceGroupName string = resourceGroup().name
@description('Resource Id of Role Assignments.')
output resourceId string = roleAssignment.id
```

## パラメータの設定

リソース名やスケジュール設定のラメータは `main.bicepparam` に入力してください。

下記の例だと、`2024/11/17 00:00:00` に起動処理が、`2024/11/17 23:50:00` に停止処理が動くように設定できます。

また、ロール定義についてはPortal などから取得して入力してください。
（今回は、カスタムロールを設定しています。）

```
using './main.bicep'

// param location = '{The region in which you want to deploy the resource}'
param appName = 'vmautostartstop'
param environment = 'idea'
param suffix = '001'

// param automationAccountName = '{automation account name}'
param identityType = 'SystemAssigned'
param automationAccountSkuName = 'Free'
param scheduleConfig_start = {
  description: null
  expiryTime: null
  frequency: 'Day'
  interval: 1
  startTime: '2024-11-17T00:00:00+09:00'
  timeZone: null
}
param scheduleConfig_stop = {
  description: null
  expiryTime: null
  frequency: 'Day'
  interval: 1
  startTime: '2024-11-17T23:50:00+09:00'
  timeZone: null
}
param roleDifinitions = [
  {
    name: '仮装マシン電源オペレーター'
    id: 'cfb29df2-1cb5-43e2-8414-e9eecc6572c3'
  }
]

// param vmResourceGroupName = '{your resource group name where the target VM exists}'

```

## リソースデプロイ

下記コマンドを実行してリソースをデプロイします。

コマンド実行は各種ファイルが存在するルートディレクトリで行ってください。

### 変数の設定

```bash
# 変数の設定
resourceGroupName={your resource group name} # デプロイするリソースグループの名前
```

### Azure ログイン

```bash
# ログイン
az login
```

### デプロイ

```bash
# デプロイ
az deployment group create \
--name 'Deploy_VMAutoStartStop' \
--resource-group $resourceGroupName \
--template-file './main.bicep' \
--parameters './main.bicepparam'
```

### デプロイ確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/86b2819d-a649-6c65-8f47-dcd0eb0545b7.png)


以上でシステムの構築は終了です。

# Runbook のコードについて

デフォルトでは、[私の GitHub リポジトリ](https://github.com/nownaka/azure-vms-auto-start-stop-template)を参照して自動でインポートされるようになっています。

```powershell
# ============================================================================
# VMを停止するスクリプト
# Created by nownaka.
# ============================================================================

Write-Output "Start!"

## 認証 ##
# https://learn.microsoft.com/ja-jp/azure/automation/enable-managed-identity-for-automation#authenticate-access-with-system-assigned-managed-identity

# Ensures you do not inherit an AzContext in your runbook
Disable-AzContextAutosave -Scope Process

# Connect to Azure with system-assigned managed identity
$AzureContext = (Connect-AzAccount -Identity).context

# Set and store context
$AzureContext = Set-AzContext -SubscriptionName $AzureContext.Subscription -DefaultProfile $AzureContext

## VM 操作 ##
# VM 一覧を取得
Write-Output "Get VM List..."
$vmList = Get-AzVM | Select-Object ResourceGroupName, Id, VmId, Name
Write-Output $vmList

# VM の電源ステータスを取得
# https://learn.microsoft.com/ja-jp/azure/virtual-machines/windows/tutorial-manage-vm#vm-power-states
# Starting	    |   起動中
# Running       |   実行中
# Stopping	    |   停止中
# Stopped	    |   停止済(コスト発生)
# Deallocating  |   割り当て解除中
# Deallocated   |   割り当て解除済み(コスト発生なし)
# -             |   電源状態は不明
Write-Output "Get VM Power Status..."
$vmPowerStatusList = @()
foreach($vm in $vmList) {
    $vmPowerStatusList += Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status |
    Select-Object ResourceGroupName, Name, @{ n = "Status"; e = { $_.Statuses[1].Code} }
}
Write-Output $vmPowerStatusList

# VM を起動
# https://learn.microsoft.com/ja-jp/powershell/module/az.compute/start-azvm?view=azps-12.4.0
Write-Output "Start VMs..."
$statusList = @("*Stopped","*Deallocating","*Deallocated")
foreach($vm in $vmPowerStatusList) {
    foreach($status in $statusList) {
        $flag = $vm.Status -like $status
    }
    if($flag) {
        Write-Output "name: $($vm.Name)"
        Start-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name
    }
}

Write-Output "Finish!"
```

```powershell
# ============================================================================
# VMを停止するスクリプト
# Created by nownaka.
# ============================================================================

Write-Output "Start!"

## 認証 ##
# https://learn.microsoft.com/ja-jp/azure/automation/enable-managed-identity-for-automation#authenticate-access-with-system-assigned-managed-identity

# Ensures you do not inherit an AzContext in your runbook
Disable-AzContextAutosave -Scope Process

# Connect to Azure with system-assigned managed identity
$AzureContext = (Connect-AzAccount -Identity).context

# Set and store context
$AzureContext = Set-AzContext -SubscriptionName $AzureContext.Subscription -DefaultProfile $AzureContext

## VM 操作 ##
# VM 一覧を取得
Write-Output "Get VM List..."
$vmList = Get-AzVM | Select-Object ResourceGroupName, Id, VmId, Name
Write-Output $vmList

# VM の電源ステータスを取得
# https://learn.microsoft.com/ja-jp/azure/virtual-machines/windows/tutorial-manage-vm#vm-power-states
# Starting	    |   起動中
# Running       |   実行中
# Stopping	    |   停止中
# Stopped	    |   停止済(コスト発生)
# Deallocating  |   割り当て解除中
# Deallocated   |   割り当て解除済み(コスト発生なし)
# -             |   電源状態は不明
Write-Output "Get VM Power Status..."
$vmPowerStatusList = @()
foreach($vm in $vmList) {
    $vmPowerStatusList += Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status |
    Select-Object ResourceGroupName, Name, @{ n = "Status"; e = { $_.Statuses[1].Code} }
}
Write-Output $vmPowerStatusList

# VM を停止
# https://learn.microsoft.com/ja-jp/powershell/module/az.compute/stop-azvm?view=azps-12.4.0
Write-Output "Stop VMs..."
$statusList = @("*Starting","*Running")
foreach($vm in $vmPowerStatusList) {
    foreach($status in $statusList) {
        $flag = $vm.Status -like $status
    }
    if($flag) {
        Write-Output "name: $($vm.Name)"
        Stop-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Force
    }
}

Write-Output "Finish!"
```

# 実際に動かしてみる

スケジュールは待つのが面倒なので、手動で実行してみます。（←）

## 停止

### 実行前

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/1d9a87e4-7faa-98ae-720f-b52a5ef0c1d9.png)


### 実行後

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/3b9cc9ea-455f-bbd8-12a5-292a08ea56d1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/d73aae17-19d8-26a4-d5cf-ae5b1d58469c.png)


## 起動

### 実行前

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/1ea4d495-34a1-2d37-3685-9361c18b64e6.png)


### 実行後

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/7451817c-6846-8972-bcfa-cd1e41115688.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/c246314a-e13c-f24f-5be6-4c77217b05fc.png)


大丈夫そうです。

# さいごに

それぞれ利用シーンは異なると思いますので、デプロイした後に Automation の設定や Runbook の内容を変更して、カスタマイズするのがよいかと思います。

なんだかんだやっていると、Bicep の作成などで時間をとられましたが、やりたいことはできたので、よかったかなと思います。

最後まで見ていただきありがとうございます。誰かの役に立てば幸いです。

## テンプレートの紹介

今回記事で紹介した内容は、テンプレートとして下記 GitHub で公開しています。
よければこちらもご確認ください。

https://github.com/nownaka/azure-vms-auto-start-stop-template

# 参考

https://jpaztech.github.io/blog/vm/rbac-vm-start-stop-restart/

https://learn.microsoft.com/ja-jp/azure/automation/enable-managed-identity-for-automation#authenticate-access-with-system-assigned-managed-identity

https://learn.microsoft.com/ja-jp/azure/virtual-machines/windows/tutorial-manage-vm#vm-power-states

https://learn.microsoft.com/ja-jp/powershell/module/az.compute/start-azvm?view=azps-12.4.0

https://learn.microsoft.com/ja-jp/powershell/module/az.compute/stop-azvm?view=azps-12.4.0

https://www.tohoho-web.com/ex/power-shell.html