[2024年12月版] 新しいRemote Desktop Service SALを使ってVisual Studio AMIを試してみた

[2024年12月版] 新しいRemote Desktop Service SALを使ってVisual Studio AMIを試してみた

Clock Icon2024.12.08

しばたです。

少し前にAWS License ManagerでRemote Desktop Services SAL (RDS SAL)の単体購読が可能になった件を紹介しました。

https://dev.classmethod.jp/articles/aws-user-based-subscription-microsoft-remote-desktop-services/

こちらの記事でVisual StudioとMicrosoft Officeのサブスクライブに使うRDS SALも刷新されると記載しました。
本記事では実際にVisual Studioのサブスクリプションを購読する事でどの様な変化があるのか確認しようと思います。

試してみた

それでは早速試していきます。

0. 検証環境

前掲の記事で構築したAWS Managed Microsoft ADとRDS SALのサブスクリプションを現在まで継続しており、今回はこちらの環境を使います。
サブスクライブするソフトウェアは環境構築の容易さと利用費を考慮してVisual Studio Professionalにしました。

1. Visual Studio Professional のサブスクライブ

最初にマネジメントコンソールからAWS License Managerを開き、左ペインにある「設定」→「ユーザーベースのサブスクリプション」をクリックします。
完全な初期状態では下図の様に

  • AWS Marketplaceから当該製品(Visual Studio)のサブスクライブ
  • AWS MarkteplaceからRDS SALのサブスクライブ

を求められるのでAWS Maketplaceに移動して両者をサブスクライブしておきます。

how-to-setup-visual-studio-ami-202412-01

2. Active Directoryの登録

AWS Marketplaceのサブスクライブを完了した後で再度「設定」の「ユーザーベースのサブスクリプション」にアクセスすると今度はActive Directoryとの紐づけを求められます。

how-to-setup-visual-studio-ami-202412-02

「Active Directoryを登録」をクリックすると登録画面に遷移するのでRDS SALと連携済みのAWS Managed Microsoft AD環境を選択して「登録」ボタンをクリックします。

how-to-setup-visual-studio-ami-202412-03

すると登録作業が開始されるので完了までしばらく待ちます。

how-to-setup-visual-studio-ami-202412-04

エラー無く完了するとIDプロバイダーのステータスが「登録済み」になります。

how-to-setup-visual-studio-ami-202412-05

併せてRDS SAL関連のエンドポイント情報も一緒に表示される形となります。

how-to-setup-visual-studio-ami-202412-06

3. EC2インスタンスの起動

次にAWS MarketplaceからVisual Studio AMIを使ってEC2インスタンスを起動します。

本日時点では2024.11.08版が最新バージョンで

  • Windows Server 2022 - 2024.11.08
  • Windows Server 2019 - 2024.11.08
  • Windows Server 2016 - 2024.11.08

の3OS選択可能となっていました。
今回はWindows Server 2022 AMIからEC2インスタンスを起動することにします。

how-to-setup-visual-studio-ami-202412-07

EC2インスタンスの起動方法は任意ですが、所定の権限をもったインスタンスプロファイルの設定を忘れない様にしてください。

how-to-setup-visual-studio-ami-202412-08

how-to-setup-visual-studio-ami-202412-09
Visual Studio AMIではSSMを使うため所定のインスタンスプロファイルの設定が必須

インスタンス作成後しばらく待ち、所定のSSM Run Commandが実施されてActive Directoryドメインに参加できていれば完了です。

how-to-setup-visual-studio-ami-202412-10

このインスタンスがAWS Reserved配下のインスタンス別OUに所属するのは従来通りの挙動です。

how-to-setup-visual-studio-ami-202412-11

ただ、現在はRD Licensing Server等の設定を強制するLicense Manager Policyが親OUから継承されるので自動でRDSライセンスサーバーエンドポイントを使う様になっています。

how-to-setup-visual-studio-ami-202412-12

補足 : (2024年12月版)ドメイン参加スクリプト

また、ドメイン参加スクリプトも変更が入っており、スクリプト内でRDSHの機能を追加する-InstallSessionHostパラメーターが増えていました。
これによりWindowsの役割・機能側の設定も適切な形に行われます。

