PowerShell Direct で VM 展開を自動化してみる

Windows 10 / Windows Server 2016 から、PowerShell Direct という機能が増えています。

これを活用すると、Hyper-V ホストから VM の中に対してファイルをコピーしたり、コマンド実行ができるので、大量の VM を自動で展開できるようになります。例えば、Azure PowerShell のバージョン毎に VM を作るには以下のような感じ。

事前に応答ファイルを差し込んだ sysprep 済みの VHD と、各バージョンの Azure PowerShell を用意しておく必要がありますが、その辺は説明が長くなるので今回は書きません。(わからない人は、応答ファイルとか unattend とか sysprep といったキーワードで検索すればいくらでも情報でるので調べてください。)

# Azure PowerShell のインストーラーを配置したフォルダー
$PowerShellInstallder = Get-ChildItem 'C:\Users\Administrator\Desktop\Azure PowerShell'

# Azure PowerShell のインストーラー分だけ VM を作成
$PowerShellInstallder | foreach{
    # ファイル名 (azure-powershell.1.0.0.msi) からバージョンを抽出
    $Ver = $_.Name -replace 'azure-powershell.', '' -replace '.msi', ''

    # 仮想マシン名の Prefix を定義
    $VMName = 'Win 10 Ent x64 with Azure PowerShell $Ver'

    # sysprep 済みのマスター イメージをコピー
    Copy-Item -Path 'D:\Win 10 Ent x64 with Azure PowerShell Master.vhdx' -Destination 'D:\Win 10 Ent x64 with Azure PowerShell $Ver.vhdx'

    # コピーした VHD をもとに VM を作成
    New-VM -Name $VMName -VHDPath 'D:\Win 10 Ent x64 with Azure PowerShell $Ver.vhdx'

    # VM のコア数、メモリ、NIC などを任意でカスタマイズ
    Set-VMProcessor -VMName $VMName -Count 16
    Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $true
    Set-VMNetworkAdapter -VMName $VMName
    Get-VMNetworkAdapter -VMName $VMName | Connect-VMNetworkAdapter -SwitchName external

    # VM を起動
    Start-VM -Name $VMName
}

上記は空の VM を作っただけなので、続いてインストーラーを VM 内にコピーして実行します。
あわせて、せっかくなので Windows PowerShell の Execution Policy とか、Explorer のレジストリも一緒に設定しています。

$PowerShellInstallder | foreach{
    # 各種変数を設定
    $FullPath = $_.FullName
    $FileName = $_.Name
    $Ver = $_.Name -replace 'azure-powershell.', '' -replace '.msi', ''
    $VMName = 'Win 10 Ent x64 with Azure PowerShell $Ver'
    $InstallerPath = 'C:\Users\Public\Documents\$FileName'

    # 新規セッションを確立
    $Session = New-PSSession -VMName $VMName -Credential $Cred

    # VM 内で実行する処理の定義
    $DeployScript = '
    reg add HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v LaunchTo /t REG_DWORD /d 1 /f
    reg add HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer /v ShowRecent /t REG_DWORD /d 0 /f
    reg add HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer /v ShowFrequent /t REG_DWORD /d 0 /f
    reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v HideFileExt /t REG_DWORD /d 0 /f
    reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v Hidden /t REG_DWORD /d 1 /f
    reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\PowerShell /v EnableScripts /t REG_DWORD /d 1 /f
    reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\PowerShell /v ExecutionPolicy /t REG_SZ /d RemoteSigned /f
    Start-Process -FilePath $InstallerPath -ArgumentList '/quiet' # Azure PowerShell のサイレント インストール
    '

    # 上記で定義した内容を、一旦ローカルに保存
    Out-File -InputObject $DeployScript -FilePath $env:TEMP\deploy.ps1

    # VM 内に Azure PowrShell のインストーラーをコピー
    Copy-Item -ToSession $Session -Path $FullPath -Destination 'C:\Users\Public\Documents'

    # VM 内でインストール処理を実行
    Invoke-Command -VMName $VMName -Credential $Cred $env:TEMP\deploy.ps1
}

以上で各バージョンがインストールされた VM が大量に生成され、インストールも完了していることが確認できます。
VHD のコピーには結構時間がかかるので、NVMe SSD とかを使うと快適ですね。

vmms

vm

イベント ビューアーの [保存されたログ] を一括削除する方法

イベント ビューアーの [保存されたログ] に大量のログが紐づいてしまう件、英語 KB で一発解決したのでメモ。

コマンド プロンプトで以下実行すると一掃できるようなので覚えておくと便利。

del /s /q %programdata%\microsoft\eventv~1\extern~1

YAMAHA のルーターは Azure とのルート ベース VPN 接続をサポートしないらしい

どこのご家庭にもある YAMAHA のルーターですが、Azure との VPN 接続はポリシー ベース (静的) しか対応していないそうです。
我が家にも NVR500 / RTX1200 / RTX1210 があるので、以前からポリシー ベースでは常時接続していたのですが、ルート ベース未対応とは…。

