PowerShellでEC2(Windows Server)のグローバルIPアドレスをRDPファイルのプロパティにセットする

#はじめに

AWS CLIを使ってEC2 Windows ServerのグローバルIPアドレスを取得し、
リモートデスクトップ接続（.rdpファイル）のプロパティを自動的に変更する処理を紹介します。

AWS EC2 の Windows Server を検証用に利用している場合、ElasticIP（固定IPアドレス）を利用せず、動的IPアドレスで必要な時にだけ起動して運用している方も多いかと思います。

しかし、動的IPアドレスで運用している場合は、クライアント側のリモートデスクトップ接続(RDP)ファイルの接続先も毎回変更しなければならず、不便に感じていたため、グローバルIPアドレスを取得してRDPファイルのプロパティを変更する処理をPowerShellで作成してみました。

GitHubにて、ソースコードも公開しています。
[https://github.com/gx3n-inue/ec2_win_set_rdp](https://github.com/gx3n-inue/ec2_win_set_rdp)

# aws ec2コマンドでグローバルIPアドレスを取得する
AWS EC2のグローバルIPアドレスは、PowerShellから`aws ec2 describe-instances`コマンドを実行して取得します。

`aws ec2`の出力フォーマット[^1]には、`json`, `text`, `table` の３種類がありますが、ここでは`text`形式と`json`形式のそれぞれに対応したスクリプトを紹介します。

##text形式の場合(`aws ec2 describe-instances --query`で検索する)
`aws ec2 describe-instances --query`コマンドの出力結果を`text`形式で取得し、InstanceIdが一致するレコードのPublicIpAddressフィールドの値を出力します。

```PowerShell:get_Win_PublicIpAddress_by_text.ps1
param( $InstanceId )

if (-Not($InstanceId)) {
    Write-Host "Usage : "$MyInvocation.MyCommand.Name" InstanceId" -ForegroundColor Red
    exit
}

<#
aws ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId, PublicIpAddress]' --output text |
%{if ($_.split("`t")[0] -match $InstanceId) { $_.split("`t")[1]; } }
#>

$res = & "aws" ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId, PublicIpAddress]' --output text

foreach ($var in $res) {
    if ($var.split("`t")[0] -match $InstanceId) {
        return $var.split("`t")[1]
    }
}

return "None"
```

##json形式の場合

`aws ec2 describe-instances`コマンドの出力結果は、起動中のインスタンスごとにブロックが分かれているので、インスタンス番号を探し、さらにそのブロック内にあるPublicIpAddressフィールドに格納されているIPアドレス文字列のみを返すようにしています。

```PowerShell:get_Win_PublicIpAddress_by_json.ps1
param( $InstanceId )

if (-Not($InstanceId)) {
    Write-Host "Usage : "$MyInvocation.MyCommand.Name"InstanceId rdp_filePath" -ForegroundColor Red
    exit
}

Write-Host "<"$MyInvocation.MyCommand.Name">" -ForegroundColor Yellow
Write-Host "Searhing InstanceId = " -NoNewline
Write-Host $InstanceId -ForegroundColor Green

$res = & "aws" ec2 describe-instances --output json

$Check_MS_Windows = $FALSE
$Check_publicIp = $FALSE

