提醒:本页面将不再更新、维护或者支持,文章、评论所叙述内容存在时效性,涉及技术细节或者软件使用方面不保证能够完全有效可操作,请谨慎参考!

最近在配置一台Windows Server 2008 R2的服务器,本来想装上个VNC的,不过想想Windows自带有远程桌面,就没必要这么麻烦了,于是开启了远程桌面访问,并且在TMG防火墙规则中配置了RDP(Terminal Services) Server规则,一切都很顺利,当路由器上映射完3389端口后,客户端成功访问并且连接! 可是过了一段时间发现,远程连接被断开,局域网连接仍然可以,外网死活都连接不上,于是查阅了TMG防火墙日志,发现有段大量不同IP尝试连接3389的记录,并且被TMG侦测为FLOOD攻击,所以拒绝了所有的外网请求,看来不能用默认的3389端口了,所以要更改默认的远程端口,具体怎么改,其实在Windows XP时代我就尝试着找可以改动的图形化界面,可是一直找不到最后还是修改注册表才更改成功。貌似现在在Win2008时代依旧不能找到图形化修改界面,看来又要修改注册表了。主要修改以下两条注册表信息的PortNamber值:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Wds\rdpwd\Tds\tcp\

为了避免以后再遇到这类问题,我觉得有必要写个脚本来解决。我们知道在VBScript中修改注册表有两种方式,第一种是使用组件WScript.Shell的RegWrite方法,遗憾的是这种方式常常会使杀毒软件误认为是病毒,因为有段时间WScript.Shell在网页挂马技术上很是流行,导致现在很多安全软件都封杀或者重点监控这个ActvieX组件。第二种是使用WMI方式,这个是我比较推荐的,下面的脚本将说明这些问题。

在提供脚本之前,大家可以到微软的 脚本中心 参考一下WMI操作注册表的一些方法。

' Author : WangYe
' For more information please visit http://wangye.org/
Option Explicit

Const AppTitle = "Modify RDP Port Number"

Const StatusOk = 0
Const StatusInvalidPortNumber = -1
Const StatusSetRDPPortNumberFailed = -2
Const StatusSetTDSPortNumberFailed = -3

Const L_Invalid_PortNumber_Text = "ERROR : Invalid port number."
Const L_User_Cancelled_Text = "User cancelled."
Const HKEY_LOCAL_MACHINE = &H80000002
Const RDPTcpPath =_
 "SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\"
Const TDSTcpPath =_
 "SYSTEM\CurrentControlSet\Control\Terminal Server\Wds\rdpwd\Tds\tcp\"
 
Class RDPTS

  Private strComputer
  Private Registry
  
  Private Sub Class_Initialize()
    strComputer = "."
    Set Registry = GetObject(_
      "winmgmts:{impersonationLevel=impersonate}!\\" &_
      strComputer & "\root\default:StdRegProv")
  End Sub

  Private Sub Class_Terminate()
    Set Registry = Nothing
  End Sub
  
  Function isPortAlreadyExists(portnum)
    ' 判断端口是否冲突(尚未实现)
    isPortAlreadyExists = False
  End Function

  Public Function getPortNumber(lowerbound, upperbound)
    If lowerbound < 4 Then lowerbound = 4
    If upperbound > 65534 Then upperbound = 65535
    Do
      Randomize
      getPortNumber = Int(_
      (upperbound - lowerbound + 1)_
      * Rnd + lowerbound)
    Loop Until getPortNumber<>3389_
        And (Not isPortAlreadyExists(getPortNumber))
  End Function

  Public Function isPortValid(portnum)
    isPortValid = False
    If Not IsNumeric(portnum) Then
      Exit Function
    End If
    If portnum < 4 Then
      Exit Function
    End If
    If portnum > 65534 Then
      Exit Function
    End If
    isPortValid = True
  End Function
  
  Public Function getRDPTcpPortNumber()
    Registry.GetDWORDValue HKEY_LOCAL_MACHINE,_
		RDPTcpPath,"PortNumber",getRDPTcpPortNumber
  End Function
  
  Public Function getTDSTcpPortNumber()
    Registry.GetDWORDValue HKEY_LOCAL_MACHINE,_
		TDSTcpPath,"PortNumber",getTDSTcpPortNumber
  End Function
  
  Public Function setRDPTcpPortNumber(portnum)
    On Error Resume Next
    setRDPTcpPortNumber = True
    Registry.SetDWORDValue HKEY_LOCAL_MACHINE,_
		RDPTcpPath,"PortNumber",portnum
    If Err Then Err.Clear : setRDPTcpPortNumber = False
  End Function
  
  Public Function setTDSTcpPortNumber(portnum)
    On Error Resume Next
    setTDSTcpPortNumber = True
    Registry.SetDWORDValue HKEY_LOCAL_MACHINE,_
		TDSTcpPath,"PortNumber",portnum
    If Err Then Err.Clear : setTDSTcpPortNumber = False
  End Function
  
  Public Function addFirewallPolicy(portnum, name, state)
    Dim netfw, policy, port, ports
    Set netfw = WScript.CreateObject("HNetCfg.FwMgr")
    Set policy = netfw.LocalPolicy.CurrentProfile
    Set port = WScript.CreateObject("HNetCfg.FwOpenPort")
      port.Port = portnum
      port.Name = name
      port.Enabled = state
    Set ports = policy.GloballyOpenPorts
      addFirewallPolicy = ports.Add(port)
    Set ports = Nothing
    Set port = Nothing
    Set policy = Nothing
    Set netfw = Nothing
  End Function
