前置き
踏み台サーバのポートフォワード経由で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()