Quantcast
Channel: Control-F5 - C#
Viewing all articles
Browse latest Browse all 13

Connect to Multiple Virtual Machines - C# Desktop App

$
0
0

Ever had the necessity to connect to many VMs at one time? 


Maybe one is your primary development machine, then you have maybe a storage Server, another one for your Database etc.

Here is a pretty simple Windows Forms app where you can manage all of these - inside, in a tabular way. One App to rule them all! Instead of several instances of Remote Desktop Connection.

Build the GUI like (I won't go on details on Anchors, Tabs and stuff - the project is attached so you can use it and look at the code in more detail):


The GUI has 5 major controls:

  • Tab control
  • Microsoft RDP client control - this is an ActiveX control that we will be using to achieve the connection to the remote machines
  • Textbox Control's for credentials to connect to the server: IP, User, Password
  • The ">" button is used to create another tab with the Microsoft RDP control inside
  • Connect and Disconnect buttons
Before we start coding we need to Import a library so References > search for and add:



Next go to Toolbox > Choose Items:


At this point you will have the "Microsoft Terminal Services Client Control - version 1" in your toolbox ready to use.

In the form UI we will create code and event handlers for our controls:
using System;using System.Windows.Forms;using MSTSCLib;using AxMSTSCLib;using System.Collections.Generic;namespace RemoteDesktop{public partial class RemoteConnectionForm : Form{// Used to dinamically add tabsprivate DynamicTabGenerator tabGenerator;// Used to validate credentialsprivate Validation validation;//Restore and store credentials that achieved a successful connectionprivate List<ConnectionCredentials> credentialsList;private StoreManager storeManager;// Timer Object User to Invalidate Connection button for 5 seconds
        Timer connectionTimer;// Constructorpublic RemoteConnectionForm(){
            InitializeComponent();

            tabGenerator =new DynamicTabGenerator();
            validation =new Validation();
            credentialsList =new List<ConnectionCredentials>();
            storeManager =new StoreManager();

            connectionTimer =new Timer();
            connectionTimer.Interval =10000;
            connectionTimer.Tick += Timer_Tick;

            txtPassword.PasswordChar ='*';
            WindowState = FormWindowState.Maximized;

            LoadExistingCredentials();}privatevoid LoadExistingCredentials(){
            credentialsList = storeManager.LoadCredentials();if(credentialsList.Count !=0){
                txtServer.Text = credentialsList[0].ServerIP;
                txtUserName.Text = credentialsList[0].UserName;
                txtPassword.Text = credentialsList[0].UserPassword;}}// Try to connect to specified remoteprivatevoid ConnectRemote(){
            connectionTimer.Start();
            btnConnect.Enabled =false;try{var remote = GetTabPageSpecificRemoteControl(null);if(remote ==null){
                    MessageBox.Show("Form not properly configured.");return;}

                remote.Server = txtServer.Text;
                remote.UserName = txtUserName.Text;var secured =(IMsTscNonScriptable)remote.GetOcx();
                secured.ClearTextPassword = txtPassword.Text;
                remote.Connect();}catch(Exception err){// Notify for specific errors
                MessageBox.Show(err.Message,"Error", MessageBoxButtons.OK);}}// Verify if connection is still openedprivatevoid DisconnectRemote(AxMsTscAxNotSafeForScripting remote){try{if(remote.Connected.ToString()=="1"){
                    remote.Disconnect();}}catch{}}// Validate connection credentialsprivatebool ValidateInput(){if(!validation.Validate(GetCurrentPageCredentials())){
                MessageBox.Show("Server, Username or Password cannot be empty.");returnfalse;}returntrue;}// Will create an instance of ConnectionCredentials with values from the formprivate ConnectionCredentials GetCurrentPageCredentials(){
            ConnectionCredentials credentials =new ConnectionCredentials();
            credentials.ServerIP = txtServer.Text;
            credentials.UserName = txtUserName.Text;
            credentials.UserPassword = txtPassword.Text;return credentials;}// Extract specific connection control from a specific tab page// Page param must be null if you want method to detect user selected tabprivate AxMsTscAxNotSafeForScripting GetTabPageSpecificRemoteControl(TabPage page){if(page ==null){
                page = tabCtrl.SelectedTab;}foreach(var ctrl in page.Controls){if(ctrl is AxMsTscAxNotSafeForScripting){return(AxMsTscAxNotSafeForScripting)ctrl;}}returnnull;}// Connect Button event handlerprivatevoid btnConnect_Click(object sender, EventArgs e){if(ValidateInput()){
                ConnectRemote();}}// Disconnect Button event handlerprivatevoid btnDisconnect_Click(object sender, EventArgs e){
            DisconnectRemote(GetTabPageSpecificRemoteControl(null));}// The ">" - add tab button that dynamically creates a new tab with remote control insideprivatevoid btnCreateTab_Click(object sender, EventArgs e){
            tabGenerator.CreateNewTab(tabCtrl);}// Event Raised when tab is changedprivatevoid tabCtrl_SelectedIndexChanged(object sender, EventArgs e){if(credentialsList.Count >= tabCtrl.SelectedIndex +1){int index = tabCtrl.SelectedIndex;
                txtServer.Text = credentialsList[index].ServerIP;
                txtUserName.Text = credentialsList[index].UserName;
                txtPassword.Text = credentialsList[index].UserPassword;}else{
                txtServer.Text =string.Empty;
                txtUserName.Text =string.Empty;
                txtPassword.Text =string.Empty;}

            btnConnect.Enabled =true;}// Will check if remote control is connected after the 10 sec interval// the reason for this is because connection cannot be checked instantlyprivatevoid Timer_Tick(object sender, EventArgs e){if(!validation.ValidateConnection(GetTabPageSpecificRemoteControl(null))){
                MessageBox.Show("Connection failed. Please check your credentials.");}else{var credentials = GetCurrentPageCredentials();if(!validation.AreCredentialsDuplicate(credentials, credentialsList)){
                    credentialsList.Add(credentials);}}
            btnConnect.Enabled =true;
            connectionTimer.Stop();}// Form Closing Event Handler// Make sure all connections are closedprivatevoid RemoteConnectionForm_FormClosing(object sender, FormClosingEventArgs e){foreach(TabPage tabPage in tabCtrl.TabPages){var remote = GetTabPageSpecificRemoteControl(tabPage);
                DisconnectRemote(remote);}if(credentialsList.Count >0){
                storeManager.SaveCredentials(credentialsList);}}}}