ルート ベースのコンフィグが公式サイトにも一向に出てくる気配がないので Web から直接問い合わせた所、一時間足らずで「全製品とも Azure の動的ルーティングに対応していない」と明確な回答をいただきました。(一応、回答メールをコピペするのは控えておきます)

多分、私以外にも問い合わせが多数あがっていて、テンプレ的な回答なんでしょうね…。
使いたい人は多いと思うんですが、明確に未対応との回答が得られたので、おとなしく Cisco を使うことにします。

頑張って動的ゲートウェイでつないでる人 (例えば以下) もいるみたいですが、メーカーが明確に未対応と言っているので、良い子は運用環境で使わないようにしましょう。

既存の VNet に P2S VPN を追加

P2S VPN を作る手順が複数ドキュメントに分かれてて面倒なんで、メモがてら。

# makecert で証明書を作成
."C:\Program Files (x86)\Windows Kits\10\bin\x64\makecert.exe" -sky exchange -r -n "CN=VpnRootCert" -pe -a sha1 -len 2048 -ss My "VpnRootCert.cer"
."C:\Program Files (x86)\Windows Kits\10\bin\x64\makecert.exe" -n "CN=VpnClientCert" -pe -sky exchange -m 96 -ss My -in "VpnRootCert" -is my -a sha1

# Win10 なら PowerShell でも証明書が作れるらしいので追記
$cert = New-SelfSignedCertificate -Type Custom -KeySpec Signature -Subject "CN=P2SRootCert" -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 -CertStoreLocation "Cert:\CurrentUser\My" -KeyUsageProperty Sign -KeyUsage CertSign
New-SelfSignedCertificate -Type Custom -DnsName P2SChildCert -KeySpec Signature -Subject "CN=P2SChildCert" -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 -CertStoreLocation "Cert:\CurrentUser\My" -Signer $cert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")
# 事前にクライアント アドレス プール (クライアント端末に払い出す IP レンジ) を追加
$Gw = Get-AzureRmVirtualNetworkGateway -Name "<Gateway 名>" -ResourceGroupName "<RG 名>"
Set-AzureRmVirtualNetworkGatewayVpnClientConfig -VirtualNetworkGateway $Gw -VpnClientAddressPool "<クライアント アドレス プールの IP レンジ>" #192.168.0.0/24 など

# ルート証明書を追加
$RootCertName = "VpnRootCert.cer"
$RootCertPath = "C:\Users\Administrator\Desktop\VpnRootCert.cer"
$RootCertBase64 = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($RootCertPath))

Add-AzureRmVpnClientRootCertificate -VpnClientRootCertificateName $RootCertName -VirtualNetworkGatewayname $Gw.Name -ResourceGroupName $Gw.ResourceGroupName -PublicCertData $RootCertBase64 

# VPN クライアントのダウンロード
$ClientUrl = Get-AzureRmVpnClientPackage -ResourceGroupName $Gw.ResourceGroupName -VirtualNetworkGatewayName $Gw.Name -ProcessorArchitecture Amd64
Invoke-WebRequest $ClientUrl

PowerShell を利用し、仮想ネットワークへのポイント対サイト接続を構成する
https://azure.microsoft.com/ja-jp/documentation/articles/vpn-gateway-howto-point-to-site-rm-ps/

ポイント対サイト構成の自己署名ルート証明書の操作
https://azure.microsoft.com/ja-jp/documentation/articles/vpn-gateway-certificates-point-to-site/

Azure P2S VPN の構築時にルート証明書が追加・取得できない

検証している中で何度かハマったのでメモ。

既存の VPN GW に対して、クライアント アドレス プールを設定していない状況下で Add-AzureRmVpnClientRootCertificate / Get-AzureRmVpnClientRootCertificate を実行すると、以下のようなエラーになります。

Add-AzureRmVpnClientRootCertificate : オブジェクト参照がオブジェクト インスタンスに設定されていません。
発生場所 行:1 文字:1
+ Add-AzureRmVpnClientRootCertificate -VirtualNetworkGatewayName $VNet ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : CloseError: (:) [Add-AzureRmVpnClientRootCertificate]、NullReferenceException
+ FullyQualifiedErrorId : Microsoft.Azure.Commands.Network.AddAzureVpnClientRootCertificateCommand
Get-AzureRmVpnClientRootCertificate : オブジェクト参照がオブジェクト インスタンスに設定されていません。
発生場所 行:1 文字:1
+ Get-AzureRmVpnClientRootCertificate -VirtualNetworkGatewayName $VNet ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : CloseError: (:) [Get-AzureRmVpnClientRootCertificate]、NullReferenceException
    + FullyQualifiedErrorId : Microsoft.Azure.Commands.Network.GetAzureVpnClientRootCertificates

P2S VPN で使用するクライアント アドレス プールをきちんと設定してから再度実行しましょう。

$Gw = Get-AzureRmVirtualNetworkGateway -Name "<Gateway 名>" -ResourceGroupName "<RG 名>"
Set-AzureRmVirtualNetworkGatewayVpnClientConfig -VirtualNetworkGateway $Gw -VpnClientAddressPool "<クライアント アドレス プールの IP レンジ>" #192.168.0.0/24 など

以上。