End Class


Function VBMain()
  
  VBMain = StatusOk
  
  Dim RDS, portnum, source
  Set RDS = New RDPTS
  portnum = RDS.getPortNumber(3390, 65530)
  source = InputBox("Original port number detected:" & vbCrLf &_
          "RDP-TCP(" & RDS.getRDPTcpPortNumber() &_
          "), TDS-TCP(" & RDS.getTDSTcpPortNumber() &_
          ")" & vbCrLf & vbCrLf &_
          "Please Enter the new port number" & vbCrLf &_
          "for RDP(Terminal Services) Server", _
          AppTitle, portnum)
          
  If source = "" Then
    WScript.Echo L_User_Cancelled_Text
    Exit Function
  End If
  
  If Not RDS.isPortValid(source) Then
    WScript.Echo L_Invalid_PortNumber_Text
    VBMain = StatusInvalidPortNumber
    Exit Function
  End If
  
  portnum = source
  
  If MsgBox("Pending changes : " & vbCrLf &  vbCrLf &_
       "RDP-TCP ` " & RDS.getRDPTcpPortNumber() &_
	   " -> " & portnum & " `" & vbCrLf &_
       "TDS-TCP ` " & RDS.getTDSTcpPortNumber() &_
	   " -> " & portnum & " `" & vbCrLf &  vbCrLf &_
	   "Are you sure?", vbOKCancel, AppTitle) = vbCancel Then
    WSH.Echo "Cancelled, No changes occured."
    Exit Function
  End If
  
  If Not RDS.setRDPTcpPortNumber(portnum) Then
    WSH.Echo "Set RDP-TCP port number `" &_
    RDS.getRDPTcpPortNumber() & "` to `" &_
    portnum & "` failed!"
    VBMain = StatusSetRDPPortNumberFailed
    Exit Function
  End If
  
  If Not RDS.setTDSTcpPortNumber(portnum) Then
    WSH.Echo "Set TDS-TCP port number `" &_
    RDS.getTDSTcpPortNumber() & "` to `" &_
    portnum & "` failed!"
    VBMain = StatusSetTDSPortNumberFailed
    Exit Function
  End If
  
  If MsgBox("Do you want add port `" & portnum &_
  "` to Windows Firewall policy?", vbOKCancel, AppTitle) = vbOK Then
    Do
      source = InputBox("Enter the name for this new policy",_
		AppTitle, "RDP(Terminal Services)")
      If source="" Then
        If MsgBox("Policy name required, Do you want quit Add Policy?",_
        vbOKCancel, AppTitle) = VbOK Then
          Exit Do
        End If
      End If
    Loop Until source<>""
    
    If source<>"" Then
      RDS.addFirewallPolicy portnum, source, 1
    End If
  End If
  WScript.Echo "All done successfully!"
  Set RDS = Nothing
End Function

Call WScript.Quit(VBMain())

好了,上面就是全部脚本,为了兼容不带中文的外文操作系统,我这里一些提示信息使用的是英语,很简单的词汇,想必大家应该能看懂的吧。isPortAlreadyExists这个函数尚未完成,是判断端口是否被其他程序占用的,实在找不到好的办法判断,这里先空着吧。对了,我们修改完端口,可能有必要将新端口加入到Windows防火墙规则中,当然如果你关闭了Windows防火墙,这一步就可以省略,不过你可能要修改其他现有防火墙的访问规则了。不能像我一样,有一次就是远程装完ISA防火墙,结果把自己关在外面了,安装前没有配置好规则惹的祸啊。

最后,打包好的脚本( RDP-ChangePorts.zip ):-)