├── .gitignore ├── RDPSelector ├── powershell │ ├── Newtonsoft.Json.dll │ ├── Microsoft.TeamFoundation.Client.dll │ ├── Microsoft.TeamFoundation.Common.dll │ ├── Microsoft.TeamFoundation.Lab.Client.dll │ ├── Microsoft.TeamFoundation.Lab.Common.dll │ ├── Microsoft.VisualStudio.Services.Client.dll │ ├── Microsoft.VisualStudio.Services.Common.dll │ ├── Microsoft.VisualStudio.Services.WebApi.dll │ ├── startstop.ps1 │ ├── envstates.ps1 │ ├── markinuse.ps1 │ └── mtmlist.ps1 ├── readme.txt └── RDPSelector.ahk ├── .gitattributes ├── README.md ├── RDPConnect Example.ahk └── RDPConnect.ahk /.gitignore: -------------------------------------------------------------------------------- 1 | *.ini 2 | *.exe 3 | *.zip 4 | *.lnk 5 | *.scc 6 | *.png 7 | *.bak 8 | *.tmp.* 9 | *.obj 10 | *.cpp 11 | *.bat -------------------------------------------------------------------------------- /RDPSelector/powershell/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/RDPTools/HEAD/RDPSelector/powershell/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /RDPSelector/powershell/Microsoft.TeamFoundation.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/RDPTools/HEAD/RDPSelector/powershell/Microsoft.TeamFoundation.Client.dll -------------------------------------------------------------------------------- /RDPSelector/powershell/Microsoft.TeamFoundation.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/RDPTools/HEAD/RDPSelector/powershell/Microsoft.TeamFoundation.Common.dll -------------------------------------------------------------------------------- /RDPSelector/powershell/Microsoft.TeamFoundation.Lab.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/RDPTools/HEAD/RDPSelector/powershell/Microsoft.TeamFoundation.Lab.Client.dll -------------------------------------------------------------------------------- /RDPSelector/powershell/Microsoft.TeamFoundation.Lab.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/RDPTools/HEAD/RDPSelector/powershell/Microsoft.TeamFoundation.Lab.Common.dll -------------------------------------------------------------------------------- /RDPSelector/powershell/Microsoft.VisualStudio.Services.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/RDPTools/HEAD/RDPSelector/powershell/Microsoft.VisualStudio.Services.Client.dll -------------------------------------------------------------------------------- /RDPSelector/powershell/Microsoft.VisualStudio.Services.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/RDPTools/HEAD/RDPSelector/powershell/Microsoft.VisualStudio.Services.Common.dll -------------------------------------------------------------------------------- /RDPSelector/powershell/Microsoft.VisualStudio.Services.WebApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/RDPTools/HEAD/RDPSelector/powershell/Microsoft.VisualStudio.Services.WebApi.dll -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RDPTools 2 | An AHK library for automating RDP client connections 3 | 4 | ##RDPConnect 5 | A class library that automates the various windows associated with the windows RDP client (mstsc.exe) 6 | Pass it an address, username / domain string and password string and it attempts to open the connection and log in for you. 7 | Will fire a callback on success or failure. 8 | Additionally, can be instructed to not fire the callback until the desktop is ready for interaction (Waits for pixel of pre-determined color to exist at coordinate 0,0). 9 | 10 | Usage: ```this.rdp := new RDPConnect(address, login, password, MyBoundFunc, {[options]})``` 11 | eg: ```this.rdp := new RDPConnect(address, login, password, this.MyMethod.Bind(this), {WaitForDesktopColor: "0xFFFFFF"})``` 12 | 13 | Callback Parameters: 14 | ``` 15 | MyMethod(e, pid, hwnd) 16 | e : The event that happened. 17 | 0 is "success", other values are errors (eg bad address, bad credentials) 18 | use GetErrorName to get a human-friendly reason for the failure 19 | pid : The Process ID of the RDP session. All RDP windows for one connection will share the same PID 20 | hwnd: The Window Handle of the session window. 21 | On connect, hwnd will be the HWND of the session window 22 | On disconnect (User logged off), hwnd will be unset (e will still be 0, as this is a "success") 23 | ``` 24 | 25 | RDPConnect uses ```ControlSend``` for all sending of keyboard input directly to the HWND of the RDP window(s), so it should be (reasonably) immune to the user performing other functions whilst RDPConnect is connecting. 26 | 27 | WARNING: Whilst RDPConnect does not store any passwords, in order to use it, you will probably need to store passwords on your disk. 28 | RDPConnect is NOT RECOMMENDED for use-cases where you wish to keep your password(s) private. It's primary use-case is intended for test environments and the like, where password secrecy is not an issue. 29 | -------------------------------------------------------------------------------- /RDPSelector/powershell/startstop.ps1: -------------------------------------------------------------------------------- 1 | # Define parameters 2 | param([string]$tfsCollectionUrl, [string]$projectName, [string]$environmentName, [string]$state) 3 | $state = ToInt32($state); 4 | #$tfsCollectionUrl = New-Object System.URI("http://tfs1.dcsl.local:8080/tfs/ExclaimerProducts"); 5 | #$projectName = "Email Alias Manager I"; 6 | #$environmentName = "NI EAM Smoke Test"; 7 | 8 | # Load Client Assembly 9 | $baseDir = Split-Path -parent $PSCommandPath 10 | add-type -Path "$baseDir\Microsoft.VisualStudio.Services.Common.dll"; 11 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Common.dll"; 12 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Client.dll"; 13 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Lab.Client.dll"; 14 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Lab.Common.dll"; 15 | 16 | # Connect to tfs 17 | $tfsCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsCollectionUrl); 18 | $labService = $tfsCollection.GetService([Microsoft.TeamFoundation.Lab.Client.LabService]); 19 | 20 | # Query for environments 21 | $labEnvironmentQuerySpec = New-Object Microsoft.TeamFoundation.Lab.Client.LabEnvironmentQuerySpec; 22 | $labEnvironmentQuerySpec.Project = $projectName; 23 | $labEnvironmentQuerySpec.Disposition = [Microsoft.TeamFoundation.Lab.Client.LabEnvironmentDisposition]::Active; 24 | 25 | $labEnvironments = $labService.QueryLabEnvironments($labEnvironmentQuerySpec); 26 | foreach ($environment in $labEnvironments) 27 | { 28 | $envName = $environment.Name; 29 | if ($envName -eq $environmentName) 30 | { 31 | $matchingEnvironment = $environment; 32 | } 33 | } 34 | 35 | # whether the environment is already in use 36 | #$inUseMarker = $matchingEnvironment.GetInUseMarker(); 37 | 38 | if ($state -eq 1){ 39 | Write-Host Starting Environment 40 | 41 | $matchingEnvironment.Start(); 42 | } else { 43 | Write-Host Shutting down Environment 44 | 45 | $matchingEnvironment.Shutdown(); 46 | } -------------------------------------------------------------------------------- /RDPConnect Example.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | Sample script for RDPConnect that demos connecting to a remote machine and issuing a command via it's Run menu. 3 | */ 4 | OutputDebug DBGVIEWCLEAR 5 | #singleinstance force 6 | #include RDPConnect.ahk 7 | 8 | mc := new MyClass() 9 | return 10 | 11 | 12 | class MyClass { 13 | __New(){ 14 | ; Assign a hotkey to launch the RDP session 15 | fn := this.Connect.Bind(this) 16 | hotkey, F1, % fn 17 | } 18 | 19 | ; The hotkey was pressed 20 | Connect(){ 21 | address := "192.168.0.2" 22 | login := "MYDOMAIN\MyUser" 23 | password := "P@ssword" 24 | 25 | ; Start the connection attempt with the specified credentials. 26 | ; Also uses the WaitForDesktopColor option to wait for pixel 0, 0 of the session window to be pure white... 27 | ; ... this attempts to detect "Ready" state of the remoote machine (As long as the desktop is white!) 28 | this.rdp := new RDPConnect(address, login, password, this.SessionEvent.Bind(this), {WaitForDesktopColor: "0xFFFFFF"}) 29 | } 30 | 31 | ; SessionEvent will be called when something changes about the session connection 32 | ; e : The event that happened. 33 | ; 0 is "success", other values are errors (eg bad address, bad credentials) 34 | ; use GetErrorName to get a human-friendly reason for the failure 35 | ; pid : The Process ID of the RDP session. All RDP windows for one connection will share the same PID 36 | ; hwnd: The Window Handle of the session window. 37 | ; On connect, hwnd will be the HWND of the session window 38 | ; On disconnect (User logged off), hwnd will be unset (e will still be 0, as this is a "success") 39 | SessionEvent(e, pid, hwnd){ 40 | if (e != 0){ 41 | errorname := this.rdp.GetErrorName(e) 42 | msgbox % "Error : " errorname 43 | return 44 | } 45 | if (hwnd){ 46 | this.rdp.SetMaximizedState(1) ; Maximize the RDP window, so keystrokes get sent 47 | Sleep 250 48 | this.rdp.SendKeys("{LWin}") ; Open start menu 49 | Sleep 1000 50 | this.rdp.SendKeys("Notepad{Enter}") ; Run Notepad 51 | return 52 | } else { 53 | Tooltip % "Session ended." 54 | Sleep 2000 55 | ToolTip 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /RDPSelector/powershell/envstates.ps1: -------------------------------------------------------------------------------- 1 | # Define parameters 2 | #$tfsCollectionUrl = New-Object System.URI("http://tfs1.dcsl.local:8080/tfs/ExclaimerProducts"); 3 | param([string]$tfsCollectionUrl); 4 | if ($tfsCollectionUrl.Equals("")){ 5 | Exit 6 | } 7 | $tfsCollectionUrl = New-Object System.URI($tfsCollectionUrl); 8 | 9 | # Load Client Assembly 10 | $baseDir = Split-Path -parent $PSCommandPath 11 | add-type -Path "$baseDir\Microsoft.VisualStudio.Services.Common.dll"; 12 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Common.dll"; 13 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Client.dll"; 14 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Lab.Client.dll"; 15 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Lab.Common.dll"; 16 | 17 | # Connect to tfs 18 | $tfsCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsCollectionUrl); 19 | $labService = $tfsCollection.GetService([Microsoft.TeamFoundation.Lab.Client.LabService]); 20 | $projectService = $tfsCollection.GetService([Microsoft.TeamFoundation.Server.ICommonStructureService]); 21 | 22 | # Query all the projects 23 | $projects = $projectService.ListAllProjects(); 24 | 25 | $arr = @{} 26 | 27 | foreach ($project in $projects) 28 | { 29 | #Write-Host Finding environments for project $project.Name 30 | 31 | # Query all environments in this project 32 | $labEnvironmentQuerySpec = New-Object Microsoft.TeamFoundation.Lab.Client.LabEnvironmentQuerySpec; 33 | $labEnvironmentQuerySpec.Disposition = [Microsoft.TeamFoundation.Lab.Client.LabEnvironmentDisposition]::Active; 34 | $labEnvironmentQuerySpec.Project = $project.Name; 35 | 36 | 37 | $labEnvironments = $labService.QueryLabEnvironments($labEnvironmentQuerySpec); 38 | 39 | #$arr["foo"] = "bar" 40 | if ($labEnvironments.Count) 41 | { 42 | $arr[$project.Name] = @{} 43 | foreach ($environment in $labEnvironments) 44 | { 45 | if ($environment.LabSystems.Count) 46 | { 47 | $arr[$project.Name][$environment.Name] = @{} 48 | $arr[$project.Name][$environment.Name].State = $environment.StatusInfo.State.ToString() 49 | } 50 | } 51 | } 52 | } 53 | 54 | $str = ConvertTo-Json($arr) -Depth 10 55 | Write-Host $str 56 | -------------------------------------------------------------------------------- /RDPSelector/powershell/markinuse.ps1: -------------------------------------------------------------------------------- 1 | # Define parameters 2 | param([string]$tfsCollectionUrl, [string]$projectName, [string]$environmentName, [string]$state) 3 | #$state = ToInt32($state); 4 | #$tfsCollectionUrl = New-Object System.URI("http://tfs1.dcsl.local:8080/tfs/ExclaimerProducts"); 5 | #$projectName = "Email Alias Manager I"; 6 | #$environmentName = "NI EAM Smoke Test"; 7 | 8 | # Load Client Assembly 9 | $baseDir = Split-Path -parent $PSCommandPath 10 | add-type -Path "$baseDir\Microsoft.VisualStudio.Services.Common.dll"; 11 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Common.dll"; 12 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Client.dll"; 13 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Lab.Client.dll"; 14 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Lab.Common.dll"; 15 | 16 | # Connect to tfs 17 | $tfsCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsCollectionUrl); 18 | $labService = $tfsCollection.GetService([Microsoft.TeamFoundation.Lab.Client.LabService]); 19 | 20 | # Query for environments 21 | $labEnvironmentQuerySpec = New-Object Microsoft.TeamFoundation.Lab.Client.LabEnvironmentQuerySpec; 22 | $labEnvironmentQuerySpec.Project = $projectName; 23 | $labEnvironmentQuerySpec.Disposition = [Microsoft.TeamFoundation.Lab.Client.LabEnvironmentDisposition]::Active; 24 | 25 | $labEnvironments = $labService.QueryLabEnvironments($labEnvironmentQuerySpec); 26 | foreach ($environment in $labEnvironments) 27 | { 28 | $envName = $environment.Name; 29 | if ($envName -eq $environmentName) 30 | { 31 | $matchingEnvironment = $environment; 32 | } 33 | } 34 | 35 | # whether the environment is already in use 36 | $inUseMarker = $matchingEnvironment.GetInUseMarker(); 37 | 38 | if ($state -eq 1){ 39 | if ($inUseMarker -eq $null) 40 | { 41 | Write-Host Marking Environment as in use; 42 | 43 | $matchingEnvironment.SetInUseMarker("Reserving the environment for script testing purpose"); 44 | } 45 | else 46 | { 47 | Write-Host Environment is already in use since $inUseMarker.Timestamp by $inUseMarker.User with details $inUseMarker.Comment; 48 | } 49 | } else { 50 | Write-Host Marking environment as not in use 51 | $matchingEnvironment.RemoveInUseMarker(); 52 | } -------------------------------------------------------------------------------- /RDPSelector/readme.txt: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | RDPSelector utility by Clive Galway 5 | email: evilc@evilc.com 6 | GitHub: https://github.com/evilC 7 | 8 | Allows rapid connection / disconnection / switching to RDP sessions. 9 | Machines to connect to and Users to connect as can be selected from a list. 10 | 11 | Uses my RDPConnect AHK library (Not needed if using the compiled EXE) 12 | 13 | Usage 14 | ===== 15 | Just run the EXE, the .ahk file is the source code. 16 | Be sure to copy the EXE, all INI files and the powershell folder to your local hard disk. 17 | You may need to execute "Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser" in powershell for the powershell scripts to work. 18 | Instructions included in the GUI. 19 | 20 | Configuration 21 | ============= 22 | Edit the INI files as follows: 23 | 24 | credentials.ini 25 | --------------- 26 | 27 | [Human Friendly Name] 28 | login=mylogin 29 | pass=mypass 30 | domain=mydomain 31 | 32 | environments.ini 33 | ---------------- 34 | 35 | [Environment Name] 36 | SomeServer1=address1.domain.local 37 | SomeServer2=address2.domain.local 38 | 39 | Changelog 40 | ========= 41 | 42 | 1.0 - 28/08/2015 43 | + Inital version 44 | 45 | 1.1 - 01/09/2015 46 | + Fixed indexed sparse array of hwnds bug. 47 | Using Remove() will decrement remaining indexes, making hwnds inaccurate. 48 | + Fixed bug preventing multiple environment trees from rendering. 49 | 50 | 1.2 - 01/09/2015 51 | + If logon fails, you can launch a new session (eg with different user) without having to close the old windows. 52 | + Added Ctrl + Shift + Tab hotkey to instantly go to Open Sessions section. 53 | + If connection to a machine fails, the next time you connect to that machine, 54 | open login dialogs for that machine will automatically be closed. 55 | 56 | 1.3 - 01/09/2015 57 | + Sparse array removal bug that 1.1 attempted to fix should really be fixed now. 58 | 59 | 1.4 - 03/09/2015 60 | + You can now double-click listview / treeview items 61 | + Extra tree level for machine TreeView - "Project" 62 | + TFS URL is now pulled from RDPSelector.ini 63 | + Environments.ini is now a JSON file 64 | + Now uses powershell script to mark TFS environments as in-use when you open an RDP session to them. 65 | + (RDPConnect lib update) - The "Don't ask me again for connections to this computer" checkbox in the certificate window will be automatically ticked if it appears. -------------------------------------------------------------------------------- /RDPSelector/powershell/mtmlist.ps1: -------------------------------------------------------------------------------- 1 | # Define parameters 2 | #$tfsCollectionUrl = New-Object System.URI("http://tfs1.dcsl.local:8080/tfs/ExclaimerProducts"); 3 | param([string]$tfsCollectionUrl); 4 | if ($tfsCollectionUrl.Equals("")){ 5 | Exit 6 | } 7 | $tfsCollectionUrl = New-Object System.URI($tfsCollectionUrl); 8 | 9 | # Load Client Assembly 10 | $baseDir = Split-Path -parent $PSCommandPath 11 | add-type -Path "$baseDir\Microsoft.VisualStudio.Services.Common.dll"; 12 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Common.dll"; 13 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Client.dll"; 14 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Lab.Client.dll"; 15 | add-type -Path "$baseDir\Microsoft.TeamFoundation.Lab.Common.dll"; 16 | 17 | # Connect to tfs 18 | $tfsCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsCollectionUrl); 19 | $labService = $tfsCollection.GetService([Microsoft.TeamFoundation.Lab.Client.LabService]); 20 | $projectService = $tfsCollection.GetService([Microsoft.TeamFoundation.Server.ICommonStructureService]); 21 | 22 | # Query all the projects 23 | $projects = $projectService.ListAllProjects(); 24 | 25 | $arr = @{} 26 | 27 | foreach ($project in $projects) 28 | { 29 | #Write-Host Finding environments for project $project.Name 30 | 31 | # Query all environments in this project 32 | $labEnvironmentQuerySpec = New-Object Microsoft.TeamFoundation.Lab.Client.LabEnvironmentQuerySpec; 33 | $labEnvironmentQuerySpec.Disposition = [Microsoft.TeamFoundation.Lab.Client.LabEnvironmentDisposition]::Active; 34 | $labEnvironmentQuerySpec.Project = $project.Name; 35 | 36 | 37 | $labEnvironments = $labService.QueryLabEnvironments($labEnvironmentQuerySpec); 38 | 39 | #$arr["foo"] = "bar" 40 | if ($labEnvironments.Count) 41 | { 42 | $arr[$project.Name] = @{} 43 | foreach ($environment in $labEnvironments) 44 | { 45 | if ($environment.LabSystems.Count) 46 | { 47 | $arr[$project.Name][$environment.Name] = @{} 48 | 49 | foreach ($machine in $environment.LabSystems) 50 | { 51 | if ($machine.ComputerName) 52 | { 53 | $arr[$project.Name][$environment.Name][$machine.Name] = $machine.ComputerName 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | $str = ConvertTo-Json($arr) -Depth 10 62 | Write-Host $str 63 | -------------------------------------------------------------------------------- /RDPConnect.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | A Class to log in via RDP to a remote computer. 3 | Tries to automate all the windows that the RDP client (mstsc.exe) creates, including... 4 | Address to connect to, login credentials, certificate warning, actual session window 5 | 6 | Fires a FuncObject callback whenever the session connects or disconnects, or when a login attempt is aborted. 7 | 8 | All windows related to one RDP session share the same PID, and each instance of the class only interacts with 9 | windows that have the same PID. 10 | All UI interactions (Key sending, Form filling) do not require the RDP windows to be focused. 11 | */ 12 | 13 | class RDPConnect { 14 | static version := "1.0.15" 15 | /* 16 | Instantiate this class to initiate a new RDP Session 17 | new RDPConnect(Address, UserName, Password, Callback) 18 | Address: The Address (eg IP) to connect to 19 | UserName: The Domain\Username to connect to 20 | Password: The password to use 21 | Callback: FuncObject to be called when session connects, disconnects, or aborts connection attempt 22 | Options: Associative Array of options 23 | 24 | When the callback is called, it is passed three params: 25 | Status: See ErrorCodes. Also see GetErrorName / GetErrorCode methods to convert codes to strings and vice versa 26 | PID: The PID of the created session 27 | HWND: The HWND of the SESSION window (The one with the desktop in), or 0 if it does not exist (eg the user disconnected) 28 | 29 | Available Options: 30 | DefaultSleep: The amount of time (in ms) to wait between each step of UI interaction. 31 | ShowOptions: Do not click OK at the options screen (Choose resolution etc stage). Allow the user to pick options and click OK. 32 | MaxLoginAttempts: The maximum amount of times to try logging in before giving up. Increase if it sometimes fails. 33 | 34 | Class Properties: 35 | All internal class properties that users of the class are not intended to interact with are prefixed with an underscore (_) 36 | So for maximum safety, if you wish to set properties on an instance of the class, don't prefix with an _underscore 37 | The following properties are not pefixed, and should NOT be set, but may be useful to read 38 | .hwnd - Starts as 0. Changes to the HWND of the Session Window (The one with the remote desktop in) once connectin succeeds, then to 0 on disconnect. 39 | .pid - The Process ID of the Session that instance is handling. Will never change for an instance. 40 | */ 41 | __New(address, username, password, callback, options := 0){ 42 | ; Save passed params for future use 43 | this._address := address 44 | this._username := username 45 | this._password := password 46 | this._callback := callback 47 | 48 | if (address = ""){ 49 | this.CloseSession(this._ErrorCodes.BAD_ADDRESS) 50 | return 51 | } 52 | 53 | ; Set Default options 54 | this._options := {DefaultSleep: 250, ShowOptions: 0, MaxLoginAttempts: 1, WaitForDesktopColor: 0} 55 | ; Modify with passed options 56 | if (IsObject(options)){ 57 | for option, value in options { 58 | this._options[option] := value 59 | } 60 | } 61 | 62 | this._ErrorCodes := {OK: 0, BAD_ADDRESS: 1, LOGIN_FAILED: 2, WAIT_DESKTOP_FAILED: 3} 63 | this._ErrorNames := {0: "OK", 1: "Bad Address", 2: "Login Failed", 3: "Wait for desktop failed"} 64 | 65 | ; These will be 0 if the relevant window does not exist, or the hwnd of the window if it exists 66 | this._SelectWindow := 0 67 | this._LoginWindow := 0 68 | this._CertificateWindow := 0 69 | this._IdentityWindow := 0 70 | this.hwnd := 0 ; The hwnd of the Session window, or 0 if no session active 71 | 72 | ; How many times we tried to log in 73 | this._LoginAttempts := 0 74 | 75 | ; Launch RDP Client, get PID of new process 76 | Run, %a_windir%\Sysnative\mstsc.exe , , , pid 77 | this.pid := pid 78 | 79 | ; Start Timer to watch for this PID creating new windows 80 | fn := this.WatchPID.Bind(this) 81 | this._WatchTimer := fn 82 | SetTimer % fn, 500 83 | } 84 | 85 | ; Timer Func to watch for windows opening and closing 86 | WatchPID(){ 87 | hwnd := WinExist("Remote Desktop Connection ahk_exe mstsc.exe ahk_class #32770 ahk_pid " this.pid, , , "Connecting to:")+0 88 | if (hwnd){ 89 | WinGetText, t, % "ahk_id " hwnd 90 | t := StrSplit(t, "`r") 91 | t := t[1] 92 | if (t = "The remote computer could not be authenticated due to problems with its security certificate. It may be unsafe to proceed."){ 93 | if (hwnd != this._CertificateWindow){ 94 | this.Log("Certificate Window detected") 95 | this.CertificateWindowChanged(hwnd) 96 | return 97 | } 98 | } else if (t = "This remote connection could harm your local or remote computer. Make sure that you trust the remote computer before you connect."){ 99 | if (hwnd != this._WarningWindow){ 100 | this.Log("Warning Window detected") 101 | this.WarningWindowChanged(hwnd) 102 | return 103 | } 104 | } else if (this.IdentityWindowExists()) { 105 | if (hwnd != this._IdentityWindow){ 106 | this.Log("Identity Window detected") 107 | this.IdentityWindowChanged(hwnd) 108 | return 109 | } 110 | } else { 111 | if (hwnd != this._SelectWindow){ 112 | this.Log("Select Window detected") 113 | this.SelectWindowChanged(hwnd) 114 | return 115 | } 116 | } 117 | } 118 | 119 | hwnd := this.LoginWindowExists() 120 | if (hwnd != this._LoginWindow){ 121 | this.LoginWindowChanged(hwnd) 122 | return 123 | } 124 | 125 | hwnd := WinExist("ahk_exe mstsc.exe ahk_class TscShellContainerClass ahk_pid " this.pid)+0 126 | if (hwnd != this.hwnd){ 127 | this.SessionWindowChanged(hwnd) 128 | return 129 | } 130 | } 131 | 132 | ; The "Select Window" (Dialog to choose machine to log on to) opened or closed 133 | ; This is the first window that opens 134 | SelectWindowChanged(hwnd){ 135 | WinGetText, t, % "ahk_id " hwnd 136 | if (t = "OK`r`n&Help`r`n"){ 137 | this.Log("Select Window: Bad Address. Aborting") 138 | ;this.CloseSession("Could not find remote computer!") 139 | this.CloseSession(this._ErrorCodes.BAD_ADDRESS) 140 | } else { 141 | this._SelectWindow := hwnd 142 | if (hwnd){ 143 | this.Log("Select Window: Selecting remote machine") 144 | ; Enter name of machine into "Computer" editbox 145 | ControlSetText, Edit1, % this._address, % "ahk_id " hwnd 146 | Sleep % this._options.DefaultSleep 147 | ControlSend, , % "{Enter}", % "ahk_id " hwnd 148 | ;while (!WinExist("Remote Desktop Connection ahk_exe mstsc.exe ahk_class #32770 ahk_pid " this.pid, "Connecting to:") && !this.LoginWindowExists() && !this.IdentityWindowExists()){ 149 | ; Click "Connect" 150 | ;ControlClick, Button5, % "ahk_id " hwnd 151 | ;Sleep 100 152 | ;} 153 | Sleep 250 154 | } 155 | } 156 | } 157 | 158 | LoginWindowExists(){ 159 | return WinExist("Windows Security ahk_exe mstsc.exe ahk_class #32770 ahk_pid " this.pid)+0 160 | } 161 | 162 | ; The "Warning Window" opened or closed 163 | WarningWindowChanged(hwnd){ 164 | this._WarningWindow := hwnd 165 | if (hwnd){ 166 | Sleep % this._options.DefaultSleep 167 | ControlClick, Button1, % "ahk_id " hwnd 168 | Sleep % this._options.DefaultSleep 169 | ControlClick, Button11, % "ahk_id " hwnd 170 | } 171 | } 172 | 173 | ; The "Login Window" opened or closed 174 | ; If the address was valid, then this is typically the second window that opens 175 | LoginWindowChanged(hwnd){ 176 | ;msgbox % "LoginWindowChanged`n`n New HWND = " hwnd "`nOld HWND = " this._LoginWindow 177 | this._LoginWindow := hwnd 178 | if (hwnd){ 179 | if (this._LoginAttempts >= this._options.MaxLoginAttempts){ 180 | this.Log("Login Window: Login Failed. Aborting") 181 | ;this.CloseSession("Login Failed!") 182 | this.CloseSession(this._ErrorCodes.LOGIN_FAILED) 183 | } else { 184 | ; Send Down Arrow to select "Use another account" 185 | this.Log("Login Window: Entering credentials...") 186 | ;WinActivate, % "ahk_id " hwnd 187 | Sleep % this._options.DefaultSleep 188 | ; This part can be unreliable if you are clicking around a lot while it happens 189 | Critical 190 | WinActivate, % "ahk_id " hwnd 191 | ControlSend, , {down}, % "ahk_id " hwnd 192 | Critical, Off 193 | ; end problem section 194 | Sleep % this._options.DefaultSleep 195 | ; Enter Username 196 | ControlSetText, Edit2, % this._username, % "ahk_id " hwnd 197 | ; Enter Password 198 | ControlSetText, Edit3, % this._password, % "ahk_id " hwnd 199 | if (!this._options.ShowOptions){ 200 | Sleep % this._options.DefaultSleep 201 | ControlSend, , % "{Enter}", % "ahk_id " hwnd 202 | ; Click OK 203 | ;while (WinExist("ahk_id " hwnd)){ 204 | ;ControlClick, Button2, % "ahk_id " hwnd 205 | ;Sleep 100 206 | ;} 207 | } 208 | this._LoginAttempts++ 209 | ;msgbox % this._options.ShowOptions 210 | } 211 | } 212 | } 213 | 214 | ; The "Certificate Window" opened or closed. 215 | ; This may appear after the "Login Window". Ticking a box and clicking OK stops it happening again for that connection. 216 | ; Once you click OK, the choice is cached: Windows places a reg key at HKCU\Software\Microsoft\Terminal Server Client\Servers 217 | CertificateWindowChanged(hwnd){ 218 | this._CertificateWindow := hwnd 219 | if (hwnd){ 220 | this.Log("Certificate Window: Dismissing...") 221 | ; Tick "Don't ask me again for connections to this computer" 222 | ControlClick, Button3, % "ahk_id " hwnd 223 | Sleep % this._options.DefaultSleep 224 | ;ControlSend, , % "{Enter}", % "ahk_id " hwnd 225 | while (WinExist("ahk_id " hwnd)){ 226 | ; Click "Yes" 227 | ControlClick, Button5, % "ahk_id " hwnd 228 | Sleep 100 229 | } 230 | } 231 | } 232 | 233 | IdentityWindowExists(){ 234 | hwnd := WinExist("Remote Desktop Connection ahk_exe mstsc.exe ahk_class #32770 ahk_pid " this.pid)+0 235 | WinGetText, t, % "ahk_id " hwnd 236 | t := StrSplit(t, "`r") 237 | t := StrSplit(t[1], "`n") 238 | t := t[1] 239 | if (t = "This problem can occur if the remote computer is running a version of Windows that is earlier than Windows Vista, or if the remote computer is not configured to support server authentication."){ 240 | return true 241 | } else { 242 | return false 243 | } 244 | } 245 | 246 | ; The "Identity Window" opened or closed. 247 | ; This may appear after the "Login Window". Ticking a box and clicking OK stops it happening again for that connection. 248 | ; Once you click OK, the choice is cached: Windows places a reg key at HKCU\Software\Microsoft\Terminal Server Client\Servers 249 | IdentityWindowChanged(hwnd){ 250 | this._IdentityWindow := hwnd 251 | if (hwnd){ 252 | this.Log("Identity Window: Dismissing...") 253 | ; Tick "Don't ask me again for connections to this computer" 254 | ControlClick, Button1, % "ahk_id " hwnd 255 | Sleep % this._options.DefaultSleep 256 | ControlSend, , % "{Enter}", % "ahk_id " hwnd 257 | ;while (WinExist("ahk_id " hwnd)){ 258 | ; Click "Yes" 259 | ;ControlClick, Button2, % "ahk_id " hwnd 260 | ;Sleep 100 261 | ;} 262 | } 263 | } 264 | 265 | ; The actual Session Window opened or closed (The one with the remote desktop in) 266 | ; This is the final window to open, and terminates the connection sequence. 267 | ; If the session closed (eg the user logged off), it will be called again and the hwnd will be 0 268 | SessionWindowChanged(hwnd){ 269 | this.hwnd := hwnd 270 | if (hwnd){ 271 | if (this._options.WaitForDesktopColor){ 272 | this.Log("Session Window: Connected, waiting for desktop color " this._options.WaitForDesktopColor) 273 | found := 0 274 | end := A_TickCount + 30000 275 | Critical, On 276 | while (A_TickCount < end){ 277 | WinActivate, % "ahk_id " this.hwnd 278 | PixelGetColor, col, 0, 0, RGB 279 | if (col = this._options.WaitForDesktopColor){ 280 | found := 1 281 | break 282 | } 283 | } 284 | Critical, Off 285 | if (!found){ 286 | this._callback.Call(this._ErrorCodes.WAIT_DESKTOP_FAILED, this.pid, hwnd) 287 | return 288 | } 289 | } else { 290 | this.Log("Session Window: Connected") 291 | } 292 | Sleep 500 293 | this.Log("Firing connection callback...") 294 | ; Connected 295 | this._callback.Call(this._ErrorCodes.OK, this.pid, hwnd) 296 | } else { 297 | this.Log("Session Window: Disconnected") 298 | ; Went from connected to disconnected 299 | this.CloseSession(this._ErrorCodes.OK) 300 | } 301 | } 302 | 303 | ; Session window closed 304 | CloseSession(e){ 305 | this.Log("CloseSession: Firing session close callback") 306 | ; Stop watching for window events 307 | if (IsObject(this._WatchTimer)){ 308 | fn := this._WatchTimer 309 | SetTimer, % fn, Off 310 | } 311 | this.KillAllWindows() 312 | this._LoginAttempts := 0 313 | 314 | ; Fire the callback with the appropriate status code 315 | this._callback.Call(e, this.pid, hwnd) 316 | } 317 | 318 | KillAllWindows(){ 319 | ; Kill all windows for this PID 320 | while (WinExist("ahk_pid " this.pid)){ 321 | WinClose 322 | } 323 | } 324 | 325 | ; Returns a human-friendly name for a given error number 326 | GetErrorName(e){ 327 | name := this._ErrorNames[e] 328 | if (name) 329 | return name 330 | else 331 | return "Unknown Error (" e ")" 332 | } 333 | 334 | ; Returns an error code from a string 335 | GetErrorCode(e){ 336 | code := this._ErrorCodes[e] 337 | if (code != "") 338 | return code 339 | else 340 | return -1 341 | } 342 | 343 | ; Maximizes or Restores the Session Window (turns on or off fullscreen) 344 | SetMaximizedState(state){ 345 | if (this.hwnd){ 346 | WinGet, currstate, Style, % "ahk_id " this.hwnd 347 | if (state && (currstate & 0x00C00000)){ ; WS_CAPTION 348 | Send ^!{CtrlBreak} 349 | } else if (!state && ((currstate & 0x00C00000) = 0)) { 350 | PostMessage, 0x112, 0xF120,,, % "ahk_id " this.hwnd ; 0x112 = WM_SYSCOMMAND, 0xF120 = SC_RESTORE 351 | } 352 | } 353 | } 354 | 355 | ; Sends keystrokes to the Session Window 356 | SendKeys(keys){ 357 | if (this.hwnd){ 358 | ControlSend, , % keys, % "ahk_id " this.hwnd 359 | } 360 | } 361 | 362 | Log(str){ 363 | OutputDebug % "RDPConnect (PID " this.pid "): " str 364 | } 365 | } -------------------------------------------------------------------------------- /RDPSelector/RDPSelector.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | RDP Selector 3 | 4 | A tool to make it quicker to select, connect, and switch between VMs in a Microsoft Test Manager (MTM) environment. 5 | 6 | * Will automatically pull a list of Projects / Environments / VMs from your TFS server. 7 | * Handy hotkeys (eg WIN+Tab to quickly select from a list of open RDP sessions). 8 | * Renames window titles (eg "Fred on Exchange1" rather than "VSLM-8415-46ef4c57-3eb4-4e96-8f1a-25476653e416"). 9 | * Automatically marks environments as in-use in the MTM Lab Center. 10 | */ 11 | OutputDebug DBGVIEWCLEAR 12 | #SingleInstance force 13 | #include ; Used for serializing objects to/from disk. http://ahkscript.org/boards/viewtopic.php?f=6&t=627 14 | ;#include Lib\JSON.ahk ; Used for serializing objects to/from disk. http://ahkscript.org/boards/viewtopic.php?f=6&t=627 15 | ;#include 16 | #include Lib\RDPConnect.ahk 17 | 18 | ; Automatically hide console windows (Used by the powershell scripts) 19 | DllCall("AllocConsole") 20 | WinHide % "ahk_id " DllCall("GetConsoleWindow", "ptr") 21 | 22 | FileInstall, Credentials.ini, Credentials.ini 23 | FileInstall, RDPSelector.ini, RDPSelector.ini 24 | 25 | r := new RDPSelector() 26 | return 27 | 28 | GuiClose: 29 | ExitApp 30 | 31 | 32 | GuiSize(GuiHwnd, EventInfo, Width, Height){ 33 | global r 34 | if (EventInfo = 0){ 35 | r.GuiSize(GuiHwnd, EventInfo, Width, Height) 36 | } 37 | } 38 | 39 | 40 | class RDPSelector { 41 | static version := "2.0.11" 42 | Domains := [] 43 | __New(){ 44 | IniRead, root, RDPSelector.ini, Settings, tfsroot 45 | if (root = "" || root = "ERROR"){ 46 | InputBox, root, TFS URL not set, Enter the URL for your TFS server`neg: http://tfs:8080/tfs/Something, , , 150,,,,, http://tfs:8080/tfs/Something 47 | if (ErrorLevel){ 48 | ExitApp 49 | } 50 | IniWrite, % root, RDPSelector.ini, Settings, tfsroot 51 | } 52 | this.tfsroot := root 53 | 54 | this.UnPackFiles() 55 | 56 | this.RDPConnectOptions := {DefaultSleep: 500, ShowOptions: 0, MaxLoginAttempts: 1} 57 | this.CredentialsList := new this.CCredentialsList() 58 | this.EnvironmentList := new this.CEnvironmentList(this.tfsroot) 59 | this.OpenSessions := {} 60 | 61 | ; Create the GUI 62 | Gui, Margin, 10, 10 63 | Gui, Add, Text, Center xm w300, User to connect as.`nIf this has focus, Connect with ENTER 64 | ;Gui, Add, ListView, hwndhLVUsers w300 h170 xm yp+30 AltSubmit -Multi, Name|Domain 65 | Gui, Add, ListView, hwndhLVUsers w300 h170 xm yp+30 AltSubmit -Multi, Name 66 | 67 | clickprocessor := this.ProcessClicks.Bind(this) 68 | GuiControl +g, % hLVUsers , % clickprocessor 69 | 70 | ;LV_ModifyCol(1, 200) 71 | ;LV_ModifyCol(2, 80) 72 | 73 | this.hLVUsers := hLVUsers 74 | 75 | ct := 1 76 | for title, creds in this.CredentialsList.Credentials { 77 | if (ct = 1){ 78 | o := "Select" 79 | } else { 80 | o := "" 81 | } 82 | ;LV_Add(o, title, creds.domain) 83 | LV_Add(o, title) 84 | ct++ 85 | } 86 | 87 | IniRead, domains, RDPSelector.ini, Settings, domains 88 | this.Domains := StrSplit(domains, "|") 89 | Loop % this.Domains.length(){ 90 | if (A_Index > 2) 91 | s .= "|" 92 | s .= this.Domains[A_Index] 93 | if (A_Index = 1) 94 | s .= "||" 95 | } 96 | Gui, Add, DDL, hwndhwnd w300, % s 97 | this.hDomainDDL := hwnd 98 | ;fn := this.DomainSelected.Bind(this) 99 | ;GuiControl +g, % this.hDomainDDL, % fn 100 | 101 | ; Add TreeView to select Environment / Machine 102 | Gui, Add, Text, Center x320 ym w300, Project / Environment / Machine to connect to.`nIf this has focus, Connect with ENTER 103 | Gui, Add, TreeView, hwndhTVMachines x320 w290 h170 yp+30 AltSubmit 104 | this.hTVMachines := hTVMachines 105 | this.TVMachines:=new treeview(hTVMachines) 106 | 107 | fn := this.ProcessMachineClicks.Bind(this) 108 | ;GuiControl +g, % hTVMachines , % clickprocessor 109 | GuiControl +g, % hTVMachines , % fn 110 | 111 | ct := 1 112 | for project, params in this.EnvironmentList.Environments { 113 | if (ct = 1){ 114 | o := "Select" 115 | } else { 116 | o := "" 117 | } 118 | ;proj := TV_Add(project,, "Expand " o) 119 | proj := TV_Add(project,, o) 120 | for environment, env_data in params { 121 | env := TV_Add(environment " | ", proj) 122 | ;Tv.Add({Label:"Purple",Back:0xff00ff,parent:top,Option:"Vis"}) 123 | for machine, address in env_data { 124 | TV_Add(machine, env) 125 | } 126 | } 127 | ct++ 128 | } 129 | 130 | ;Gui, Add, Text, xp yp+190 131 | Gui, Add, Button, xp yp+180 w290 center hwndhUpdateEnvStates, Refresh Environment States 132 | this.hUpdateEnvStates := hUpdateEnvStates 133 | 134 | fn := this.UpdateEnvStates.Bind(this) 135 | GuiControl +g, % hUpdateEnvStates, % fn 136 | this.UpdateEnvStates() 137 | 138 | ; Add ListView that lists current open connections 139 | Gui, Add, Text, Center xm w610 hwndhSessionsLabel, Open RDP Sessions`nIf this has focus, Activate with ENTER, Close with DELETE 140 | this.hSessionsLabel := hSessionsLabel 141 | Gui, Add, ListView, hwndhLVSessions w600 h100 AltSubmit -Multi, Project|Environment|Machine|User|UniqueID 142 | this.hLVSessions := hLVSessions 143 | GuiControl +g, % hLVSessions , % clickprocessor 144 | 145 | LV_ModifyCol(1, 120) 146 | LV_ModifyCol(2, 230) 147 | LV_ModifyCol(3, 80) 148 | LV_ModifyCol(4, 150) 149 | LV_ModifyCol(5, 0) ; Hidden column 150 | 151 | ; Help Text 152 | Gui, Add, Text, Center xm w610 hwndhInstructionsLabel, Tab switches fields, Arrow keys to navigate.`nWin + \ to toggle window.`nWin + Tab to focus Open Sessions list.`nConfigure Environments / Machines / Users via INI files. 153 | this.hInstructionsLabel := hInstructionsLabel 154 | 155 | Gui, Show, x0 y0, % "RDP Selector v" this.version " (RDPConnect v" RDPConnect.version ")" 156 | WinSet,Redraw,,A 157 | Gui +resize 158 | this.hGui := WinExist("A") 159 | 160 | ; Hotkeys which only work while the GUI is active 161 | hotkey, IfWinActive, % "ahk_id " this.hGui 162 | 163 | ; Submit form on 164 | fn := this.FormSubmitted.Bind(this) 165 | hotkey, $Enter, % fn 166 | 167 | ; Close the session on 168 | fn := this.CloseSession.Bind(this) 169 | hotkey, $Delete, % fn 170 | 171 | hotkey, IfWinActive 172 | 173 | ; Global Hotkeys 174 | ; Open the GUI and focus the Active Sessions List 175 | fn := this.ActivateSessionsDialog.Bind(this) 176 | hotkey, $#Tab, % fn 177 | 178 | ; Show / Hide the GUI 179 | fn := this.ToggleGui.Bind(this) 180 | hotkey, ~$#\, % fn 181 | 182 | ;fn := this.CycleWindowsOnWheel.Bind(this, 1) 183 | ;hotkey, *WheelUp, % fn, On 184 | ;fn := this.CycleWindowsOnWheel.Bind(this, -1) 185 | ;hotkey, *WheelDown, % fn, On 186 | 187 | ; Set up app switcher 188 | ;fn := this.WatchSwap.Bind(this) 189 | ;SetTimer, % fn, 500 190 | } 191 | 192 | ; An GUI item was double-clicked, or Enter was pressed with a GUI item highlighted 193 | FormSubmitted(){ 194 | ; Determine context of ENTER key depending on which control has focus 195 | ControlGetFocus, f , % "ahk_id " this.hGui 196 | if (f = "SysTreeView321"){ 197 | focus := "Env" 198 | } else if (f = "SysListView321"){ 199 | focus := "User" 200 | } else if (f = "SysListView322"){ 201 | focus := "Win" 202 | } else { 203 | return 204 | } 205 | 206 | if (focus = "Env" || focus = "User"){ 207 | ; Focus is Environment or User - Connect! 208 | ; Find parameters 209 | machine_idx := TV_GetSelection() 210 | TV_GetText(machine, machine_idx) 211 | machine_parent := TV_GetParent(machine_idx) 212 | ; Do not try to connect to projects 213 | if (!machine_parent){ 214 | return 215 | } 216 | TV_GetText(environment, machine_parent) 217 | environment := StrSplit(environment, " | ") 218 | environment := environment[1] 219 | env_parent := TV_GetParent(machine_parent) 220 | 221 | ; Do not try to connect to environments 222 | if (!env_parent || TV_GetParent(env_parent)){ 223 | return 224 | } 225 | TV_GetText(project, env_parent) 226 | 227 | Gui, ListView, % this.hLVUsers 228 | LV_GetText(user, LV_GetNext() ) 229 | if (!IsObject(this.CredentialsList.Credentials[user])){ 230 | SoundBeep 231 | return 232 | } 233 | 234 | ;login := this.CredentialsList.Credentials[user].domain "\" this.CredentialsList.Credentials[user].login 235 | GuiControlGet, domain, , % this.hDomainDDL 236 | login := domain "\" this.CredentialsList.Credentials[user].login 237 | password := this.CredentialsList.Credentials[user].password 238 | address := this.EnvironmentList.Environments[project, environment, machine] 239 | 240 | ;r := new RDPConnect(address, login, password, this.SessionChange.Bind(this), this.RDPConnectOptions) 241 | r := new RDPConnect(address, login, password, this.SessionChange.Bind(this)) 242 | r.project := project 243 | r.environment := environment 244 | r.machine := machine 245 | r.user := this.CredentialsList.Credentials[user].login 246 | ;r.domain := this.CredentialsList.Credentials[user].domain 247 | r.domain := domain 248 | 249 | this.OpenSessions[r.pid] := r 250 | } else { 251 | ; Focus is active Session list - switch to Session 252 | winnum := this.GetSelectedSessionIndex() 253 | if (winnum){ 254 | pid := this.PIDFromLVIndex(winnum) 255 | WinActivate, % "ahk_id " this.OpenSessions[pid].hwnd 256 | } 257 | } 258 | } 259 | 260 | ; Delete was pressed while the Open Sessions dialog had focus - Close the Session 261 | CloseSession(){ 262 | winnum := this.GetSelectedSessionIndex() 263 | if (winnum){ 264 | ;hwnd := this.PIDFromLVIndex(winnum) 265 | WinClose, % "ahk_id " this.OpenSessions[this.PIDFromLVIndex(winnum)].hwnd 266 | } 267 | } 268 | 269 | ; A double-click of a GUI item was detected 270 | ProcessClicks(){ 271 | if (A_GuiEvent = "DoubleClick"){ 272 | this.FormSubmitted() 273 | } 274 | } 275 | 276 | ProcessMachineClicks(){ 277 | if (A_GuiEvent = "DoubleClick"){ 278 | this.FormSubmitted() 279 | } else if (A_GuiEvent = "RightClick"){ 280 | this.ShowMachineContextMenu() 281 | } 282 | } 283 | 284 | ShowMachineContextMenu(){ 285 | Gui, Treeview, this.hTVMachines 286 | machine_idx := TV_GetSelection() 287 | TV_GetText(machine, machine_idx) 288 | machine_parent := TV_GetParent(machine_idx) 289 | ; Do not try to connect to projects 290 | if (!machine_parent){ 291 | return 292 | } 293 | TV_GetText(environment, machine_parent) 294 | environment := StrSplit(environment, " | ") 295 | environment := environment[1] 296 | if (!environment) 297 | return 298 | env_parent := TV_GetParent(machine_parent) 299 | TV_GetText(project, env_parent) 300 | if (!project) 301 | return 302 | startstring := "Start " environment 303 | shutdownstring := "Shutdown " environment 304 | Menu, EnvironmentOperations, Add, Dummy, MenuHandler 305 | Menu, EnvironmentOperations, DeleteAll 306 | Menu, EnvironmentOperations, Add, % startstring, MenuHandler 307 | Menu, EnvironmentOperations, Add, % shutdownstring, MenuHandler 308 | Menu, EnvironmentOperations, Show 309 | return 310 | MenuHandler: 311 | if (A_ThisMenuItem == startstring){ 312 | action := 1 313 | } else { 314 | action := 0 315 | } 316 | str := "powershell.exe -executionpolicy bypass -file powershell\startstop.ps1 """ this.tfsroot """ """ project """ """ environment """ " action 317 | OutputDebug % "Running powershell script " str 318 | RunWaitOne(str) 319 | Sleep 500 320 | 321 | this.UpdateEnvStates() 322 | return 323 | } 324 | 325 | ; Show / Hide the GUI 326 | ToggleGui(forceactive := 0){ 327 | if (forceactive || !WinActive("ahk_id " this.hGui)){ 328 | WinRestore % "ahk_id " this.hGui 329 | WinActivate % "ahk_id " this.hGui 330 | } else { 331 | WinMinimize % "ahk_id " this.hGui 332 | } 333 | } 334 | 335 | ; Show the GUI and set the focus to the "Open RDP Sessions" dialog 336 | ActivateSessionsDialog(){ 337 | this.ToggleGui(1) 338 | GuiControl, focus, % this.hLVSessions 339 | } 340 | 341 | ; An RDP session was opened or closed 342 | ; This is the callback that RDPConnect fires 343 | SessionChange(e, pid, hwnd){ 344 | if (e = 0){ 345 | ; Everything went OK 346 | ; Login Suceeded, or Session was closed 347 | if (hwnd){ 348 | ; Connected 349 | s := this.OpenSessions[pid] 350 | 351 | ct := 0 352 | for key, value in this.OpenSessions { 353 | ; Get count of number of items in array 354 | ct++ 355 | } 356 | if (ct = 1){ 357 | ; If adding 1st item, then make sure it starts selected 358 | o := "Select" 359 | } else { 360 | o := "" 361 | } 362 | ; Add session to "Open RDP Sessions" list 363 | Gui, ListView, % this.hLVSessions 364 | LV_Add(o, s.project, s.environment, s.machine, s.domain "\" s.user, s.pid) 365 | 366 | ; Change title of RDP session 367 | WinSetTitle, % "ahk_id " s.hwnd, , % s.user " on " s.machine " (" s.project "\" s.environment ")" 368 | 369 | ; Mark environment as in use in MTM 370 | found := 0 371 | for p, session in this.OpenSessions{ 372 | if (p = pid){ 373 | continue 374 | } 375 | if (this.OpenSessions[pid].environment = session.environment){ 376 | found := 1 377 | break 378 | } 379 | } 380 | if (!found){ 381 | str := "powershell.exe -executionpolicy bypass -file powershell\markinuse.ps1 """ this.tfsroot """ """ this.OpenSessions[pid].project """ """ this.OpenSessions[pid].environment """ " 1 382 | Run, % str, , Hide 383 | } 384 | } else { 385 | ; Disconnected 386 | i := this.LVIndexFromPID(pid) 387 | if (i){ 388 | LV_Delete(i) 389 | } 390 | 391 | ; Unmark in use 392 | found := 0 393 | for p, session in this.OpenSessions{ 394 | if (p = pid){ 395 | continue 396 | } 397 | if (this.OpenSessions[pid].environment = session.environment){ 398 | found := 1 399 | break 400 | } 401 | } 402 | if (!found){ 403 | str := "powershell.exe -executionpolicy bypass -file powershell\markinuse.ps1 """ this.tfsroot """ """ this.OpenSessions[pid].project """ """ this.OpenSessions[pid].environment """ " 0 404 | Run, % str, , Hide 405 | } 406 | 407 | this.OpenSessions.Delete(pid) 408 | 409 | } 410 | } else { 411 | ; There was a problem 412 | SplashTextOn, 300, 70, Connection attempt aborted! , % "`nReason: " this.OpenSessions[pid].GetErrorName(e) 413 | this.OpenSessions.Delete(pid) 414 | Sleep 1000 415 | SplashTextOff 416 | } 417 | } 418 | 419 | ; Finds the LV Index for a given PID 420 | LVIndexFromPID(pid){ 421 | Gui, ListView, % this.hLVSessions 422 | Loop % LV_GetCount() { 423 | LV_GetText(p, A_Index, 5) 424 | if (p = pid){ 425 | return A_Index 426 | } 427 | } 428 | return 0 429 | } 430 | 431 | ; Finds the PID for a given LV index 432 | PIDFromLVIndex(idx){ 433 | Gui, ListView, % this.hLVSessions 434 | LV_GetText(p, idx, 5) 435 | return p 436 | } 437 | 438 | ; Retrieves the row number for the selected Session in the Sessions Listview 439 | GetSelectedSessionIndex(){ 440 | Gui, ListView, % this.hLVSessions 441 | return LV_GetNext() 442 | } 443 | 444 | UpdateEnvStates(){ 445 | SplashTextOn, 200 , 20 , RDPSelector, Loading environment states... 446 | FileDelete, EnvStates.ini 447 | str := "powershell -executionpolicy bypass -file powershell\envstates.ps1 " this.tfsroot " > EnvStates.ini" 448 | OutputDebug % "Executing " str 449 | clipboard := str 450 | ret := RunWaitOne(str) 451 | SplashTextOff 452 | if (!FileExist("EnvStates.ini")) 453 | return 454 | FileRead, j, EnvStates.ini 455 | EnvStates := JSON.Load(j) 456 | Gui, Treeview, this.hTVMachines 457 | for project, params in EnvStates { 458 | for environment, env_data in params { 459 | 460 | } 461 | } 462 | project := "" 463 | Loop { 464 | project := TV_GetNext(project) 465 | if (!project) 466 | break 467 | TV_GetText(projname, project) 468 | env := TV_GetChild(project) 469 | Loop { 470 | if (env = 0) 471 | break 472 | TV_GetText(envname, env) 473 | envname := StrSplit(envname, " | ") 474 | envname := envname[1] 475 | 476 | state := EnvStates[projname,envname].State 477 | if (state = "stopped"){ 478 | col := 0xFFFFFF 479 | } else if (state = "mixed" || state = "paused" || state = "starting"){ 480 | col := 0x0099FF 481 | } else if (state = "notready"){ 482 | col := 0x0000FF 483 | } else if (state = "ready"){ 484 | col := 0x00FF00 485 | } else { 486 | col := 0xFF5555 487 | } 488 | TV_Modify(env,"",envname " | " state) 489 | this.TVMachines.Modify({hwnd: env, Back: col, Fore: 0}) 490 | env := TV_GetNext(env) 491 | } 492 | } 493 | 494 | } 495 | 496 | ; Resize GUI Elements when the Gui resizes 497 | GuiSize(GuiHwnd, EventInfo, Width, Height){ 498 | static w := 0, h := 0 499 | 500 | if (w && h){ 501 | delta_w := width - w 502 | delta_h := height - h 503 | ;tooltip % delta_w 504 | GuiControlGet, pos, pos, % this.hTVMachines 505 | GuiControl, Move, % this.hTVMachines, % " w" posw + delta_w " h" posh + delta_h 506 | 507 | GuiControlGet, pos, pos, % this.hLVUsers 508 | GuiControl, Move, % this.hLVUsers, % "h" posh + delta_h 509 | 510 | GuiControlGet, pos, pos, % this.hDomainDDL 511 | GuiControl, Move, % this.hDomainDDL, % "y" posy + delta_h 512 | 513 | GuiControlGet, pos, pos, % this.hUpdateEnvStates 514 | GuiControl, Move, % this.hUpdateEnvStates, % "y" posy + delta_h " w" posw + delta_w 515 | 516 | 517 | GuiControlGet, pos, pos, % this.hSessionsLabel 518 | GuiControl, Move, % this.hSessionsLabel, % " w" posw + delta_w " y" posy + delta_h 519 | 520 | GuiControlGet, pos, pos, % this.hLVSessions 521 | GuiControl, Move, % this.hLVSessions, % " w" posw + delta_w " y" posy + delta_h 522 | 523 | GuiControlGet, pos, pos, % this.hInstructionsLabel 524 | GuiControl, Move, % this.hInstructionsLabel, % " w" posw + delta_w " y" posy + delta_h 525 | 526 | } 527 | w := width 528 | h := height 529 | } 530 | 531 | UnPackFiles(){ 532 | ; Pack required files into compiled EXE, pull out on run of compiled exe 533 | FileCreateDir powershell 534 | FileInstall, powershell\mtmlist.ps1, powershell\mtmlist.ps1 535 | FileInstall, powershell\envstates.ps1, powershell\envstates.ps1 536 | FileInstall, powershell\markinuse.ps1, powershell\markinuse.ps1 537 | FileInstall, powershell\Microsoft.VisualStudio.Services.Common.dll, powershell\Microsoft.VisualStudio.Services.Common.dll 538 | FileInstall, powershell\Microsoft.TeamFoundation.Common.dll, powershell\Microsoft.TeamFoundation.Common.dll 539 | FileInstall, powershell\Microsoft.TeamFoundation.Client.dll, powershell\Microsoft.TeamFoundation.Client.dll 540 | FileInstall, powershell\Microsoft.TeamFoundation.Lab.Client.dll, powershell\Microsoft.TeamFoundation.Lab.Client.dll 541 | FileInstall, powershell\Microsoft.TeamFoundation.Lab.Common.dll, powershell\Microsoft.TeamFoundation.Lab.Common.dll 542 | FileInstall, powershell\Microsoft.VisualStudio.Services.Client.dll, powershell\Microsoft.VisualStudio.Services.Client.dll 543 | FileInstall, powershell\Microsoft.VisualStudio.Services.WebApi.dll, powershell\Microsoft.VisualStudio.Services.WebApi.dll 544 | FileInstall, powershell\Newtonsoft.Json.dll, powershell\Newtonsoft.Json.dll 545 | FileInstall, Credentials.Default.ini, Credentials.ini 546 | } 547 | 548 | /* 549 | WatchSwap(){ 550 | if (this.IsMouseAtEdgeOfWindow() && this.isWindowFullScreen( "A" )){ 551 | fn := this.CycleWindowsOnWheel.Bind(this, 1) 552 | hotkey, *WheelUp, % fn, On 553 | fn := this.CycleWindowsOnWheel.Bind(this, -1) 554 | hotkey, *WheelDown, % fn, On 555 | } else { 556 | fn := this.CycleWindowsOnWheel.Bind(this, 1) 557 | hotkey, *WheelUp, % fn, Off 558 | fn := this.CycleWindowsOnWheel.Bind(this, -1) 559 | hotkey, *WheelDown, % fn, Off 560 | } 561 | } 562 | 563 | CycleWindowsOnWheel(dir){ 564 | if (this.isWindowFullScreen( "A" ) && this.IsMouseAtEdgeOfWindow()){ 565 | hwnd := WinExist("A") 566 | WinGet, active_pid, pid 567 | last_window := 0 568 | next_window := 0 569 | found_this := 0 570 | 571 | indexed := [] 572 | this_index := 0 573 | i := 0 574 | for pid in this.OpenSessions { 575 | i++ 576 | indexed.push(pid) 577 | if (pid = active_pid) 578 | this_index := i 579 | } 580 | 581 | max := indexed.length() 582 | this_index += dir 583 | 584 | if (this_index < 1){ 585 | this_index := max 586 | } else if (this_index > max){ 587 | this_index := 1 588 | } 589 | 590 | new_pid := indexed[this_index] 591 | 592 | hwnd := this.OpenSessions[new_pid].hwnd 593 | WinActivate, % "ahk_id " hwnd 594 | ToolTip % "User: " this.OpenSessions[new_pid].domain "\" this.OpenSessions[new_pid].user "`nMachine: " this.OpenSessions[new_pid].machine "`nEnvironment: " this.OpenSessions[new_pid].project "\" this.OpenSessions[new_pid].environment 595 | fn := this.RemoveToolTip.Bind(this) 596 | SetTimer, % fn, -3000 597 | } else { 598 | if (dir > 0){ 599 | Send {WheelUp} 600 | } else { 601 | Send {WheelDown} 602 | } 603 | } 604 | } 605 | 606 | RemoveToolTip(){ 607 | Tooltip 608 | } 609 | 610 | IsMouseAtEdgeOfWindow(){ 611 | hwnd := WinExist("A") 612 | MouseGetPos, mx, my 613 | WinGetPos, wx, wy, ww, wh, % "ahk_id " hwnd 614 | ;tooltip % "hwnd: " hwnd ", mx: " mx ", wx: " wx ", ww: " ww 615 | if (mx < 10){ 616 | return 1 617 | } else if (mx >= ww - 10){ 618 | return 1 619 | } 620 | return 0 621 | } 622 | 623 | IsWindowFullScreen( winTitle ) { 624 | ;checks if the specified window is full screen 625 | 626 | winID := WinExist( winTitle ) 627 | 628 | If ( !winID ) 629 | Return false 630 | 631 | WinGet style, Style, ahk_id %WinID% 632 | WinGetPos ,,,winW,winH, %winTitle% 633 | ; 0x800000 is WS_BORDER. 634 | ; 0x20000000 is WS_MINIMIZE. 635 | ; no border and not minimized 636 | Return ((style & 0x20800000) or winH < A_ScreenHeight or winW < A_ScreenWidth) ? false : true 637 | } 638 | */ 639 | 640 | Class CCredentialsList { 641 | __New(){ 642 | this.Credentials := {} 643 | IniRead, Users, credentials.ini 644 | Users := StrSplit(users, "`n") 645 | Loop % users.length(){ 646 | IniRead, login, credentials.ini, % users[A_Index], login 647 | IniRead, password, credentials.ini, % users[A_Index], password 648 | ;IniRead, domain, credentials.ini, % users[A_Index], domain 649 | ;this.Credentials[users[A_Index]] := {login: login, password: password, domain: domain} 650 | this.Credentials[users[A_Index]] := {login: login, password: password} 651 | } 652 | } 653 | } 654 | 655 | class CEnvironmentList { 656 | __New(tfsroot){ 657 | if (!FileExist("Environments.ini")){ 658 | SplashTextOn, 200 , 20 , RDPSelector, Loading environment list... 659 | RunWaitOne("powershell -executionpolicy bypass -file powershell\mtmlist.ps1 " tfsroot " > Environments.ini") 660 | SplashTextOff 661 | } 662 | FileRead, j, Environments.ini 663 | try { 664 | this.Environments := JSON.Load(j) 665 | } catch { 666 | msgbox % "MTMlist powershell script failed.`nOutput`n`n" j 667 | } 668 | } 669 | } 670 | } 671 | 672 | RunWaitOne(command) { 673 | ; WshShell object: http://msdn.microsoft.com/en-us/library/aew9yb99 674 | shell := ComObjCreate("WScript.Shell") 675 | ; Execute a single command via cmd.exe 676 | exec := shell.Exec(ComSpec " /C " command) 677 | ; Read and return the command's output 678 | return exec.StdOut.ReadAll() 679 | } 680 | 681 | ; Maestrith's "Treeview Custom Item Colors" library 682 | ; https://autohotkey.com/boards/viewtopic.php?t=2632 683 | class treeview{ 684 | static list:=[] 685 | __New(hwnd){ 686 | this.list[hwnd]:=this 687 | OnMessage(0x4e,"WM_NOTIFY") 688 | this.hwnd:=hwnd 689 | } 690 | add(info){ 691 | Gui,TreeView,% this.hwnd 692 | hwnd:=TV_Add(info.Label,info.parent,info.option) 693 | if info.fore!="" 694 | this.control["|" hwnd,"fore"]:=info.fore 695 | if info.back!="" 696 | this.control["|" hwnd,"back"]:=info.back 697 | return hwnd 698 | } 699 | modify(info){ 700 | this.control["|" info.hwnd,"fore"]:=info.fore 701 | this.control["|" info.hwnd,"back"]:=info.back 702 | WinSet,Redraw,,A 703 | } 704 | Remove(hwnd){ 705 | this.control.Remove("|" hwnd) 706 | } 707 | } 708 | WM_NOTIFY(Param*){ 709 | static list:=[],ll:="" 710 | control:= 711 | if (this:=treeview.list[NumGet(Param.2)])&&(NumGet(Param.2,2*A_PtrSize,"int")=-12){ 712 | stage:=NumGet(Param.2,3*A_PtrSize,"uint") 713 | if (stage=1) 714 | return 0x20 ;sets CDRF_NOTIFYITEMDRAW 715 | if (stage=0x10001&&info:=this.control["|" numget(Param.2,A_PtrSize=4?9*A_PtrSize:7*A_PtrSize,"uint")]){ ;NM_CUSTOMDRAW && Control is in the list 716 | if info.fore!="" 717 | NumPut(info.fore,Param.2,A_PtrSize=4?12*A_PtrSize:10*A_PtrSize,"int") ;sets the foreground 718 | if info.back!="" 719 | NumPut(info.back,Param.2,A_PtrSize=4?13*A_PtrSize:10.5*A_PtrSize,"int") ;sets the background 720 | } 721 | } 722 | } --------------------------------------------------------------------------------