For our credentials we create a new class: 
publicclass ConnectionCredentials{publicstring ServerIP {get;set;}publicstring UserName {get;set;}publicstring UserPassword {get;set;}}

To generate new tab with all we need functional inside it create a new class DynamicTabGenerator: 
using AxMSTSCLib;using System.ComponentModel;using System.Windows.Forms;namespace RemoteDesktop{publicclass DynamicTabGenerator{private AxMsTscAxNotSafeForScripting remoteConControl;// Dinamically create Tab Page Control and remote connection control inside itpublicvoid CreateNewTab(TabControl tabControl){
            TabPage page =new TabPage("VM"+(tabControl.TabPages.Count +1).ToString());

            tabControl.TabPages.Add(page);

            remoteConControl = CreateRemoteControl();

            page.Controls.Add(remoteConControl);}// Create the remote connection object / controlprivate AxMsTscAxNotSafeForScripting CreateRemoteControl(){
            ComponentResourceManager resources =new ComponentResourceManager(typeof(RemoteConnectionForm));
            remoteConControl =new AxMsTscAxNotSafeForScripting();//Code above is related to UI stuff - same as designer file code for the controls on tab1/2 ((ISupportInitialize)(remoteConControl)).BeginInit();
            remoteConControl.Anchor =((AnchorStyles)((((AnchorStyles.Top 
                                                       | AnchorStyles.Bottom)
                                                       | AnchorStyles.Left)
                                                       | AnchorStyles.Right)));
            remoteConControl.Enabled =true;
            remoteConControl.Location =new System.Drawing.Point(0,0);
            remoteConControl.Name ="remoteConControl";
            remoteConControl.OcxState =((AxHost.State)(resources.GetObject("remoteConControl.OcxState")));
            remoteConControl.Size =new System.Drawing.Size(1001,670);
            remoteConControl.TabIndex =0;
            remoteConControl.Dock = DockStyle.Fill;return remoteConControl;}}}

