0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

踏み台サーバ経由のRDPを自動化する

Posted at

前置き

踏み台サーバのポートフォワード経由でWindows Serverにリモートデスクトップ接続するのを楽にするためのツールを作成した。
踏み台サーバにTeraTermでSSH接続してから使う想定なので、TeraTermの転送設定(TERATERM.INI)の変更も必要です。前提条件は以下

  • SSH クライアント(TeraTerm)がインストール済み
  • 踏み台サーバーへの SSH 鍵認証設定済み
  • mstsc.exe(RDP) が利用可能
  • .NET Framework 4.5以上

例)
AD1 (10.60.58.132)
AD2 (10.60.58.133)
踏み台サーバ(10.60.58.153)

PowerShellスクリプト

# RDP_Connector.ps1
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# サーバー設定
$servers = @{
    "AD1 (10.60.58.132)" = @{ LocalPort = 11112; RemoteHost = "10.60.58.132"; RemotePort = 3389 }
    "AD2 (10.60.58.133)" = @{ LocalPort = 11113; RemoteHost = "10.60.58.133"; RemotePort = 3389 }
}

# 踏み台サーバー設定
$bastionHost = "10.60.58.153"
$bastionUser = "ec2-user"

# フォームの作成
$form = New-Object System.Windows.Forms.Form
$form.Text = "RDP接続ツール"
$form.Size = New-Object System.Drawing.Size(500, 320)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.MinimizeBox = $false

# アイコン設定
try {
    $form.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$env:SystemRoot\System32\mstsc.exe")
} catch {
    # アイコンの設定に失敗した場合は無視
}

# メインラベル
$mainLabel = New-Object System.Windows.Forms.Label
$mainLabel.Location = New-Object System.Drawing.Point(20, 20)
$mainLabel.Size = New-Object System.Drawing.Size(450, 25)
$mainLabel.Text = "接続するサーバーを選択してください:"
$mainLabel.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
$form.Controls.Add($mainLabel)

# プルダウン(ComboBox)
$comboBox = New-Object System.Windows.Forms.ComboBox
$comboBox.Location = New-Object System.Drawing.Point(20, 55)
$comboBox.Size = New-Object System.Drawing.Size(450, 25)
$comboBox.DropDownStyle = "DropDownList"
$comboBox.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", 9)

# サーバーリストを追加(サーバー名順でソート)
$sortedServers = $servers.Keys | Sort-Object { ($_ -split ' \(')[0] }
foreach ($serverName in $sortedServers) {
    $comboBox.Items.Add($serverName)
}

# デフォルトで最初のアイテムを選択
if ($comboBox.Items.Count -gt 0) {
    $comboBox.SelectedIndex = 0
}

$form.Controls.Add($comboBox)

# サーバー情報表示エリア
$infoGroupBox = New-Object System.Windows.Forms.GroupBox
$infoGroupBox.Location = New-Object System.Drawing.Point(20, 95)
$infoGroupBox.Size = New-Object System.Drawing.Size(450, 100)
$infoGroupBox.Text = "接続情報"
$form.Controls.Add($infoGroupBox)

$infoLabel = New-Object System.Windows.Forms.Label
$infoLabel.Location = New-Object System.Drawing.Point(10, 20)
$infoLabel.Size = New-Object System.Drawing.Size(430, 70)
$infoLabel.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", 8)
$infoGroupBox.Controls.Add($infoLabel)

# ComboBoxの選択変更イベント
$comboBox.Add_SelectedIndexChanged({
    if ($comboBox.SelectedItem) {
        $selectedServer = $servers[$comboBox.SelectedItem]
        $serverNameOnly = ($comboBox.SelectedItem -split ' \(')[0]
        $infoText = "サーバー名: $serverNameOnly`n"
        $infoText += "IPアドレス: $($selectedServer.RemoteHost)`n"
        $infoText += "ローカルポート: $($selectedServer.LocalPort)`n"
        $infoText += "踏み台サーバー: $bastionHost"
        $infoLabel.Text = $infoText
    }
})

# 初期情報表示
if ($comboBox.SelectedItem) {
    $selectedServer = $servers[$comboBox.SelectedItem]
    $serverNameOnly = ($comboBox.SelectedItem -split ' \(')[0]
    $infoText = "サーバー名: $serverNameOnly`n"
    $infoText += "IPアドレス: $($selectedServer.RemoteHost)`n"
    $infoText += "ローカルポート: $($selectedServer.LocalPort)`n"
    $infoText += "踏み台サーバー: $bastionHost"
    $infoLabel.Text = $infoText
}

