PowerShellでAWSにインスタンス作ってWinRMで初期構築したい

# はじめに

PowerShellでAWSにインスタンス作って、そのまま WinRM で繋いで初期構築まで自動化したい、と以前から思い続けていたのですが、この度やっと着手したのでメモ書きしておきます。

各所雑かもしれないのでツッコミ待ちです。

# 準備

AWS Tools を使えるようにする必要があるので、以下からダウンロードしてインストールします。PowerShell 5.0 を使っている場合は、`Install-Module -Name AWSPowerShell` でもインストールできるみたいです。

[AWS Tools for Windows PowerShell | AWS](https://aws.amazon.com/jp/powershell/)

AWS Tools が使えるようになったことを確認します。

```powershell
PS> Get-Module -Name AWSPowerShell

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Binary     3.3.130.0  AWSPowerShell                       {Add-AASScalableTarget, Add-ACMCertificateTag, Add-ADSConf...

```

PowerShell から AWS にアクセスするためには、IAM アカウントのアクセスキーとシークレットキーが必要なので、以下のリンクを参照してキーを入手します。

[AWS アカウントとアクセスキー - AWS Tools for Windows PowerShell](https://docs.aws.amazon.com/ja_jp/powershell/latest/userguide/pstools-appendix-sign-up.html)

IAM アカウントを作成（キーを入手）できたら、以下のコマンドでキーを保存します。`{AccessKey}` と `{SecretKey}` は実際のキーに読み替えてください。このとき保存したキーは、永続的に（PCを再起動しても）保存されます。

```powershell
PS> Set-AWSCredential -AccessKey {AccessKey} -SecretKey {SecretKey} -StoreAs MyProfileName
```

AWS Tools のコマンドを実行するたびにプロファイルを指定するのは面倒なので、デフォルトのプロファイル（とリージョン）を決めておくことが推奨されています。リージョンは適当に東京(ap-northeast-1)にしておきます。

```powershell
PS> Initialize-AWSDefaultConfiguration -ProfileName MyProfileName -Region ap-northeast-1
```

以上で準備は終了です。

# AWSにインスタンス作って WinRM でつなぐ

インスタンスの作成には `New-EC2Instance` コマンドを使います。が、いくつか前提があるので順を追います。

まず AWS インスタンスのパスワードを復元するためにキーペアを作成しておく必要があります。作成したキーペアは自分しかアクセスできないフォルダに保管します。

```powershell
$myPSKeyPair = New-EC2KeyPair -KeyName myPSKeyPair
$myPSKeyPair.KeyMaterial | Out-File -Encoding ascii myPSKeyPair.pem
```

続いて、セキュリティグループを作成します。ハッシュテーブルでルールを作ると見通しが良いと思います。

```powershell
$sgid = New-EC2SecurityGroup -GroupName "test" -Description "hoge"
$RDP    = @{ IpProtocol="TCP";  FromPort="3389"; ToPort="3389"; IpRanges="0.0.0.0/0" }
$SMB    = @{ IpProtocol="TCP";  FromPort="445";  ToPort="445";  IpRanges="0.0.0.0/0" }
$WinRM  = @{ IpProtocol="TCP";  FromPort="5985"; ToPort="5985"; IpRanges="0.0.0.0/0" }
$ICMPv4 = @{ IpProtocol="ICMP"; FromPort="-1";   ToPort="-1";   IpRanges="0.0.0.0/0" }
Grant-EC2SecurityGroupIngress -GroupId $sgid -IpPermissions @($RDP, $SMB, $WinRM, $ICMPv4)
```

そろそろインスタンス作成に移りたいのですが、WinRM は Windows ファイアウォール（Publicプロファイルの場合）で拒否されているので、UserData を作っておきます。UserData は、AWS にインスタンスを作った直後に自動で実行されるスクリプトで、PowerShell のコマンドを書くことができます。UserData は Base64 でエンコードしておく必要があります。

```powershell
$Text = @"
<powershell>
Get-NetFirewallRule -Name WINRM-HTTP-In-TCP-Public | Set-NetFirewallRule -RemoteAddress Any
</powershell>
"@
$UTF8 = [Text.Encoding]::UTF8
$Bytes = $UTF8.GetBytes($Text)
$UserData = [Convert]::ToBase64String($Bytes)
```

はい、準備ができたのでインスタンスを作ります。無料枠の Windows Server 2016 にリモデしたりすると非常に重いので t2.medium ぐらいにしておきましょう。インスタンスは Reservation というグループで作られるらしく、コマンド1発で複数のインスタンスを起動できるようです。今回は手順の確認だけなので、1インスタンスだけ作成します。

```powershell
$Win2016 = "ami-0ecf3c199663ebcde"
$Reservation = New-EC2Instance -ImageId $Win2016 -KeyName myPSKeyPair -SecurityGroupId $sgid -InstanceType t2.medium -UserData $UserData
```

作成した直後のインスタンスは pending ステータスなので、ステータスが安定するまで待つ必要があります。インスタンスのステータスは `$Reservation.Instances` で取得します。

```powershell
$Instance = $Reservation.Instances | Select-Object -First 1
while ($Instance.state.code -NE 16) {
   Start-Sleep -Seconds 60
   $Reservation = Get-EC2Instance -InstanceId $Instance.InstanceId
   $Instance = $Reservation.instances | Select-Object -First 1
}
# ステータスが running(16) になっていても、
# 実はまだ接続できないので待ちます。
Start-Sleep -Seconds 300
```

インスタンスが起動しきったら、キーペアを使ってインスタンスへのログインパスワードを取得します。

```powershell
$Password = Get-EC2PasswordData -InstanceId $Instance.InstanceId -PemFile "MyPSKeyPair.pem"
```

パスワードは、おなじみのコマンドで SecureString へ変換し、認証情報を作ります。

```powershell
$SecureString = ConvertTo-SecureString -AsPlainText -Force -String $Password
$Cred = New-Object System.Management.Automation.PSCredential("Administrator",$SecureString)
```

以下のようにして、インスタンスのホスト名が取得できれば成功です。

```powershell
$Session = New-PSSession -ComputerName $Instance.PublicDnsName -Credential $Cred
Invoke-Command -Session $Session -ScriptBlock { hostname }
```

あとは `Install-WindowsFeature` で煮るなり焼くなり。

# 終わりに

CloudFormation とか TerraForm でも似たようなことができたと思うのですが、PowerShell の世界だけで完結できたので満足です。