Hyper-Vで一気に複数VMを作成する

9月のWindowsUpdateというテロ行為にあってRAID1組んでいたOSディスクが完全に認識されなくなり、おまけに別ドライブにしていたSSDまで別のPCでも認識できないという目にあいました。
CDKTFで作成していた3クラウドのバックエンドの追加作業も吹っ飛んで、データ復旧業者にまで頼んだんですが、データは取り戻せませんでした。Gitにあげときゃよかったという後悔しかなくその後の復旧とかにもかなり時間がかかっています。

幸いNASに昨年の12月分のバックアップがあって、そっから更新されたデータは吹っ飛んで泣きそうになりながらもなんとか使える状態には戻しました。
RAID1は解除し、1TBのSSD4基購入してきて、NASのディスクをWDの6TBのものに切り替えて復旧作業もようやく落ち着いてきたかなという感じです。

そんでCDKで作業するにあたって、Hyper-Vの復旧作業が必要になったんですが、手動で20台近くのVMを作るなんて狂気の沙汰的なことはやりたくないので、PowerShellで一撃で作成するスクリプト準備しました。
外部と接続できるスィッチも作成する感じにしてます。ベタ書き嫌だったので外部ファイルから読み込ませて動くようにしています。自分はヨワヨワなのでGemini君に書いてもらいました。ほんとに自分なんてもういらないなと思うようになってきましたよ。ええ


◆スクリプト本体

param(
    [Parameter(Mandatory=$true)]
    [string]$ConfigFilePath
)

# ============================================
# メインスクリプト:Hyper-V 仮想マシン一括作成
# 設定ファイルは $ConfigFilePath で指定されます。
# ============================================

# 設定ファイル名 (引数から取得)
$ConfigFileName = $ConfigFilePath

# ============================================
# 設定ファイルの読み込み
# ============================================

Write-Host "設定ファイル '$ConfigFileName' の読み込みを開始します..." -ForegroundColor Yellow

if (-not (Test-Path -Path $ConfigFileName)) {
    Write-Error "設定ファイル '$ConfigFileName' が見つかりません。スクリプトを終了します。"
    exit
}

try {
    # JSONファイルの内容を読み込み、PowerShellオブジェクトに変換
    $ConfigData = Get-Content -Path $ConfigFileName | ConvertFrom-Json
    
    # 変数の設定
    $StorageRootPath = $ConfigData.StorageRootPath
    $VirtualSwitchName = $ConfigData.VirtualSwitchName
    $VMGeneration = $ConfigData.VMGeneration
    $VMConfigurations = $ConfigData.VMConfigurations
    
    Write-Host "設定ファイル '$ConfigFileName' を正常に読み込みました。" -ForegroundColor Green
}
catch {
    Write-Error "設定ファイル '$ConfigFileName' の読み込みまたは解析に失敗しました: $($_.Exception.Message)"
    exit
}

# ============================================
# 事前チェックとスイッチ・フォルダ作成
# ============================================

# --- 1. スイッチの存在チェックと作成 ---
if ($null -eq (Get-VMSwitch -Name $VirtualSwitchName -ErrorAction SilentlyContinue)) {
    Write-Host "指定された仮想スイッチ '$VirtualSwitchName' が見つかりません。外部スイッチを作成します..." -ForegroundColor Yellow
    try {
        # 外部ネットワークに接続するスイッチを作成
        $NetAdapter = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' -and $_.Virtual -eq $false } | Select-Object -First 1

        if ($null -ne $NetAdapter) {
            New-VMSwitch -Name $VirtualSwitchName -NetAdapterName $NetAdapter.Name -AllowManagementOS $true -ErrorAction Stop | Out-Null
            Write-Host "外部仮想スイッチ '$VirtualSwitchName' を物理アダプター '$($NetAdapter.Name)' に作成しました。" -ForegroundColor Green
        }
        else {
            Write-Error "外部スイッチの作成に失敗しました: 物理ネットワークアダプターが見つかりません。スクリプトを終了します。"
            exit
        }
    }
    catch {
        Write-Error "仮想スイッチの作成中にエラーが発生しました: $($_.Exception.Message)"
        exit
    }
}
else {
    Write-Host "仮想スイッチ '$VirtualSwitchName' は既に存在します。" -ForegroundColor Green
}

# --- 2. ルートフォルダの存在チェックと作成 ---
if ($null -eq $StorageRootPath -or -not (Test-Path -Path $StorageRootPath -PathType Container)) {
    Write-Host "ストレージルートフォルダ '$StorageRootPath' が存在しません。作成します..." -ForegroundColor Yellow
    try {
        New-Item -Path $StorageRootPath -ItemType Directory | Out-Null
        Write-Host "ルートフォルダを作成しました: $StorageRootPath" -ForegroundColor Green
    }
    catch {
        Write-Error "フォルダの作成に失敗しました: $($_.Exception.Message)"
        exit
    }
}


# ============================================
# 仮想マシンの作成と設定
# ============================================