ドメイン参加スクリプト (RDSHの機能をインストールする関数だけ抜粋)
Function Install-SessionHost {
    Param (
        [String]$Username,
        [String]$DomainRoot,
        [System.Management.Automation.PSCredential]$Credential
    )
    # In order to create a session with a user credential in CredSSP, the user needs to be in local administrators group.
    Write-Output 'Adding LocalGroupMember'
    Add-LocalGroupMember -group administrators -member $Username -ErrorAction SilentlyContinue
    # only enable CredSSP if it's not configured
    $CredSSPStatus = Get-CredSSP

    Write-Output "CredSSPStatus before enabling is. $_"

    if (-not ($CredSSPStatus -match "This computer is configured to receive credentials from a remote client computer.")) {
        Enable-CredSSP -DomainRoot $DomainRoot
        $CredSSPStatus = Get-CredSSP
        Write-Output "CredSSPStatus after enabling is. $_"
    }

    $script = {
        Function Write-TerminatingError {
            <#
 .SYNOPSIS
 write the error to the console and exit the program.
 #>
            Param (
                [String]$Message
            )
            Write-Error $Message
            exit 1
        }

        Import-Module ServerManager -Force
        Add-WindowsFeature rds-rd-server
    }

    # Invoke the script with Reserved OU Admin Credential. This is done because the default system creds with which
    # SSM would run this script, would not have the permission to create or modify GPO.
    $session = New-PSSession -Credential $Credential -Authentication Credssp
    Invoke-Command -session $session -ScriptBlock $script
}

参考情報としてスクリプト全体は以下に載せておきます。

ドメイン参加スクリプト(全体) : クリックして展開
<#
 .SYNOPSIS
 Domain joins an instance to the customers MAD

 .DESCRIPTION
 This script will domain join an instance in the MAD under the given OU, this script will run on customers instance using SSM

 It will perform the follow actions:
 1. Validation : Check if instance is part of domain already and under the correct OU, if not proceed
 2. Install AD tools on the instance
 3. Set the DNS Address to a MAD Domain Controllers IP
 4. Domain join the instance to the computer object already created using One time machine pascode
 5. Exit with status 3010 which tells ssm to restart the computer and rerun the script for validation on 1

As this script is running on the cutomers instance we will not be placing it on the instance rather will be running it by directly passing the script in a SSM RunPowershell document.

#>

#==================================================
# Utility Functions 
#==================================================
Function Write-TerminatingError {
    <#
 .SYNOPSIS
 write the error to the console and exit the program.
 #>
    Param (
        [String]$Message
    )
    throw $Message
}

Function Enable-CredSSP {
    Param(
        [String]$DomainRoot
    )
    <#
 .SYNOPSIS
 Enable local to local Cred SSP for the running computer.
 #>

    Write-Output 'Enabling CredSSP'
    Try {
        Enable-PSRemoting -force -ErrorAction Stop
    } Catch {
        Write-TerminatingError "Failed to enable PS Remoting $_"
    }
    Write-Output 'Enabling CredSSP for Server'
    Try {
        Enable-WSManCredSSP -Role Server -Force -ErrorAction Stop
    } Catch {
        Write-TerminatingError "Failed to enable PS Remoting for Server $_"
    }
    Write-Output 'Enabling CredSSP for Client'
    Try {
        Enable-WSManCredSSP -Role Client -DelegateComputer localhost -Force -ErrorAction Stop
        Enable-WSManCredSSP -Role Client -DelegateComputer $env:COMPUTERNAME -Force -ErrorAction Stop
        Enable-WSManCredSSP -Role Client -DelegateComputer $DomainRoot -Force -ErrorAction Stop
        Enable-WSManCredSSP -Role Client -DelegateComputer "*.$DomainRoot" -Force -ErrorAction Stop
    } Catch {
        Write-TerminatingError "Failed to enable PS Remoting for Client $_"
    }
    Set-Item -Path "wsman:\localhost\service\auth\credSSP" -Value $True -Force
    New-Item -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation -Name AllowFreshCredentialsWhenNTLMOnly -Force
    Try {
        New-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentialsWhenNTLMOnly -Name 1 -Value * -PropertyType String -ErrorAction Stop
    } Catch {
        Write-TerminatingError "Failed to add new registry key to enable CredSSP $_"
    }
}

Function Get-CredSSP {
    return Out-String -InputObject (Get-WSManCredSSP)
}