ExpressRoute / VPN の強制トンネリング

最近 Nano Server をデプロイして遊んでる毎日ですが、Azure のことも書いておこうと思います。

Azure をエンタープライズ用途で使う場合、ExpressRoute か VPN を使うことが多いと思います。ExR と VPN では色々違う部分もありますが、今回は強制トンネリングについて簡単に解説をば。

強制トンネリングって何ぞ

Azure のネットワークを触っていると、強制トンネリングとか、Forced Tunneling という表現をよく使います。何かというと、全ての通信をオンプレ側に向ける設定、さらにかみ砕くと、デフォルト ルートをオンプレに向けてインターネットに直接出ていかないようにするということです。

図にすると以下のような感じ。(図ではオンプレからインターネットに出てますが、オンプレから外に出られないようにすれば、外に出ない様な構成もできます。ただまあ、留意点もいくつかあるので後述。)

(強制トンネリングが無効の場合)
Forced Tunneling1

(強制トンネリングが有効の場合)
Forced Tunneling2

で、具体的にどう設定するの?っていう話が以下。

ExpressRoute での強制トンネリング

ExpressRoute の場合、オンプレからデフォルト ルート (0.0.0.0/0) を広報します。L2 モデルの Equinix の場合はオンプレのルーター側から自分で、その他 IIJ などの L3 モデルで契約している場合は接続プロバイダに依頼しましょう。

この結果、Azure のルーターに BGP でデフォルト ルートすなわち 0.0.0.0/0 宛のパケットをオンプレのルーターへ向ける経路情報が届くので、ルート テーブル (Azure のシステム ルートとか、ユーザー定義ルート: UDR) にない宛先へはすべてオンプレ側にルーティングされていきます。
Forced Tunneling3

ちなみに、良くある間違いとして、UDR で 0.0.0.0/0 宛を Vnet Gateway に向ける方がいますが、技術的に不可です。
下図の通り、確かに VNet Gateway まではルーティングされるのですが、オンプレミス側から BGP で経路が広報されていない限り、VNet Gateway から Edge Router 側へのルートがないため疎通できません。

VPN での強制トンネリング

一方で VPN の場合には UDR を使って、VNet 内のサブネットに対して、0.0.0.0/0 を GatewaySubnet に向けるルートを書きます。

Forced Tunneling4

設定手順は以下ドキュメント参照で。

クラシック (ASM) の場合: https://azure.microsoft.com/ja-jp/documentation/articles/vpn-gateway-about-forced-tunneling/

New-AzureRouteTable –Name "MyRouteTable" –Label "Routing Table for Forced Tunneling" –Location "North Europe"
Set-AzureRoute –RouteTableName "MyRouteTable" –RouteName "DefaultRoute" –AddressPrefix "0.0.0.0/0" –NextHopType VPNGateway
Set-AzureSubnetRouteTable -VNetName "MultiTier-VNet" -SubnetName "Midtier" -RouteTableName "MyRouteTable"
Set-AzureSubnetRouteTable -VNetName "MultiTier-VNet" -SubnetName "Backend" -RouteTableName "MyRouteTable"

リソース マネージャー (ARM) の場合: https://azure.microsoft.com/ja-jp/documentation/articles/vpn-gateway-forced-tunneling-rm/

New-AzureRmRouteTable –Name "MyRouteTable" -ResourceGroupName "ForcedTunneling" –Location "North Europe"
$rt = Get-AzureRmRouteTable –Name "MyRouteTable" -ResourceGroupName "ForcedTunneling"
Add-AzureRmRouteConfig -Name "DefaultRoute" -AddressPrefix "0.0.0.0/0" -NextHopType VirtualNetworkGateway -RouteTable $rt
Set-AzureRmRouteTable -RouteTable $rt
$vnet = Get-AzureRmVirtualNetwork -Name "MultiTier-Vnet" -ResourceGroupName "ForcedTunneling"
Set-AzureRmVirtualNetworkSubnetConfig -Name "MidTier" -VirtualNetwork $vnet -AddressPrefix "10.1.1.0/24" -RouteTable $rt
Set-AzureRmVirtualNetworkSubnetConfig -Name "Backend" -VirtualNetwork $vnet -AddressPrefix "10.1.2.0/24" -RouteTable $rt
Set-AzureRmVirtualNetwork -VirtualNetwork $vnet

※ UDR の設定のほかに、GatewayDefaultSite 等も必要です。詳しくはドキュメントを参照ください。

強制トンネリングを使う際の留意点

公式ブログのほうでも情報書いてますが、強制トンネリングを使うとライセンス認証の通信や、Azure 基盤との通信もオンプレ側に流れてしまいます。この結果、ライセンス認証ができないとか、インターネットに出られない (オンプレから外に出れない場合) 等々が発生します。強制トンネリングは Azure のデフォのルートを利用者側で捻じ曲げている訳なので、色々配慮しないといけないことが出ますが、その辺は覚悟のうえで使いましょう。

ExpressRoute 環境でライセンス認証ができない事象について
https://blogs.technet.microsoft.com/jpaztech/2016/05/16/azure-vm-may-fail-to-activate-over-expressroute/