# 接続ボタン
$connectButton = New-Object System.Windows.Forms.Button
$connectButton.Location = New-Object System.Drawing.Point(20, 210)
$connectButton.Size = New-Object System.Drawing.Size(120, 35)
$connectButton.Text = "接続開始"
$connectButton.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", 9, [System.Drawing.FontStyle]::Bold)
$connectButton.BackColor = [System.Drawing.Color]::LightBlue
$connectButton.Add_Click({
    if ($comboBox.SelectedItem) {
        ConnectToServer $comboBox.SelectedItem
    } else {
        [System.Windows.Forms.MessageBox]::Show("サーバーを選択してください。", "エラー", "OK", "Warning")
    }
})
$form.Controls.Add($connectButton)

# 設定確認ボタン
$settingsButton = New-Object System.Windows.Forms.Button
$settingsButton.Location = New-Object System.Drawing.Point(150, 210)
$settingsButton.Size = New-Object System.Drawing.Size(120, 35)
$settingsButton.Text = "設定確認"
$settingsButton.Add_Click({
    ShowServerList
})
$form.Controls.Add($settingsButton)

# 終了ボタン
$exitButton = New-Object System.Windows.Forms.Button
$exitButton.Location = New-Object System.Drawing.Point(350, 210)
$exitButton.Size = New-Object System.Drawing.Size(120, 35)
$exitButton.Text = "終了"
$exitButton.Add_Click({
    $form.Close()
})
$form.Controls.Add($exitButton)

# ステータスバー
$statusStrip = New-Object System.Windows.Forms.StatusStrip
$statusLabel = New-Object System.Windows.Forms.ToolStripStatusLabel
$statusLabel.Text = "準備完了 - 合計 $($servers.Count) サーバー"
$statusStrip.Items.Add($statusLabel)
$form.Controls.Add($statusStrip)

# Enterキーで接続
$form.KeyPreview = $true
$form.Add_KeyDown({
    if ($_.KeyCode -eq "Enter" -and $comboBox.SelectedItem) {
        ConnectToServer $comboBox.SelectedItem
    } elseif ($_.KeyCode -eq "Escape") {
        $form.Close()
    }
})

# サーバーリスト表示関数
function ShowServerList() {
    $listForm = New-Object System.Windows.Forms.Form
    $listForm.Text = "サーバー設定一覧"
    $listForm.Size = New-Object System.Drawing.Size(600, 500)
    $listForm.StartPosition = "CenterParent"
    $listForm.FormBorderStyle = "Sizable"

    # データグリッドビュー
    $dataGridView = New-Object System.Windows.Forms.DataGridView
    $dataGridView.Location = New-Object System.Drawing.Point(10, 10)
    $dataGridView.Size = New-Object System.Drawing.Size(560, 400)
    $dataGridView.AutoGenerateColumns = $false
    $dataGridView.AllowUserToAddRows = $false
    $dataGridView.AllowUserToDeleteRows = $false
    $dataGridView.ReadOnly = $true
    $dataGridView.SelectionMode = "FullRowSelect"

    # 列の定義
    $nameColumn = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
    $nameColumn.Name = "ServerName"
    $nameColumn.HeaderText = "サーバー名"
    $nameColumn.Width = 120
    $dataGridView.Columns.Add($nameColumn)

    $ipColumn = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
    $ipColumn.Name = "IPAddress"
    $ipColumn.HeaderText = "IPアドレス"
    $ipColumn.Width = 120
    $dataGridView.Columns.Add($ipColumn)

    $portColumn = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
    $portColumn.Name = "LocalPort"
    $portColumn.HeaderText = "ローカルポート"
    $portColumn.Width = 100
    $dataGridView.Columns.Add($portColumn)

    $statusColumn = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
    $statusColumn.Name = "Status"
    $statusColumn.HeaderText = "ポート状態"
    $statusColumn.Width = 100
    $dataGridView.Columns.Add($statusColumn)

    # データの追加
    foreach ($serverEntry in $servers.GetEnumerator() | Sort-Object { ($_.Key -split ' \(')[0] }) {
        $serverNameOnly = ($serverEntry.Key -split ' \(')[0]
        $server = $serverEntry.Value
        
        # ポート状態チェック
        $portStatus = "未確認"
        try {
            $tcpClient = New-Object System.Net.Sockets.TcpClient
            $tcpClient.ReceiveTimeout = 1000
            $tcpClient.SendTimeout = 1000
            $result = $tcpClient.BeginConnect("localhost", $server.LocalPort, $null, $null)
            $success = $result.AsyncWaitHandle.WaitOne(1000, $true)
            if ($success -and $tcpClient.Connected) {
                $portStatus = "接続可能"
            } else {
                $portStatus = "未接続"
            }
            $tcpClient.Close()
        } catch {
            $portStatus = "未接続"
        }

        $row = @($serverNameOnly, $server.RemoteHost, $server.LocalPort, $portStatus)
        $dataGridView.Rows.Add($row)
    }

    $listForm.Controls.Add($dataGridView)

    # 閉じるボタン
    $closeButton = New-Object System.Windows.Forms.Button
    $closeButton.Location = New-Object System.Drawing.Point(490, 420)
    $closeButton.Size = New-Object System.Drawing.Size(80, 30)
    $closeButton.Text = "閉じる"
    $closeButton.Add_Click({
        $listForm.Close()
    })
    $listForm.Controls.Add($closeButton)

    $listForm.ShowDialog()
}