Function Install-SessionHost {
    Param (
        [String]$Username,
        [String]$DomainRoot,
        [System.Management.Automation.PSCredential]$Credential
    )
    # In order to create a session with a user credential in CredSSP, the user needs to be in local administrators group.
    Write-Output 'Adding LocalGroupMember'
    Add-LocalGroupMember -group administrators -member $Username -ErrorAction SilentlyContinue
    # only enable CredSSP if it's not configured
    $CredSSPStatus = Get-CredSSP

    Write-Output "CredSSPStatus before enabling is. $_"

    if (-not ($CredSSPStatus -match "This computer is configured to receive credentials from a remote client computer.")) {
        Enable-CredSSP -DomainRoot $DomainRoot
        $CredSSPStatus = Get-CredSSP
        Write-Output "CredSSPStatus after enabling is. $_"
    }

    $script = {
        Function Write-TerminatingError {
            <#
 .SYNOPSIS
 write the error to the console and exit the program.
 #>
            Param (
                [String]$Message
            )
            Write-Error $Message
            exit 1
        }

        Import-Module ServerManager -Force
        Add-WindowsFeature rds-rd-server
    }

    # Invoke the script with Reserved OU Admin Credential. This is done because the default system creds with which
    # SSM would run this script, would not have the permission to create or modify GPO.
    $session = New-PSSession -Credential $Credential -Authentication Credssp
    Invoke-Command -session $session -ScriptBlock $script
}

Function Write-Log {
    Param (
        [String]$Message
    )
    Write-Host "$(Get-Date) - $Message"
}

Function Install-ADTools {
    $attempts = 3
    $sleepInSeconds = 1
    do {
        try {
            Write-Log -Message 'Installing AD tools on the instance...'
            Install-WindowsFeature -Name 'RSAT-AD-Tools' -ErrorAction Stop
            Write-Log -Message 'Successfully installed AD tools on the instance...'
            break;
        } catch {
            Write-Log -Message "Unable to install AD tools on the machine, exception: $_"
            if ($attempts -eq 1) {
                Write-TerminatingError -Message "Unable to install AD tools on the machine, failing error: $_"
            }
        }
        Write-Log -Message 'Will retry installing tools...'
        $attempts--
        if ($attempts -gt 0) { sleep $sleepInSeconds }
    } while ($attempts -gt 0)
}

Function Remove-Computer-From-CurrentDomain {
    <#
 .SYNOPSIS
 In case of recovery, we also need to remove it from any domain explicitly even if it's already in the target domain.
 #>
    Write-Log -Message 'Removing the computer from the current domain if it is in any domain, failure to do so will not terminate the script.'
    $hostname = hostname
    netdom remove $hostname /domain: /force
}

Function Get-GP-Update-Output {

    Param(
        [string]$Domain,
        [string]$GPErrorMsg,
        [string]$CurrentDomain

    )
    #Only check for GPUpdate result if computer is part of domain
    if ($CurrentDomain -eq $Domain) {
        $attempts = 15
        $sleepInSeconds = 5
        do {
            $GPUpdateResult = gpupdate /force
            $GPUpdateResult = Out-String -InputObject $GPUpdateResult
            Write-Log -Message "GPUpdateResult : $GPUpdateResult"
            Start-Process gpupdate -ArgumentList "/force" -NoNewWindow -Wait
            $exitCode = $LASTEXITCODE
            Write-Log -Message "exitCode : $exitCode"
            if (-not ($GPUpdateResult -match $GPErrorMsg)) {
                return $GPUpdateResult
            }
            Write-Log -Message "Retrying GPUpdateResult"
            $attempts--
            sleep $sleepInSeconds
        } while ($attempts -gt 0)
        return $GPUpdateResult
    }
    Write-Log -Message "Computer is not domain joined so its the first run hence skipping GPUpdateResult"
    return ""
}

Function Successfully-Exit-And-Restart-Instance {
    exit 3010
}