for ($i = 0; $i -lt $res.Length; $i++) {
    ##--------------------------------------------------------------##
    ## PlatformがWindowsかどうかを判定する
    ##--------------------------------------------------------------##
    if ($res[$i].IndexOf("`"Platform`": `"windows`"") -ge 0) {
        $Check_MS_Windows = $TRUE
    }
    
    if ($Check_MS_Windows -eq $TRUE) {
        ##--------------------------------------------------------------##
        ## PublicIpAddressの値を取得する
        ##--------------------------------------------------------------##
        if ($res[$i].IndexOf("`"PublicIpAddress`":") -ge 0) {
            $workStr = $res[$i].Replace(" ","").Replace("`"","").Replace(",","").split(":")
            if ($workStr.Length -gt 0) {
                $publicIpStr = $workStr[1]
                $Check_publicIp = $TRUE
            }
        }

        ##--------------------------------------------------------------##
        ## InstanceIdが見つかったらpublicIpを返す
        ##--------------------------------------------------------------##
        if ($Check_publicIp -eq $TRUE) {
            if ($res[$i].IndexOf("`"InstanceId`":") -ge 0) {
                if ($res[$i].IndexOf("`"$InstanceId`"") -ge 0) {
                    Write-Host "publicIpAddress : " -NoNewline
                    Write-Host $publicIpStr -ForegroundColor Cyan
                    Write-Host

                    return $publicIpStr
                }
                else {
                    $Check_publicIp = $FALSE
                }
            }
        }
    }
}

# 見つからなかった場合はnullを返す
return "None"
```

全レコードから検索する処理を書いてしまったため、少し長くなってしまいましたが、

```
aws ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId, PublicIpAddress]' --output json
```

の出力結果からInstanceIdを探し、その次のフィールドに出力されている値を取り出しても良いかもしれません。（その方が余計なトラフィックが流れない）


# リモートデスクトップ接続の接続先プロパティに取得したIPアドレスをセットする
リモートデスクトップ接続の接続先プロパティに取得したIPアドレスをセットする処理を紹介します。RDPファイルをテキストエディタで開いてみると解りますが、接続先のホスト名またはIPアドレスは、"full address:s:"というフィールドの値として保存されていますので、その値の文字列（FQDNまたはIPアドレス）を取得したIPアドレスに書き換え、RDPファイルに書き戻します。念のため、現在のRDPファイルには上書きせず、最終更新日時を付加したファイル名にリネームして残すようにしています。

###

```PowerShell:overwrite_rdp.ps1
param( $rdp_filePath, $publicIp )

##--------------------------------------------------------##
## RDPファイルの読み込み
##--------------------------------------------------------##
function load_RDP_File_and_ipChange([string]$filePath)
{
    $f = (Get-Content $filePath) -as [string[]]
    $lines = @()

    foreach ($currentLine in $f){

        # 接続先IPアドレス情報の検索
        if ($currentLine.IndexOf("full address:s:") -eq 0) {
        
            $workStr = $currentLine.Split(":")
            
            if ($workStr.Length -eq 3) {
                $new_fullAddr = $workStr[0] + ":" + $workStr[1] + ":" + $publicIp

                Write-Host "Current Value ... " -NoNewline
                Write-Host $currentLine -ForegroundColor Cyan
                Write-Host "New     Value ... " -NoNewline
                Write-Host $new_fullAddr -ForegroundColor Cyan
                Write-Host

                # キー入力を調べる
                if ((Check_ReadKey $filePath) -eq $FALSE) {
                    # "n"が入力された場合は終了する
                    return $FALSE
                }

                $lines += $new_fullAddr
            }
            else {
                Write-Host "full address get Error!"
                return $FALSE
            }
        }
        else {
            $lines += $currentLine
        }
    }

    return($lines)
}

##--------------------------------------------------------##
## キー入力チェック
##--------------------------------------------------------##
function Check_ReadKey([string]$filePath)
{
    while ($TRUE) {
        Write-Host "["$filePath"]" -NoNewline -ForegroundColor Yellow
        Write-Host " Overwrite? y/n [y]:" -NoNewline

        # キー入力の読み込み
        $keyInfo = [Console]::ReadKey($TRUE)

        Write-Host

        if (($keyInfo.Key -eq "N") -Or ($keyInfo.Key -eq "n")) {
            Write-Host "Canceled."
            Write-Host
            return $FALSE
        }
        elseif (($keyInfo.Key -eq "Y") -Or ($keyInfo.Key -eq "y")) {
            Write-Host
            return $TRUE
        }
        elseif ($keyInfo.Key -eq "Enter") {
            Write-Host
            return $TRUE
        }
    }
}


##--------------------------------------------------------##
## メイン
##--------------------------------------------------------##
Write-Host "<"$MyInvocation.MyCommand.Name">" -ForegroundColor Yellow

if (-Not($publicIp)) {
    Write-Host "Usage : "$MyInvocation.MyCommand.Name"publicIp rdp_filePath" -ForegroundColor Red
    exit
}

if (-Not($rdp_filePath)) {
    Write-Host "Usage : "$MyInvocation.MyCommand.Name"publicIp rdp_filePath" -ForegroundColor Red
    exit
}

# rdpファイルが存在しているかどうか調べる
if ((Test-Path($rdp_filePath)) -eq $FALSE) {
    Write-Host "["$rdp_filePath"] is not found." -ForegroundColor Red
    exit
}