We don't want to always type our credentials so let's make the app smart enough so it can remember the credentials. All credentials that performed a successful connections will be stored in XML except passwords. If you want to save passwords as well then you will need encryption. For this we create class StoreManager:
using System.Collections.Generic;using System.IO;using System.Xml;using System.Xml.Serialization;namespace RemoteDesktop{publicclass StoreManager{// Save credentials except password to XMLpublicvoid SaveCredentials(List<ConnectionCredentials> credentials){
            CreateFileIfNeeded();
            DeletePW(credentials);
            XmlSerializer serializer =new XmlSerializer(typeof(List<ConnectionCredentials>));// Serialize List of Connnection Credentials to XMLusing(FileStream stream = File.OpenWrite(Directory.GetCurrentDirectory()+@"\"+ fileName)){
                serializer.Serialize(stream, credentials);}}// Return the list of saved credentials from XML filepublic List<ConnectionCredentials> LoadCredentials(){
            List<ConnectionCredentials> credentials =new List<ConnectionCredentials>();if(VerifyIfFileExists(fileName)){
                XmlSerializer serializer =new XmlSerializer(typeof(List<ConnectionCredentials>));// Desirialize list of credentials from XML and cast to method return typeusing(FileStream stream = File.OpenRead(Directory.GetCurrentDirectory()+@"\"+ fileName)){
                    credentials =(List<ConnectionCredentials>)serializer.Deserialize(stream);}}return credentials;}// Create XML file if it doesn't already existprivatevoid CreateFileIfNeeded(){if(!VerifyIfFileExists(fileName)){
                XmlWriterSettings settings =new XmlWriterSettings();
                settings.Indent =true;using(XmlWriter writer = XmlWriter.Create(fileName, settings)){
                    writer.Dispose();}}}// Return true if file exists otherwise falseprivatebool VerifyIfFileExists(string fileName){string fullFilePath =string.Concat(Directory.GetCurrentDirectory(),@"\", fileName);return File.Exists(fullFilePath);}// We do not want to store PW so they will be deleted from credentials listprivatevoid DeletePW(List<ConnectionCredentials> credentials){
            credentials.ForEach(x => x.UserPassword =string.Empty);}// Name of the file that will store our dataprivateconststring fileName ="ucs.xml";}}

Now we will also want our application to make some validations like see if Credentials fields are not empty strings or they are not already in the XML store and see if connection is ok:
using AxMSTSCLib;using System.Collections.Generic;namespace RemoteDesktop{publicclass Validation{// Very simple Validation // it will look if all connection credentials are not empty// returns true if validation succeded otherwise falsepublicbool Validate(ConnectionCredentials credentials){if(string.IsNullOrWhiteSpace(credentials.ServerIP) ||string.IsNullOrWhiteSpace(credentials.UserName) ||string.IsNullOrWhiteSpace(credentials.UserPassword)){returnfalse;}returntrue;}// Validate if remote object is in connected statepublicbool ValidateConnection(AxMsTscAxNotSafeForScripting remote){return remote.Connected.ToString()=="1";}// Verify if credentials already exist in listpublicbool AreCredentialsDuplicate(ConnectionCredentials credentials,
                                            List<ConnectionCredentials> credentialsList){foreach(var item in credentialsList){if(credentials.ServerIP == item.ServerIP && credentials.UserName == item.UserName){returntrue;}}returnfalse;}}}

I first saw this on CodeProject (check it out for more details, good post) so I thought this might be useful for me so I build a complete app. 

Let me know if you have any questions, full project is attached: RemoteDesktop.zip.

Cheers,
Alex 


Viewing all articles
Browse latest Browse all 13

Trending Articles