Introduction
We have many, many servers at my job. Enterprise applications like SQL Server, Oracle, etc., are installed in them by the system administrators. But when we (the developers) create a new application or a new component, we have to deploy the new assemblies in each server. We don't like this, we hate this, so I tried to make an application which make this task easier for us...
Background
There are two applications. Both of them use sockets to communicate with each other and transfer the file names and data within. These are the applications:
ReceiveFiles
This is a console application that listens for new requests. When a request is sent to a server, this application gets it and copies the file to the desired final path. This is not a Windows service that is listening all the time. I will later explain how to start this thing remotely.
SendFiles
This is a Windows application, with a little GUI, used to start the remote ReceiveFiles application and send the files to the server.
PsExec
PsExec is a Windows application which you can use to start a remote process. It is used to start ReceiveFiles remotely. I can not attach it here, because it's forbidden. But you can download it for free from here: http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx.
How to use it
- Copy ReceiveFiles in the servers you want to deploy files. With a little luck, this will be the last time you will have to copy anything by yourself.
- You have to download PsExec from the Microsoft web page in order to start ReceiveFiles remotely. Once it is downloaded, you have to copy PsExec in the same path where SendFiles is installed
- Populate the configuration file with the server names, and users and passwords.
- Start the SendFiles app. In the first text box, "Path where ReceiveFile is...", you have to write the remote path where ReceiveFiles is installed. It's recommended that the path is the same in all machines.
- Select the desired servers and click "Start remote listeners" to start the remote ReceiveFiles application.
- Once the remote applications are started (you can check it in the MS DOS windows), you can select the files to copy by clicking the "Get Files" button. Choose what you want.
- Click the "Send Files" button to copy the selected files to all servers.
- Once the process is finished, you can close the remote apps by clicking "Close remote listeners".
That's all...
Using the code
The code is a modification of some code found in the web; so far it works OK!
Once the ReceiveFiles app is started, it will open two sockets in an infinite loop until the application is closed manually or a closing request is received.
The first of the sockets is open by the ReceiveFilename
method. It will wait for a file name and a path to start the process. When the data is received (the file name), it will create the path in case it doesn't exist. Later, the ReceiveFile
method will open another socket in order to wait for the binary data, which is the content of the file. After the file is created, it will wait for any other request. If the file name is "CLOSE APP", the application will shutdown, because this is the way to close the app remotely.
The core of the ReceiveFiles app is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
namespace RecepcionFicherosSocket
{
public class SocketsFileTransfer
{
private const string STRING_TO_CLOSE = "CLOSE APP";
public void ReceiveFile(int port, string filepathandname)
{
string methodname = "ReceiveFile";
try
{
IPEndPoint ipEnd = new IPEndPoint(IPAddress.Any, port);
Socket sock = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.IP);
sock.Bind(ipEnd);
sock.Listen(1);
Socket serverSocket = sock.Accept();
byte[] data = new byte[1000000];
int received = serverSocket.Receive(data);
int filenameLength = BitConverter.ToInt32(data, 0);
string filename = Encoding.ASCII.GetString(data, 4, filenameLength);
this.CreateDirectoryFromPath(filepathandname);
BinaryWriter bWrite = new BinaryWriter(
File.Open(filepathandname, FileMode.Create));
bWrite.Write(data, filenameLength + 4, received - filenameLength - 4);
int received2 = serverSocket.Receive(data);
while (received2 > 0)
{
bWrite.Write(data, 0, received2);
received2 = serverSocket.Receive(data);
}
bWrite.Close();
serverSocket.Close();
sock.Close();
MyLogs.WriteLog(methodname, "File copied ok: " +
filepathandname, false);
}
catch (Exception ex)
{
MyLogs.WriteLog(methodname, "Port: " + port +
" file: " + filepathandname +
" " + ex.ToString(), true);
}
}
public string ReceiveFilename(IPAddress ipAddress, int port)
{
string filename = string.Empty;
string methodname = "ReceiveFilename";
try
{
TcpListener tcpListener = new TcpListener(ipAddress, port);
tcpListener.Start();
Socket socketForClient = tcpListener.AcceptSocket();
if (socketForClient.Connected)
{
MyLogs.WriteLog(methodname,"Connected",false);
NetworkStream networkStream = new NetworkStream(socketForClient);
StreamWriter streamWriter = new StreamWriter(networkStream);
StreamReader streamReader = new StreamReader(networkStream);
string theString = "Ok so far";
filename = streamReader.ReadLine();
MyLogs.WriteLog(methodname,theString +
" " + filename,false);
streamWriter.WriteLine(theString);
streamWriter.Flush();
streamReader.Close();
streamWriter.Close();
networkStream.Close();
socketForClient.Close();
tcpListener.Stop();
if (filename == STRING_TO_CLOSE)
{
MyLogs.WriteLog(methodname, "Closing requested", false);
Environment.Exit(999);
}
}
}
catch (Exception ex)
{
MyLogs.WriteLog("ReceiveFilename", ex.ToString(), true);
}
return filename;
}
public IPAddress GetIpFromHostName(string servername)
{
IPAddress ip = null;
String strHostName = string.Empty;
if (servername == string.Empty)
{
strHostName = Dns.GetHostName();
MyLogs.WriteLog("GetIpFromHostName",
"Machine name: " + strHostName,false);
}
else
{
strHostName = servername;
}
IPHostEntry ipEntry = Dns.GetHostByName(strHostName);
IPAddress[] addr = ipEntry.AddressList;
ip = addr[0];
return ip;
}
private void CreateDirectoryFromPath(string path)
{
string directoryPath = System.IO.Path.GetDirectoryName(path);
if (Directory.Exists(directoryPath) == false)
{
try
{
Directory.CreateDirectory(directoryPath);
MyLogs.WriteLog("CreateDirectoryFromPath",
"Directory: " + directoryPath +
" created", false);
}
catch (Exception ex)
{
MyLogs.WriteLog("CreateDirectoryFromPath",
"Cant create directory for: " + path +
" " + ex.ToString(), true);
}
}
}
}
}
The main point of the ReceiveFiles app will make a non-stop execution of the application, until it is closed or a closing request is received.
The main point of the ReceiveFiles app is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
namespace RecepcionFicherosSocket
{
class Program
{
static void Main(string[] args)
{
ReceiveFiles ns = new ReceiveFiles();
SocketsFileTransfer sft = new SocketsFileTransfer();
ReceiveFiles.ipAddress = sft.GetIpFromHostName(string.Empty);
ns.Start();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
namespace RecepcionFicherosSocket
{
public class ReceiveFiles
{
public static IPAddress ipAddress;
public void Start()
{
SocketsFileTransfer sockets = new SocketsFileTransfer();
int port = 9999;
string filename = sockets.ReceiveFilename(ipAddress, port);
port = 9998;
sockets.ReceiveFile(port, filename);
MyLogs.WriteLog("Start","Process finished",false);
this.Start();
}
}
}
The SendFiles app uses two sockets (the same numbers as ReceiveFiles) to send both the file name and the file data (in binary). First of all, it will send the file name to the remote app and wait for a ReceiveFile confirmation. Once the confirmation has arrived, the file is open in binary mode, and the data is read and sent through another socket to ReceiveFiles.
The core of the SendFiles app is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
namespace SendFiles
{
public class SocketsFileTransfer
{
private const string STRING_TO_CLOSE = "CLOSE APP";
public void SendFileName(string server, int port, string filename)
{
TcpClient socketForServer;
try
{
socketForServer = new TcpClient(server, port);
}
catch (Exception ex)
{
Console.WriteLine("No se pudo conectar a " + port +
":" + server + " " + ex.ToString());
return;
}
NetworkStream networkStream = socketForServer.GetStream();
StreamReader streamReader = new System.IO.StreamReader(networkStream);
StreamWriter streamWriter = new System.IO.StreamWriter(networkStream);
try
{
streamWriter.WriteLine(filename);
streamWriter.Flush();
string outputString = streamReader.ReadLine();
Console.WriteLine(outputString);
}
catch
{
Console.WriteLine("Exception reading from Server");
}
finally
{
networkStream.Close();
}
}
public void SendFile(IPAddress ipAddress, int port, string filenameToUse)
{
IPEndPoint ipEnd = new IPEndPoint(ipAddress, port);
Socket clientSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.IP);
filenameToUse = filenameToUse.Replace("\\", "/");
byte[] filenameData = Encoding.ASCII.GetBytes(filenameToUse);
byte[] output = new byte[4 + filenameData.Length];
BitConverter.GetBytes(filenameData.Length).CopyTo(output, 0);
filenameData.CopyTo(output, 4);
clientSocket.Connect(ipEnd);
clientSocket.SendFile(filenameToUse, output, null,
TransmitFileOptions.UseDefaultWorkerThread);
clientSocket.Close();
}
public IPAddress GetIpFromHostName(string servername)
{
IPAddress ip = null;
String strHostName = string.Empty;
if (servername == string.Empty)
{
strHostName = Dns.GetHostName();
MyLogs.WriteLog("GetIpFromHostName",
"Machine name: " + strHostName, false);
}
else
{
strHostName = servername;
}
IPHostEntry ipEntry = Dns.GetHostByName(strHostName);
IPAddress[] addr = ipEntry.AddressList;
ip = addr[0];
return ip;
}
}
}
It would be very annoying to start the remote ReceiveFiles application server by server, manually, before sending the files. So, I use the Microsoft PsExec application to start ReceiveFiles remotely. Basically, I use the Process
class to start PsExec and pass the necessary command parameters to start ReceiveFiles in the remote server.
The Send
method loops each file and calls SendFileName
and SendFile
in order to transfer the data.
The Close
method sends a particular message as a file name ("CLOSE APP") to request the ReceiveFiles app to close; PsExec is not necessary here, because a previous communication has been established between both the applications.
Let's now see how to start the ReceiveFiles app remotely, send the files, and then close the ReceiveFiles app:
Start the app:
private void btnStartRemoteListeners_Click(object sender, RoutedEventArgs e)
{
bool bcontinue = true;
bool wasanyerror = false;
string psexecPath = System.Windows.Forms.Application.StartupPath + "\\psexec.exe</a />";
string remoteExecutablePath = txtPathWhereReceiveFilesIs.Text;
string parameters = string.Empty;
bool showFinalMessage = true;
progressBar1.Maximum = lstServers.SelectedItems.Count;
progressBar1.Value = 0;
txtLog.Text = String.Empty;
if (ValidateStart()==true)
{
foreach (string serverName in lstServers.SelectedItems)
{
Server server = null;
if (ServersList.TryGetValue(serverName, out server) == true)
{
server.User = server.User == "" ? "Error" : server.User;
server.Password = server.Password == "" ? "Error" : server.Password;
try
{
if (bcontinue == true)
{
if (server.MyProcess == null)
{
Process p = new Process();
ProcessStartInfo psinfo = new ProcessStartInfo(psexecPath);
parameters = @"\\" + server.ServerName +
" -u " + server.User + " -p " +
server.Password + " -i 0 " + remoteExecutablePath;
psinfo.Arguments = parameters;
txtLog.AppendText(psexecPath + " " +
parameters + Environment.NewLine);
psinfo.CreateNoWindow = true;
psinfo.WindowStyle = ProcessWindowStyle.Minimized;
p = Process.Start(psinfo);
server.MyProcess = p;
System.Threading.Thread.Sleep(1000);
if (p.HasExited == true)
{
server.MyProcess = null;
throw new Exception();
}
}
else
{
System.Windows.Forms.MessageBox.Show("The process " +
"is already running at the server: " +
server.ServerName, "Start Listener",
MessageBoxButtons.OK, MessageBoxIcon.Information);
showFinalMessage = false;
}
}
}
catch (Exception ex)
{
string msg = "Error starting the remote listener at " +
server + Environment.NewLine + ex.Message +
Environment.NewLine + "Path: " +
psexecPath + Environment.NewLine +
"Parameters: " + parameters + Environment.NewLine +
"Maybe the remote path does not exists, " +
"the password is incorrect or the remote " +
"listener is still running";
txtLog.AppendText(msg + Environment.NewLine);
System.Windows.Forms.MessageBox.Show(msg, "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
wasanyerror = true;
if (System.Windows.Forms.MessageBox.Show(
"Do you want to continue?",
"Continue", MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes)
{
bcontinue = false;
}
}
progressBar1.Value++;
}
}
if (showFinalMessage == true)
{
if (wasanyerror == false)
{
System.Windows.Forms.MessageBox.Show(
"Remote listeners started ok", "Ok",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
System.Windows.Forms.MessageBox.Show(
"Remote listener started with errors",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
Copy files:
private void btnSendFiles_Click(object sender, RoutedEventArgs e)
{
SocketsFileTransfer socketsFileTransfer = new SocketsFileTransfer();
int filenamePort = 9999;
int filedataPort = 9998;
string serversWithError = string.Empty;
bool bcontinue = true;
bool wasanerror = false;
if (this.ValidateSending() == true)
{
this.Cursor = System.Windows.Input.Cursors.Wait;
progressBar1.Maximum = lstServers.SelectedItems.Count;
progressBar1.Value = 0;
txtLog.Text = String.Empty;
foreach (string serverName in lstServers.SelectedItems)
{
System.Net.IPAddress ipAddress =
socketsFileTransfer.GetIpFromHostName(serverName);
txtLog.AppendText(serverName + " ip: " +
ipAddress.ToString() + Environment.NewLine);
foreach (string filePath in filesToSend)
{
string fileName = System.IO.Path.GetFileName(filePath);
string finalPath = cboRemotePaths.Text + "\\" + fileName;
txtLog.AppendText("Uploading " + fileName +
" to " + serverName + "::" +
finalPath + Environment.NewLine);
try
{
if (bcontinue == true)
{
socketsFileTransfer.SendFileName(serverName, filenamePort, finalPath);
txtLog.AppendText("File name sent" + Environment.NewLine);
System.Threading.Thread.Sleep(250);
socketsFileTransfer.SendFile(ipAddress, filedataPort, filePath);
txtLog.AppendText("Data sent" + Environment.NewLine);
}
}
catch (Exception ex)
{
string msg = "Error sending: " + fileName +
" to " + serverName + Environment.NewLine + ex.Message;
txtLog.AppendText(msg);
System.Windows.Forms.MessageBox.Show(msg, "Error at " +
serverName, MessageBoxButtons.OK, MessageBoxIcon.Error);
wasanerror = true;
serversWithError += serverName + Environment.NewLine;
if (System.Windows.Forms.MessageBox.Show("Do you want " +
"to continue with the send process?", "Continue",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) ==
System.Windows.Forms.DialogResult.No)
{
bcontinue = false;
}
}
}
progressBar1.Value++;
System.Windows.Forms.Application.DoEvents();
}
if (wasanerror == false)
{
System.Windows.Forms.MessageBox.Show("Process finished ok",
"Ok", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
System.Windows.Forms.MessageBox.Show("Process finished " +
"with errors at: " + Environment.NewLine + serversWithError,
"Errors", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
this.Cursor = System.Windows.Input.Cursors.Arrow;
}
}
Close the app:
private void btnCloseRemoteListeners_Click(object sender, RoutedEventArgs e)
{
bool bcontinue = true;
bool wasanyerror = false;
progressBar1.Maximum = lstServers.SelectedItems.Count;
progressBar1.Value = 0;
txtLog.Text = String.Empty;
foreach (string serverName in lstServers.SelectedItems)
{
if (bcontinue == true)
{
if (this.CloseServer(serverName) == false)
{
wasanyerror = true;
if (System.Windows.Forms.MessageBox.Show(
"Do you want to close the remote listeners?",
"Continue", MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.No)
{
bcontinue = false;
}
}
}
progressBar1.Value++;
}
if (wasanyerror == false)
{
System.Windows.Forms.MessageBox.Show("Remote listeners closed",
"Ok", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
System.Windows.Forms.MessageBox.Show(
"Remote listeners closed with errors",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
The configuration file
In this file, you can specify the name, domain, username, and passwords of the servers with which you will work. This data is shown in the list on the left side of the SendFiles form:
="1.0"="utf-8"
<configuration>
<Server>
<ServerName>machine1.domain.xxx</ServerName>
<User>domain\user</User>
<Password>password</Password>
</Server>
<Server>
<ServerName>machine2.domain.xxx</ServerName>
<User>domain\user</User>
<Password>password</Password>
</Server>
</configuration>
Points of interest
Don't hesitate to comment anyway you want. I am here to learn.
History