$lines = @()

# RDPファイルを読み込む
$lines = load_RDP_File_and_ipChange $rdp_filePath

if ($lines -eq $FALSE) {
    return $FALSE
}

# 現在の日付時刻を取得する
$timestamp = $(Get-ItemProperty $rdp_filePath).LastWriteTime.ToString('_yyyyMMdd_HHmmss')

# 現在のファイルをリネームしておく
$oldFileName = $rdp_filePath.Replace(".rdp", $timestamp + ".rdp")
Move-Item $rdp_filePath $oldFileName

Write-Host "["$rdp_filePath"]" -NoNewline -ForegroundColor Yellow
Write-Host " Rename to "
Write-Host "["$oldFileName"]" -ForegroundColor Yellow
Write-Host

# RDPファイルに書き込む
Set-Content -Path $rdp_filePath -Value $lines -Encoding Unicode

# 書き込み終了メッセージ
Write-Host "["$rdp_filePath"]" -NoNewline -ForegroundColor Yellow
Write-Host " was saved."
Write-Host

return $TRUE
```

#メインプログラム
次に、メインプログラムです。さきほどのget_publicIP.ps1（グローバルIPアドレスを取得するスクリプト）を実行してグローバルIPアドレスを取得後、overwrite_rdp.ps1（RDPファイルのプロパティを変更するスクリプト）でRDPファイルを更新します。更新後は、リモートデスクトップ接続を起動させています。
###
```PowerShell:ec2_win_set_rdp.ps1
param( $id, $rdp_filePath )

##--------------------------------------------------------##
## メイン
##--------------------------------------------------------##
if (-Not($id)) {
    $id = "ここにインスタンスIDをセットしてください"
}

if (-Not($rdp_filePath)) {
    $rdp_filePath = "ここにRDPファイルのパスをセットしてください"
}

if ($rdp_filePath.Substring(0,1) -ne ".") {
    $rdp_filePath = ".\" + $rdp_filePath
}

Write-Host "<"$MyInvocation.MyCommand.Name">" -ForegroundColor Yellow

# 指定した[InstanceId]の[PublicIpAddress]を取得
# $publicIp = &".\get_Win_PublicIpAddress_by_text.ps1" $id    # text形式での検索
$publicIp = &".\get_Win_PublicIpAddress_by_json.ps1" $id      # json形式での検索

if ($publicIp -eq "None") {
    Write-Host "publicIp is Nothing."
    exit
}

# 指定したrdpファイルの接続先IPアドレスを上書きする
$result = .\overwrite_rdp.ps1 $rdp_filePath $publicIp

if ($result -eq $TRUE) {
    Write-Host "Start " -NoNewline
    Write-Host "["$rdp_filePath"]" -ForegroundColor Yellow

    # リモートデスクトップ接続を起動する
    &$rdp_filePath
}
```

#実行方法
実行方法は以下のとおりです。

```
PS > .\ec2_win_set_rdp.ps1 <InstancdId> <RDP filePath>
```
インスタンス番号およびRDPファイルのパスは、ec2_win_set_rdp.ps1（メイン処理）の

``````PowerShell:
$id = "ここにインスタンスIDをセットしてください"
$rdp_filePath = "ここにRDPファイルのパスをセットしてください"
```
の箇所に直接記述することもできます。

#実行例
```
PS D:\ec2_win_set_rdp> .\ec2_win_set_rdp.ps1 i-xxxxxxxxxxxxxxxxx .\sample.rdp
< ec2_win_set_rdp.ps1 >
< get_publicIP.ps1 >
Searhing InstanceId = i-xxxxxxxxxxxxxxxxx
publicIpAddress : xx.xxx.xxx.xx

< overwrite_rdp.ps1 >
Current Value ... full address:s:xx.xxx.xxx.xx
New     Value ... full address:s:xx.xxx.xxx.xx

[ .\sample.rdp] Overwrite? y/n [y]:

[ .\sample.rdp] Rename to
[ .\sample_20180806_233719.rdp]

[ .\sample.rdp] was saved.

Start [ .\sample.rdp]
PS D:\ec2_win_set_rdp>
```

[^1]: Default output formatは json、text、table の3種類があり、設定内容は`C:\Users\USERNAME \.aws\config`に格納されています。デフォルトは json です。`aws configure`コマンドで確認および変更できます。