「Hyper-V小技・裏技集」シリーズでは、Hyper-Vの仮想環境を利用していて、知っていると便利な小技や裏技を紹介します。前回は、PowerShell DirectとCopy-VMFileの使いこなしについて説明しました。今回からは、これらを活用した、VMのゲスト側タスクの自動化例をいくつか紹介します。
※ここで紹介するサンプルコードは、PowerShellスクリプト(.ps1)内に記述することを想定しています。-ErrorAction Stopパラメーターは、エラー時にスクリプト全体を停止します。PowerShellウィンドウに入力して実行した場合は、エラーが発生しても以後のスクリプトは停止しないため、別のエラーが発生する可能性があることに注意してください。
VMのゲストOSの初期設定(ついでにVM名も変更)
次のPowerShellスクリプトのコードは、新規インストールしてAdministratorのパスワードを設定しただけのWindows Serverを実行するVMを想定しています。PowerShell Directを使用してOSの以下の初期設定を行い、再起動したあと、最後にVM名をOSのコンピューター名に変更します(画面1)。VMのテンプレートから無人応答ファイル(Unattend.xml)を使って展開したVM(参考: vol.18 ラボ環境 on Azureを作る(6) - VMテンプレートを作成する)の設定にも使用できます。
- Administratorパスワードの変更
- コンピューター名の変更
- ネットワークの設定(IPv4静的アドレス、デフォルトゲートウェイ、DNSサーバー)
- リモートデスクトップサービスの有効化
- VM名の変更
#$cred = Get-Credential
# または
#(事前に Get-Credential | Export-Clixml $HOME\vm01-cred.xml でXMLを作成)
#$cred = Import-Clixml $HOME\vm01-cred.xml
# または
$username = ".\Administrator"
$password = "P@ssw0rd!"
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$cred = [PSCredential]::new($username, $securePassword)
$vmName = "vm01"
# 設定パラメーター
$NewVMName = "winsv01"
$ComputerName = $NewVMName
$AdminPasswd = "nEwP@sw0D9"
$IPAddr = "192.168.0.109"
$SubnetPrefix = 24
$GWAddr = "192.168.0.1"
$DnsAddr = @("192.168.0.202", "192.168.0.201")
$EnableRDP = $true
$vm = Get-VM -Name $vmName -ErrorAction Stop
Write-Host "VM $($vmName) の設定を開始します..."
if (($vm.State -eq "Running") -and ($vm.HeartBeat -Like "Ok*")) {
Invoke-Command -VMName $vm.Name -Credential $cred `
-ScriptBlock {
# --- ScriptBlock ---
param($NAME, $PASSWD, $IP, $PREFIX, $GTW, $DNS, $RDP)
Write-Host "Administrator のパスワードを変更します。"
net user Administrator $PASSWD
Write-Host "コンピューター名を変更します。"
Rename-Computer -NewName $NAME -Force
Write-Host "ネットワークを設定します。"
$nic = Get-NetAdapter | Where {$_.Status -eq "Up"} | Select -First 1
# Remove Current IPv4 and GTW
Get-NetIPAddress -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 | Remove-NetIPAddress -Confirm:$false
Get-NetRoute -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 -DestinationPrefix "0.0.0.0/0" | Remove-NetRoute -Confirm:$false
# New IPv4 and GW
New-NetIPAddress -InterfaceIndex $nic.ifIndex -IPAddress $IP -PrefixLength $PREFIX -DefaultGateway $GTW
Set-DnsClientServerAddress -InterfaceIndex $nic.ifIndex -ServerAddresses $DNS
# Enable RDP
if ($RDP) {
Write-Host "リモートデスクトップを有効化します。"
Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value 0
Enable-NetFirewallRule -Name "RemoteDesktop-UserMode-In-TCP"
Enable-NetFirewallRule -Name "RemoteDesktop-UserMode-In-UDP"
}
# Restart Computer
Write-Host "再起動します。"
Restart-Computer -Force
# --- ScriptBlock ---
} `
-ArgumentList $ComputerName, $AdminPasswd, $IPAddr, $SubnetPrefix, $GWAddr, $DnsAddr, $EnableRDP
while ($vm.HeartBeat -Like "Ok*") {
Start-Sleep 1
}
while ($vm.HeartBeat -notLike "Ok*") {
Start-Sleep 1
}
Write-Host "VM の名前を変更します。'$($vm.Name)' > '$($NewVMName)'"
Rename-VM -VM $vm -NewName $NewVMName
Write-Host "完了しました。"
if ($EnableRDP){
mstsc /v $IPAddr
} else {
vmconnect localhost $vm.Name
}
} else {
Write-Host "VM $($vmName) がオフであるため、設定をスキップしました。VM を開始してから再実行してください。"
}
$result
(プレーンテキストでダウンロード/表示)
※ この例のようにスクリプト内に平文パスワードを記述することは推奨されません。平文パスワードを排除する方法については、「スクリプトから平文パスワードを排除する簡単な方法」を参照してください。