# 接続関数
function ConnectToServer($serverName) {
    $server = $servers[$serverName]
    $serverNameOnly = ($serverName -split ' \(')[0]
    
    $statusLabel.Text = "[$serverNameOnly] ポートフォワードを確立中..."
    $form.Refresh()
    
    # ボタンを無効化
    $connectButton.Enabled = $false
    $connectButton.Text = "接続中..."
    
    try {
        # 既存のポートフォワードプロセスをチェック・終了
        $existingProcesses = Get-Process | Where-Object { 
            $_.ProcessName -eq "ssh" -and 
            $_.CommandLine -like "*$($server.LocalPort):$($server.RemoteHost):$($server.RemotePort)*" 
        } -ErrorAction SilentlyContinue
        
        if ($existingProcesses) {
            $existingProcesses | Stop-Process -Force
            Start-Sleep -Seconds 1
            $statusLabel.Text = "[$serverNameOnly] 既存の接続を終了しました"
        }
        
        # SSHポートフォワードの確立
        $sshArgs = @(
            "-L", "$($server.LocalPort):$($server.RemoteHost):$($server.RemotePort)",
            "-N", "-f",
            "$bastionUser@$bastionHost"
        )
        
        $sshProcess = Start-Process -FilePath "ssh" -ArgumentList $sshArgs -PassThru -WindowStyle Hidden
        
        $statusLabel.Text = "[$serverNameOnly] 接続を確立中..."
        $form.Refresh()
        
        # ポートフォワードが確立されるまで待機
        for ($i = 1; $i -le 5; $i++) {
            Start-Sleep -Seconds 1
            $statusLabel.Text = "[$serverNameOnly] 接続を確立中... ($i/5)"
            $form.Refresh()
        }
        
        # ポートが利用可能かテスト
        $tcpClient = New-Object System.Net.Sockets.TcpClient
        try {
            $tcpClient.Connect("localhost", $server.LocalPort)
            $tcpClient.Close()
            
            $statusLabel.Text = "[$serverNameOnly] RDP接続を開始中..."
            $form.Refresh()
            
            # RDP接続の開始
            Start-Process -FilePath "mstsc" -ArgumentList "/v:localhost:$($server.LocalPort)"
            
            $statusLabel.Text = "[$serverNameOnly] RDP接続を開始しました"
            
            # 成功メッセージ
            $result = [System.Windows.Forms.MessageBox]::Show(
                "RDP接続を開始しました。`n`nサーバー: $serverNameOnly`nIPアドレス: $($server.RemoteHost)`nローカルポート: $($server.LocalPort)`n`nこのツールを閉じますか?", 
                "接続成功", 
                "YesNo", 
                "Information"
            )
            
            if ($result -eq "Yes") {
                $form.Close()
            }
            
        } catch {
            $statusLabel.Text = "[$serverNameOnly] 接続に失敗しました"
            [System.Windows.Forms.MessageBox]::Show("ポートフォワードの確立に失敗しました。`n`nサーバー: $serverNameOnly`nSSH接続を確認してください。`nエラー: $($_.Exception.Message)", "接続エラー", "OK", "Error")
        } finally {
            if ($tcpClient) {
                $tcpClient.Dispose()
            }
        }
        
    } catch {
        $statusLabel.Text = "[$serverNameOnly] エラーが発生しました"
        [System.Windows.Forms.MessageBox]::Show("接続エラー: $($_.Exception.Message)", "エラー", "OK", "Error")
    } finally {
        # ボタンを有効化
        $connectButton.Enabled = $true
        $connectButton.Text = "接続開始"
    }
}

# フォームを表示
$form.ShowDialog()

最後に

こんなイメージ。
コードは生成AI君に書いていただいたw
image.png

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?