#==================================================
# Main
#==================================================
Function Domain-Join-Instance {
    <# The script accepts the below parameters
 DCIpAddress1,DCIpAddress2 : Domain Controller IPs for DCs of the directory, Domain: customers domain, OneTimeMachinePasscode : Password for the Computer Object for this instance
 #>
    Param(
        [string]$DCIpAddress1,
        [string]$DCIpAddress2,
        [string]$Domain,
        [string]$OneTimeMachinePasscode,
        [string]$ROUserPass,
        [string]$ROUsername,
        [string]$Region,
        [string]$InstanceId,
        [string]$IsOfficeProductCode,
        [string]$InstallSessionHost,
        [string]$AdminUsername,
        [string]$AdminPassword
    )

    $LMReservedOUName = 'LicenseManager'
    $sleepInSeconds = 1

    Write-Log -Message "****** OneTimeMachinePasscode: $OneTimeMachinePasscode *******"

    try {
        $CurrentDomain = Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction Stop | Select-Object -ExpandProperty 'Domain'
    } catch [System.Exception] {
        Write-TerminatingError -Message "Unable to Computer details to check current domain: $_"
    }

    # We validate if gpo update is working, if not we need to perform the domain join.
    # when we are recovering a broken instance, it's possible that the local domain name is right but the computer object has been replaced.
    Write-Log -Message "Checking GPUpdateResult"
    $GPErrorMsg = 'Computer policy could not be updated successfully.'
    $GPUpdateResult = Get-GP-Update-Output -Domain $Domain -GPErrorMsg $GPErrorMsg -CurrentDomain $CurrentDomain
    Write-Log -Message "CurrentDomain of the instance: $CurrentDomain and GP update output is $GPUpdateResult"
    $GPUpdateResult
    if (($CurrentDomain -eq $Domain) -and (-not ($GPUpdateResult -match $GPErrorMsg))) {
        Write-Log -Message "Instance is part of domain: $CurrentDomain"

        $username = "$Domain\$ROUsername"
        $password = $ROUserPass | ConvertTo-SecureString -asPlainText -Force
        $credential = New-Object System.Management.Automation.PSCredential($username, $password)

        #Getting OU Path of computer object
        $attempts = 3
        do {
            try {
                Write-Log -Message "Getting AD Computer details for computer : $ENV:ComputerName"
                $ADComputer = Get-ADComputer -Identity $ENV:ComputerName -Credential $credential -ErrorAction Stop
                break;
            } catch [System.Exception] {
                Write-Log -Message "Unable to get AD Computer details..., exception: $_"
                if ($attempts -eq 1) {
                    Write-TerminatingError -Message "Unable to fetch details for the computer object: $_"
                }
            }
            $attempts--
            if ($attempts -gt 0) { sleep $sleepInSeconds }
        } while ($attempts -gt 0)

        $DistinguishedName = $ADComputer | Select-Object -ExpandProperty DistinguishedName
        # Example for DistinguishedName : CN=EC2AMAZ-PCQS7MN,OU=i-05f83f681c65dfd6c-OU,OU=IAD,OU=LicenseManager,OU=AWS Reserved,DC=example,DC=com

        $OUPathArr = $DistinguishedName -split ',', 2
        $CurrentOUPath = $OUPathArr[1]

        #Building the OU Path from customers domain and reserved ou path
        $attempts = 3
        do {
            try {
                Write-Log -Message 'Getting AD Domain details...'
                $ADDomain = Get-ADDomain -credential $credential -ErrorAction Stop
                break;
            } catch [System.Exception] {
                Write-Log -Message "Unable to get AD Domain details......, exception: $_"
                if ($attempts -eq 1) {
                    Write-TerminatingError -Message "Unable to fetch AD domain details: $_"
                }
            }
            $attempts--
            if ($attempts -gt 0) { sleep $sleepInSeconds }
        } while ($attempts -gt 0)

        $DomainFQDN = $ADDomain.DNSRoot
        $BasePath = $ADDomain.DistinguishedName
        $ReservedOUPath = "OU=$LMReservedOUName,OU=AWS Reserved"
        $ExpectedOUPath = "OU=$InstanceId-OU,OU=$Region,$ReservedOUPath,$BasePath"

        #SUCCESS : If computer is part of OU - exit 0
        if ($CurrentOUPath -eq $ExpectedOUPath) {
            Write-Log -Message "Success... Instance was domain joined under the correct OU, at path: $ExpectedOUPath"
            Get-LocalGroupMember -Group “Administrators”
            Write-Log -Message "---Adding the Domain Admins to the Administrators group---"
            Add-LocalGroupMember -Group “Administrators” -Member “$env:userdomain\AWS Delegated Server Administrators”
            Get-LocalGroupMember -Group “Administrators”

            # Check common registry paths for pending reboot status
            $pendingReboot = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\").RebootPending -or
 (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\").RebootRequired -or
 (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\").PendingFileRenameOperations

            if ($pendingReboot) {
                Write-Output "A reboot is pending."
            } else {
                Write-Output "No reboot is pending."
            }

            return
        } else {
            Write-TerminatingError -Message "Failure... Instance is joined under OU path: $CurrentOUPath `nInstead of expected OU path: $ExpectedOUPath"
        }
    } else {
        Write-Log -Message "Instance is not part of domain so proceeding to join it to the domain: $Domain"
    }

    #Remove-Computer-From-CurrentDomain
    Remove-Computer-From-CurrentDomain

    #Install AD Tools
    Install-ADTools

    Write-Log -Message "Office instance launched: $IsOfficeProductCode"

    # Set DNS address only for VS. For Office we are taking Route53 resolver approach
    if ($IsOfficeProductCode -eq 'false') {
        Write-Log -Message 'Setting the DNS address to MAD Domain Controller IP for VS Product...'
        try {
            $dns = Get-DnsClientServerAddress
            $OriginalDNSAddress = $dns.ServerAddresses[0]
            Write-Log -Message ("Current Value for index " + $dns.InterfaceIndex[0] + "-" + $dns.ServerAddresses)
            Write-Log -Message "OriginalDNSAddress : $OriginalDNSAddress"
            Set-DnsClientServerAddress -InterfaceIndex $dns.InterfaceIndex[0] -ServerAddresses ($DCIpAddress1, $DCIpAddress2) -ErrorAction Stop
            $dns = Get-DnsClientServerAddress
            Write-Log -Message ("Updated Value for index " + $dns.InterfaceIndex[0] + "-" + $dns.ServerAddresses)
            Write-Log -Message 'Successfully! updated the DNS address to MAD Domain Controller IP...'
        } catch [System.Exception] {
            Write-TerminatingError -Message "Unable to set DNS address: $_"
        }
    }

    #Domain join this instance to the AD and connect to the Computer Object created already using OneTimeMachinePasscode
    $OneTimeMachinePasscodeSecure = $OneTimeMachinePasscode | ConvertTo-SecureString -AsPlainText -Force
    Write-Log -Message 'Domain joining the instance...'
    try {
        $JoinCred = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList ([PSCustomObject]@{
                UserName = $Null
                Password = ($OneTimeMachinePasscodeSecure)[0]
            })
        Add-Computer -Domain $Domain -Options UnsecuredJoin, PasswordPass -Credential $JoinCred -ErrorAction Stop
        Write-Log -Message 'Successfully! domain joined the instance'
    } catch [System.Exception] {
        if ($IsOfficeProductCode -eq 'false') {
            Set-DnsClientServerAddress -InterfaceIndex $dns.InterfaceIndex[0] -ServerAddresses $OriginalDNSAddress -ErrorAction Stop
            Write-Log -Message "Reset the dns name for VS as the domain join failed to : $OriginalDNSAddress"
        }
        Write-TerminatingError -Message "Unable to domain join the instance: $_"
    }

    $attempts = 3
    $sleepInSeconds = 1
    do {
        try {
            Write-Log -Message 'Getting AD Domain details...'
            $username = "$Domain\$ROUsername"
            $password = $ROUserPass | ConvertTo-SecureString -asPlainText -Force
            $credential = New-Object System.Management.Automation.PSCredential($username, $password)
            $ADDomain = Get-ADDomain -credential $credential -server $DCIpAddress1 -ErrorAction Stop
            Write-Log -Message 'Successfully retrieved ADDomain...'

            if ($InstallSessionHost -eq 'true') {
                $adminUsername = "$Domain\$AdminUsername"
                $adminPwd = $AdminPassword | ConvertTo-SecureString -asPlainText -Force
                $adminCredential = New-Object System.Management.Automation.PSCredential($adminUsername, $adminPwd)
                Write-Log -Message 'Installing session host...'
                Install-SessionHost -Username $adminUsername -DomainRoot $ADDomain.DNSRoot -Credential $adminCredential
                Write-Log -Message 'Installed session host... Checking installation status'
                Get-WindowsFeature -Name RDS-RD-Server
            }
            break;
        } catch [System.Exception] {
            Write-Log -Message "Unable to Install Session Host, exception: $_"
            if ($attempts -eq 1) {
                Write-TerminatingError -Message "Failing : Unable to Install Session Host: $_"
            }
        }
        $attempts--
        if ($attempts -gt 0) { sleep $sleepInSeconds }
    } while ($attempts -gt 0)

    Write-Log -Message 'Restarting the instance to reflect changes...'
    #exit 3010 sends a request to ssm to restart instance and rerun the script again : https://docs.aws.amazon.com/systems-manager/latest/userguide/send-commands-reboot.html
    Successfully-Exit-And-Restart-Instance
}

#Function call that runs the script, it will be replaced in code with param and values while calling the script
Function Main {
    Param(
        [string]$DCIpAddress1,
        [string]$DCIpAddress2,
        [string]$Domain,
        [string]$OneTimeMachinePasscode,
        [string]$ROUserPass,
        [string]$ROUsername,
        [string]$Region,
        [string]$InstanceId,
        [string]$IsOfficeProductCode,
        [string]$InstallSessionHost,
        [string]$AdminUsername,
        [string]$AdminPassword
    )

    Domain-Join-Instance -DCIpAddress1 $DCIpAddress1 -DCIpAddress2 $DCIpAddress2 -Domain $Domain -OneTimeMachinePasscode $OneTimeMachinePasscode -ROUsername $ROUsername -ROUserPass $ROUserPass -Region $Region -InstanceId $InstanceId -IsOfficeProductCode $IsOfficeProductCode -InstallSessionHost $InstallSessionHost -AdminUsername $AdminUsername -AdminPassword $AdminPassword
}
Main -DCIpAddress1 '10.0.21.34' -DCIpAddress2 '10.0.22.141' -Domain 'corp.contoso.com' -OneTimeMachinePasscode 'xxxxxxxxxxxxxxxxxxxxxxxxxx' -ROUsername 'RO-LM-NRT' -ROUserPass 'xxxxxxxxxxxxxxxxxxxxxxxxxx' -Region 'NRT' -InstanceId 'i-010d5dd65f9e774a1' -IsOfficeProductCode 'false' -InstallSessionHost 'true' -AdminUsername 'admin' -AdminPassword 'xxxxxxxxxxxxxxxxxxxxxxxxxx'

4. Visual Studio Professionalのサブスクライブ

マネジメントコンソールからAWS License Managerの「ユーザーベースのサブスクリプション」→「ユーザーの関連付け」を選ぶとインスタンス一覧が表示されます。

how-to-setup-visual-studio-ami-202412-13

ここから「ユーザーをサブスクライブして関連付ける」をしてやればVisual Studio Professionalのサブクライブとインスタンスの紐づけを一気に行えます。

how-to-setup-visual-studio-ami-202412-14

ユーザーの関連付けが完了すればインスタンスにRDP接続可能になります。

how-to-setup-visual-studio-ami-202412-15

how-to-setup-visual-studio-ami-202412-16

今回は意図的に一度サブスクリプションを解除したadminユーザーで登録してみたのですが、RDS SALのサブスクリプションが再登録される形になっていました。

how-to-setup-visual-studio-ami-202412-17

Visual Studioの方はこんな感じです。

how-to-setup-visual-studio-ami-202412-18

5. EC2インスタンスへの接続

最後にEC2インスタンスにRDP接続してやります。

how-to-setup-visual-studio-ami-202412-19

このインスタンスにはRDSH自体はインストールされているのですが「リモート デスクトップ ライセンス診断ツール」は未インストールだったのでPowerShellコマンドからRD Licensin Serverの指定状況を確認してやります。

EC2内部でRemote Desktop Service関連の設定を調査
# 機能がインストールされているか確認
Get-WindowsFeature -Name RDS-RD-Server

# ライセンスサーバー設定等の確認
$obj = Get-WmiObject -Namespace "Root/CIMV2/TerminalServices" Win32_TerminalServiceSetting
Write-Output ("サーバーモード : {0}" -f $obj.TerminalServerMode)
Write-Output ("現在のライセンスタイプ : {0} ({1})" -f $obj.LicensingType, $obj.LicensingName)
Write-Output ("ライセンスサーバー : {0}" -f $($obj.GetSpecifiedLicenseServerList().SpecifiedLSList))

結果は下図の通り、適切なライセンスタイプ(接続ユーザー数)でRDSライセンスサーバーエンドポイントの指定がされていました。

  • サーバーモード : 1 (AppServer) → RDSHサーバーとして構成済み
  • 現在のライセンスタイプ : 4 (接続ユーザー数) → RDS SALを利用するための適切な設定
  • ライセンスサーバー : RDSエンドポイントのホスト名が設定済み → RDS SALを利用するための適切な設定

how-to-setup-visual-studio-ami-202412-20

グループポリシーによる強制なので当然と言えば当然の結果ではあります。

Visual Studio AMI提供当初とは異なり、2024年12月現在では適切にRD Licensing Serverの設定がされていることが実際に確認できました。

おわりに

以上となります。

本記事では費用的に3人以上での同時接続まで実行することは出来ませんが、この設定であれば間違いなく3人以上で同時接続可能です。
開発者が使用するEC2を集約することで開発環境の統一化やインスタンスの稼働費を抑えることできるので実施可能な環境であれば検討してみると良いでしょう。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.