画面1 VMのOS初期設定とVM名を変更するPowerShellスクリプトの実行例(クリックで拡大)
このスクリプトでは、Get-VMでVMとゲストの実行状態を確認し、実行中であれば、PowerShell Direct(Invoke-Command)を使用して次のスクリプトブロックをVMで実行してOSを再起動します。
|
# Administratorのパスワード変更 net user Administrator 新しいパスワード # コンピューター名の変更 Rename-Computer -NewName 新しいコンピューター名 -Force # ネットワークの設定: Up状態の最初のネットワークアダプターを選択 $nic = Get-NetAdapter | Where {$_.Status -eq "Up"} | Select -First 1 # ネットワークの設定: 既存のIP設定およびデフォルトゲートウェイの設定を念のため削除(New-NetIPAddressのエラー回避のため) Get-NetIPAddress -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 | Remove-NetIPAddress -Confirm:$false Get-NetRoute -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 -DestinationPrefix "0.0.0.0/0" | Remove-NetRoute -Confirm:$false # 静的IPv4アドレスの設定 New-NetIPAddress -InterfaceIndex $nic.ifIndex -IPAddress IPアドレス -PrefixLength サフィックス -DefaultGateway ゲートウェイのアドレス Set-DnsClientServerAddress -InterfaceIndex $nic.ifIndex -ServerAddresses DNSサーバーのアドレス # リモートデスクトップ接続の有効化
Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value 0 Enable-NetFirewallRule -Name "RemoteDesktop-UserMode-In-TCP" Enable-NetFirewallRule -Name "RemoteDesktop-UserMode-In-UDP" # 再起動
Restart-Computer -Force
|
スクリプトでは、設定パラメーターをローカル変数で定義しています。しかし、ローカルセッションの(ホストの)PowerShellセッションで使用しているローカル変数をリモートセッションで実行されるスクリプトブロックにそのまま渡すことはできません。なぜなら、リモートセッションでは未定義のローカル変数であるからです。ローカルセッションのローカル変数をリモートセッションに渡す方法の1つは、$usingスコープ修飾子を使用する方法です。リモートセッションからは、$using:変数名を使用すると別のセッションで定義された変数を参照できます(画面2)。リモートに渡したい変数が少数の場合はこの方法が手軽です。

画面2 ローカル変数$ComputerNameはリモートセッションには存在しない変数。スクリプトブロック内では$using:ComputerNameで参照できる
ローカル変数の数が多い場合は、画面3のようにInvoke-Commandの-ArgumentListパラメーターでスクリプトブロックにパラメーター(Param)としてローカル変数を渡します。スクリプト ブロック内のパラメーターは、 -ArgumentListに指定された配列値から位置によって渡されます。 今回のスクリプトではこの方法を利用しています。

画面3 Invoke-CommandのーArgumentListパラメーターを使用して、変数をパラメーターとして渡す
スクリプトから平文パスワードを排除する簡単な方法
今回紹介したサンプルスクリプトは、簡潔にするためにパスワードを平文で記述していますが、スクリプトにパスワードを平文で記述することは、セキュリティ上の理由から行うべきではありません。セキュリティ上のリスクだけでなく、パスワードの変更時に、パスワードを記述したすべてのスクリプトを修正するという手間もあります。
スクリプトから平文パスワードを排除するために、Export-Clixml/Import-Clixmlを使用した簡単な方法を紹介します。これらは、PowerShellで扱うオブジェクトをXMLベースの形式でエクスポート/インポートできるものであり、Windows PowerShell 5.1とPowerShell 7の両方で利用できます。これらの特に有用な用途の1つとして、資格情報やSecureStringを安全にエクスポート/インポート(Windows標準のデータ保護API《DPAPI》を使用して暗号化/復号)できることがあります。これは、同じ管理用コンピューターで繰り返し実行するようなスクリプトとの相性が良い方法です。
資格情報をXMLファイルに保存するには、次のコマンドラインを実行します。ユーザー名と資格情報の入力が求められるので、入力すると、ファイルに安全にエクスポートされます。
| Get-Credential | Export-Clixml $HOME\vm01-cred.xml |
XMLファイルに保存された資格情報を利用するには、次のように実行するだけです(上記のスクリプトではこのコードが#コメントアウトされています)。
| $cred = Import-Clixml $HOME\vm01-cred.xml |
 (Export-Clixml/Import-Clixmlによる資格情報のエクスポート/インポート)
Export-ClixmlでエクスポートされたXMLファイルをインポートできるのは、エクスポートしたのと同じユーザー、同じコンピューターに限定されます。そうでない場合、「Import-Clixml : 指定された状態で使用するには無効なキーです。」(Windows PowerShell 5.1)または「Import-Clixml: Error occurred during a cryptographic operation.」(PowerShell 7)のエラーが表示され、資格情報の復号に失敗します。つまり、XMLファイルが外部に流出したとしても、そこから資格情報を取り出すことができないため安全です。
また、外部ツールには依存しますが、PowerShell Garallyで公開されているCredentialManager(Windows PowerShell 5.1対応)やTUN.CredntialManager(PowerShell 6/7対応)を利用するという方法があります。これらは、資格情報をWindows資格情報マネージャーに登録し、取り出して利用することを可能にするPowerShellモジュールです。
CredentialManager|PowerShell Garally
TUN.CredentialManager|PowerShell Garally
 (TUN.CredentialManagerモジュールのPowerShell 7へのインストールと使用)
|
リファレンス(Microsoft Learn):
Get-VM(Hyper-V)|Invoke-Command(Hyper-V)| about_Scopes(PowerShell)| Export-Clixml(PowerShell)| Import-Clixml(PowerShell)
Hyper-V 小技・裏技集 (1)|(2)|(3)|(4)|(5)|(6)|(7)