foreach ($VMConfig in $VMConfigurations) {
    # 必須パラメータのチェック
    if ($null -eq $VMConfig.Name -or $null -eq $VMConfig.CPUCount -or $null -eq $VMConfig.StartupMemoryMB) {
        Write-Host "警告: 無効なVM設定が検出されました。スキップします。" -ForegroundColor Red
        continue
    }

    $VMName = $VMConfig.Name
    $CPUCount = $VMConfig.CPUCount
    $StartupMemoryMB = $VMConfig.StartupMemoryMB
    $StartupMemoryBytes = $StartupMemoryMB * 1MB
    $VHDPath = $VMConfig.VHDPath 

    # 仮想マシン格納用サブフォルダのパスを定義
    $VMPath = Join-Path -Path $StorageRootPath -ChildPath $VMName

    Write-Host ""
    Write-Host "--- 仮想マシン '$VMName' (CPU:$CPUCount, Memory:$StartupMemoryMB MB) の作成を開始します ---" -ForegroundColor Cyan

    # 仮想マシン格納用サブフォルダの作成
    if (-not (Test-Path -Path $VMPath -PathType Container)) {
        New-Item -Path $VMPath -ItemType Directory | Out-Null
        Write-Host "仮想マシン格納フォルダを作成しました: $VMPath"
    }
    
    try {
        # 既存のVMをチェック(冪等性確保のため)
        if ($null -ne (Get-VM -Name $VMName -ErrorAction SilentlyContinue)) {
            Write-Host "仮想マシン '$VMName' は既に存在するため、スキップします。" -ForegroundColor Yellow
            continue
        }

        # 1. New-VMで仮想マシンの基本を作成(-NoVHDでVHDは作成しない)
        New-VM -Name $VMName `
               -Path $VMPath `
               -Generation $VMGeneration `
               -MemoryStartupBytes $StartupMemoryBytes `
               -SwitchName $VirtualSwitchName `
               -NoVHD `
               -ErrorAction Stop

        Write-Host "基本設定を完了しました。"
        
        # 2. Set-VMProcessorでCPU数を設定
        Set-VMProcessor -VMName $VMName -Count $CPUCount -ErrorAction Stop | Out-Null
        Write-Host "CPU数 ($CPUCount コア) を設定しました。"

        # 3. Set-VMで自動チェックポイントを無効に設定
        Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -ErrorAction Stop | Out-Null
        Write-Host "自動チェックポイントを無効にしました。"

        # 4. Set-VMFirmwareでセキュアブートを無効に設定
        Set-VMFirmware -VMName $VMName -EnableSecureBoot Off -ErrorAction Stop | Out-Null
        Write-Host "セキュアブートを無効にしました。" -ForegroundColor Yellow

        # 5. 既存の仮想ディスクの接続
        # $VHDPath が $null ではなく、かつ空文字列 ('') でないことを確認
        if ($null -ne $VHDPath -and $VHDPath) {
            if (Test-Path -Path $VHDPath -PathType Leaf) {
                # 接続先はSCSIコントローラー
                Add-VMHardDiskDrive -VMName $VMName -Path $VHDPath -ErrorAction Stop | Out-Null
                Write-Host "既存の仮想ディスク '$VHDPath' を接続しました。" -ForegroundColor Green
            } else {
                Write-Host "警告: 指定されたVHDXファイル '$VHDPath' が見つかりません。ディスクは接続されませんでした。" -ForegroundColor Red
            }
        } else {
            Write-Host "VHDPathが指定されていないため、ディスクの接続をスキップします。"
        }
        
        Write-Host "仮想マシン '$VMName' の作成と設定が完了しました。" -ForegroundColor Green
    }
    catch {
        Write-Error "仮想マシン '$VMName' の作成中にエラーが発生しました: $($_.Exception.Message)"
    }
}

Write-Host ""
Write-Host "=============================================" -ForegroundColor Cyan
Write-Host "すべての仮想マシンの作成処理が完了しました。" -ForegroundColor Cyan
Write-Host "=============================================" -ForegroundColor Cyan


◆設定ファイル

{
    "StorageRootPath": "C:\\VMs",
    "VirtualSwitchName": "test-sw-external",
    "VMGeneration": 2,
    "VMConfigurations": [
        {
            "Name": "TEST1-WEB",
            "CPUCount": 2,
            "StartupMemoryMB": 1024,
            "VHDPath": "C:\\DISK1\\VMServers\\VHD\\000_BaseServer.vhdx"
        },
        {
            "Name": "TEST2-WEB",
            "CPUCount": 2,
            "StartupMemoryMB": 2048,
            "VHDPath": ""
        },
        {
            "Name": "TEST3-WEB",
            "CPUCount": 4,
            "StartupMemoryMB": 4096,
            "VHDPath": "C:\\DISK1\\VMServers\\VHD\\002_LoadBarancer.vhdx"
        }
    ]
}


◆参照サイト

・仮想スイッチ

https://www.tsure-dure.jp/Note/Windows/Virtualization/Virtualization_10

・複数マシン作成

https://qiita.com/minoden_works/items/c2234b1e92e45864d831

・ポリシー設定

https://qiita.com/Targityen/items/3d2e0b5b0b7b04963750


実行に際してはこんな感じで実行して。設定ファイルのパスを指定して実行するだけです。
> PowerShell -ExecutionPolicy RemoteSigned .\VMBuild.ps1

それとPowerShellで日本語扱うときはSJISで保存しないとまともに動かないというPowerShellのテロ仕様があるので要注意です。


可読性でいえばYAMLのほうがよかったかなぁと思いつつも、jsonでも意図は理解しやすいとは思うのでそのままです。

Windowsはテロを起こすので、早めに別のOSに切り替えたほうがいいと思いつつもできていないんですよね。
そこまで特殊なソフト使っていないのでこだわりはないんですけど、まぁ過去資産の蓄積とかあってなかなか離れられないという。

しかしバックアップ重要ですね。今回のWindowsのテロで身に沁みました。
あ、そのバックアップどうするかがまだ決められていない。
これも課題、ということでリカバリーにまだまだかかりそうです。

コメント

このブログの人気の投稿

GASでGoogleDriveのサブフォルダとファイル一覧を出力する

マクロ経済学(IS-LM分析)

IEのセキュリティ設定をいじくるバッチ