Automating PuTTY
Windows users who need a command line connection to another system via telnet or SSH are big fans of PuTTY. It’s free, it has every feature you need, and it’s reliable.
One thing many people would like to do is use PuTTY as a component in their program. Apparently this comes up so often enough that there is a FAQ entry dedicated to the topic. Alas, PuTTY does not have any sort of automation interface, so this goal has always been out of reach.
In this article I will show you how to work around this minor shortcoming. Creating a version of PuTTY that can be driven from a Windows program turns out to be an easy task. I’ll demonstrate this with a small C++ program that shows exactly how to get this versatile program to do your bidding. My solution works for C++, but the changes I make should work well with any Windows software that can properly process a few messages.
Putting Together the Project
I’m using Visual Studio 2010 to build both my program and the modified version of Putty. I created the basic outline as follows:
-
Use the File|New|Project menu item to bring up the list of available project wizards.
Select MFC project, and enter a project name (I used the uninspired name PuttyDriver.)
I don't want the default MFC settings, so in the MFC App Wizard, select the Next button.
On the Application Type page of the wizard, change the Application Type to Dialog Based.
The project is ready to go at this point, you can click the Finish button and then build your initial project.
My driver program is only going to do one thing: direct PuTTY to connect to the host of my choice, then log in using canned credentials. The resulting UI is shown below, and I am going to leave the very minor details of creating it up to the reader.

Adding Putty to the Project
The next step in this process is to add the Putty components to the project. I downloaded version
0.61 of the PuTTY source from the
download page
and extracted it to a separate folder. I then used Visual Studio’s
File|Add|Existing Project to add the compatible project file, Putty.dsp
,
found in /Windows/MSVC/Putty
. Visual Studio has to convert this project to a version
10 project file, but it should do so with no problems.
I then right-clicked on the Putty project in Solution Explorer and renamed it to AutoPutty. Since this version of PuTTY will have some slightly different behavior, I don’t want to confuse the executable I am creating with the real thing.
From Project|Project Dependencies, I set the PuttyDriver project to depend on AutoPutty - this insures that both projects get built when I build the entire solution.
My final change to the project is to modify the output directory for both Debug and Release versions of AutoPutty. I set the project to build the executable in the root directory of my PuttyDriver project - this will make it easy to find the executable when I need to launch it. I had to make this change in two places: Properties|Configuration Properties|General|Output Directory and Properties|Linker|Outuput File.
When you finally build the project, you’ll find that current version of Microsoft’s C++ compiler
complain quite a bit about the use of functions like strcpy
- Microsoft would like
you to use safer replacement functions. You may choose to turn those errors off by defining
_CRT_SECURE_NO_WARNINGS
in the project file. While you are there, you should define
SECURITY_WIN32
as well - it is required by Windows header sspi.h
.
After a successful build you should find a copy of AutoPutty.exe
in the root
directory of your project, and it should run on your system and behave just like PuTTY.
Launching AutoPutty
If I’m going to have a PuTTY component in my PuttyDriver program, one of the first things I need is to be able to start and stop AutoPutty. So my first step in this project is to create the code that launches the program from PuttyDriver. The code below is inserted into the handler for the Start button:
UpdateData( true );
char path[MAX_PATH];
GetCurrentDirectory(MAX_PATH, path);
if ( path[ strlen(path) - 1 ] != '\\' )
strcat_s( path, MAX_PATH, "\\" );
strcat_s( path, MAX_PATH, "AutoPutty.exe -ssh " );
strcat_s( path, MAX_PATH, m_HostName.GetBuffer() );
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi) );
STARTUPINFO si;
ZeroMemory(&si, sizeof(si) );
si.cb = sizeof(si);
if ( CreateProcess( NULL, path, NULL, NULL, NULL, NULL, NULL, NULL, &si, &pi ) )
{
Sleep( 1000 );
BringWindowToTop();
}
This code assumes that AutoPutty.exe
is in the current directory, and launches it
with a command line telling it to connect to the host named in the dialog using
ssh.
Assuming that you have the project set up properly, pushing the start button should now start an
independent copy of AutoPutty, which will behave identically to classic PuTTY.
Taking Ownership of AutoPutty
At this point I can successfully launch AutoPutty, but I can’t really start calling this an integrated part of my main program, PuttyDriver. All I have done is set up a launcher for a separate executable.
The next step in the integration process is to establish PuttyDriver as the owner of AutoPutty’s main window. Most Windows programmers are familiar with the traditional parent/child relationship between windows. That relationship is well understood, but I can’t use it here - it doesn’t work for two top level windows.
Setting PuttyDriver to be the owner (as opposed to the parent) of AutoPutty has the following effects, as explained here by Microsoft:
-
The owned window will always be above its owner in the z-order.
The system automatically destroys the owned window when the owner is destroyed.
The owned window is hidden when the owner is minimized.
The most straightforward way to set ownership of the window is to pass the owner’s handle in the
call to CreateWindow()
, which means I will now make my first modifications to the
PuTTY source code.
There are a number of ways to pass the owner handle to AutoPutty for use in the call to
CreateWindow()
, with the most obvious being to pass it on the command line.
In the interest of minimizing changes to the existing PuTTY code base, I elected to pass it by
creating an environment variable that holds the owner window handle. Since a child
process inherits the parent’s environment, this is a no-fuss way to get the data to AutoPutty.
I added the following code to the end of InitDialog()
in PuttyDriver:
CString hwnd_text;
hwnd_text.Format( "%d", m_hWnd );
SetEnvironmentVariable("PUTTY_OWNER", hwnd_text );
This sets the environment variable for AutoPutty to find when it gets launched.
Now I come to the point where I am actually making changes to the PuTTY code. Fortunately, all of
the changes needed for this program are confined to two files: terminal.c
and
windows/window.c
. My first change is to window.c
. This file contains the
WndProc for the PuTTY window, and thus most of the rendering and control code for the GUI.
In order to establish the Owner/Owned relationship, I need to modify the code that calls
CreateWindow()
. I hoisted the function call into a block, added code to get the owner
window handle, and inserted the handle into the call to CreateWindow()
:
{
HWND owner_hwnd = 0;
char buffer[ 132 ];
if ( GetEnvironmentVariable( "PUTTY_OWNER", buffer, 132 ) )
sscanf( buffer, "%d", &owner_hwnd );
if ( owner_hwnd == 0 )
MessageBox( NULL,
"AutoPutty did not find the handle for the "
"owner window, this is not going to work",
"Fail",
MB_OK );
hwnd = CreateWindowEx(exwinmode, appname, appname,
winmode, CW_USEDEFAULT, CW_USEDEFAULT,
guess_width, guess_height,
owner_hwnd, NULL, inst, NULL);
}
At this point I’ve only modified one small block of code in the PuTTY source, but I’m well on my way to having it behave more like a component of PuttyDriver and less like an independent program. The ownership status means that the two programs only appear once on the taskbar, and will only appear once when you are pressing ALT-TAB to select a new active process. And they only produce a single entry in the Applications Tab of Task Manager.
The Communications Link
In order to achieve the automation that I am seeking, I also need to have two way communications between AutoPutty and the driver program. Since this is Windows, a natural choice for communications is to use native Windows messages. In order to do this, both programs need the Window handle of their opposite number.
I’ve already solved half of that problem through the ownership relationship established when I created the main window for AutoPutty. Now that it has set PuttyDriver as its owner window, I can get this window handle any place in the program through a simple function call:
HWND parent = GetWindow(hwnd, GW_OWNER);
But the reverse is not true - PuttyDriver does not know have a copy of the window handle for AutoPutty.
To remedy this situation, I added code to window.c
that notifies its owner when
it is created, and when it is destroyed. First I add this statement immediately after the call to
CreateWindow()
:
if ( owner_hwnd )
PostMessage( owner_hwnd, WM_APP, 0, (LPARAM) hwnd );
This tells PuttyDriver that the window is created, and gives it the handle to use for communications.
I also need to know when the window is closed, and I have to add that code two places in
window.c
- because Putty can be shut down two different ways.
Normally AutoPutty will shut down in response to a windows message. When this happens, I can count
on a WM_CLOSE
message being sent to the Windows Procedure. I add this code the
existing handler for WM_CLOSE
:
if (!cfg.warn_on_close || session_closed ||
MessageBox(hwnd,
"Are you sure you want to close this session?",
str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1)
== IDOK) {
HWND parent = GetWindow(hwnd, GW_OWNER);
if ( parent )
SendMessage( parent, WM_APP, 0, 0 );
DestroyWindow(hwnd);
}
This lets PuttyDriver know that the window has been destroyed.
The original PuTTY code has an alternative method of shutdown. When it receives one of several
possible network events, such as a telnet connection being broken, it calls
PostQuitMessage()
. When a program shuts down this way, it doesn’t issue messages to
destroys its windows - it relies on the O/S to destroy the windows when the process exists. As a
result, I have to make a change in WinMain()
, the main window procedure for PuTTY.
This procedure extracts the messages sent to it using PeekMessage
, and I add some
code to handle the processing when a WM_QUIT
message is sent:
if (msg.message == WM_QUIT) {
HWND parent = GetWindow(hwnd, GW_OWNER);
if ( parent )
SendMessage( parent, WM_APP, 0, 0 );
goto finished; /* two-level break */
}
Handling the AutoPutty Lifecycle Events
To keep track of the state of AutoPutty, I have to add a handler for WM_APP
to
PuttyDriver. It does two things when handling the incoming WM_APP
event.
First, then handler stores the handle of the AutoPutty window - or sets the value to 0 when the window has been destroyed.
Second, it either enables or disables the button used to start up AutoPutty. Since this program can only manage one window at a time, I don’t want to allow any inadvertent button pushes:
afx_msg LRESULT CPuttyDriverDlg::OnWmApp(WPARAM wParam, LPARAM lParam)
{
m_PuttyWindow = (HWND) lParam;
m_StartButton.EnableWindow( !m_PuttyWindow );
return 0;
}
One final piece of bookkeeping is to make sure that the AutoPutty window is shut down when PuttyDriver shuts down. (The Windows documentation claims this happens automatically to owned windows, but it doesn’t seem to be the case.)
void CPuttyDriverDlg::OnDestroy()
{
CDialogEx::OnDestroy();
if ( m_PuttyWindow )
::SendMessage( m_PuttyWindow, WM_CLOSE, 0, 0 );
}
Monitoring Input Traffic
Now that I have control over the lifetime of my AutoPutty window, it’s time to take the next step in automation. My driver program needs to watch all the data coming in from the remote end so that it can take action on various types of input.
Depending on how you set up your connection, PuTTY can receive input data from a serial port, a
Telnet connection, or an SSH connection. Fortunately the Windows version of PuTTY uses a standard
handle-based interface to all three types of connections. The routine term_data()
in terminal.c
is called as data arrives, regardless of the source.
Since we are using the Windows API to communicate between processes, it makes sense to use the
WM_COPYDATA
message to send data to the parent program as it arrives.
WM_COPYDATA
is a good choice, as it takes care of marshalling the data between the
two processes, which can add some complication to other solutions. The modified routine is shown
below:
int term_data(Terminal *term, int is_stderr, const char *data, int len)
{
HWND parent = GetWindow(hwnd, GW_OWNER);
if ( parent ) {
COPYDATASTRUCT cd;
cd.dwData = (ULONG_PTR) 0xDEADBEEF;
cd.cbData = len;
cd.lpData = (PVOID) data;
SendMessage( parent, WM_COPYDATA, (WPARAM) hwnd, (LPARAM) &cd );
}
Receiving the Data
To receive this messages in PuttyDriver, I simply create a handler for WM_COPYDATA
and start grabbing the data as it arrives. One important thing to note is that because AutoPutty
has to use SendMessage()
to send the data to its parent, it has to wait for
PuttyDriver to finish processing the data until it can continue. This dictates a certain style of
behavior on my part.
There are quite a few ways to skin this cat, and I’m keeping it very simple here. I’m using a
deque<char>
container to hold the last 64 characters I’ve received. After
each WM_COPYDATA
message I received, I check to see if the current output snapshot
ends in one of my trigger messages. If it does, I post the message number to myself for later
processing, then return so that AutoPutty can continue its work.
The code I’m using here is doing something fairly simple: automating the login process by using the credentials that I’ve entered into the dialog box. That means the two strings I’m looking for are the login and password prompts. The resulting code is shown here:
BOOL CPuttyDriverDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
char *p = (char *) pCopyDataStruct->lpData;
int len = pCopyDataStruct->cbData;
if ( len >= 64 ) {
p += len - 64;
len = 64;
m_Snapshot.clear();
}
while ( len-- )
m_Snapshot.push_front(*p++);
m_Snapshot.resize(64);
static const char *needles[2] = { "login as: ", "password: " };
for ( int i = 0 ; i < 2 ; i++ ) {
int len = strlen( needles[i] );
int j;
for ( j = 0 ; j < len ; j++ ) {
if ( needles[i][j] != m_Snapshot[len-1-j] )
break;
}
if ( j == len )
PostMessage( WM_APP+1, i, 0 );
}
return TRUE;
}
There is plenty of room for improvement in this routine, much of it depending on what type of automation you are going to be using in your program. Some obvious items would include the ability to add and remove triggers as the program progresses, and regular expression matching for triggers.
Driving PuTTY
This login program is now complete save for one detail: I need a way to send my responses back to AutoPutty.
The first part of this is pretty obvious - I just need to read the data from the dialog box and
post it to AutoPutty with my useful WM_COPYDATA
command. This happens in my
WM_APP+1
handler:
afx_msg LRESULT CPuttyDriverDlg::OnWmAppPlusOne(WPARAM wParam, LPARAM lParam)
{
UpdateData(TRUE);
CString msg;
switch ( wParam ) {
case 0 :
msg = this->m_UserId + '\r'; break;
case 1:
msg = this->m_Password + '\r'; break;
}
if ( this->m_PuttyWindow ) {
COPYDATASTRUCT cd;
cd.dwData = (ULONG_PTR) 0xF00DFACE;
cd.cbData = msg.GetLength();
cd.lpData = (PVOID) (const char *) msg;
::SendMessage( this->m_PuttyWindow,
WM_COPYDATA,
(WPARAM) this->m_hWnd,
(LPARAM) &cd );
}
return 0;
}
Sending this data to AutoPutty is fine, but right now the program doesn’t do anything with that message.
The final piece of work is to add a WM_COPYDATA
handler to window.c.
Simply grabbing the data is easy enough - the data structure that accompanies the message contains a pointer to the data and a value indicating its length. However, I have two problems I have to solve before the data is actually sent out to the to whatever device AutoPutty is connected to.
First, I have to take into account the fact that PuTTY was written to use wide characters. My
driver program was built using MultiByte characters, so we have a mismatch. This means I have to
do a conversion of the data from one domain to the other. This is a two step process - I call
MultiByteToWideChar()
once to determine how much space I need, then I allocate a
buffer and call it again.
The second thing I need to do is determine what to do with the data once I’ve converted it. PuTTY
takes all terminal input and eventually passes through a function called luni_send()
.
Calling this function directly from the Windows procedure seems to work just fine.
The WM_COPYDATA
handler I created looks like this:
case WM_COPYDATA :
{
COPYDATASTRUCT *cd = (COPYDATASTRUCT *) lParam;
int wsize = MultiByteToWideChar( CP_ACP,
MB_PRECOMPOSED,
(LPCSTR) cd->lpData,
cd->dwData,
NULL,
0 );
wchar_t *buf = (wchar_t *) calloc( wsize+1, sizeof(wchar_t) );
MultiByteToWideChar( CP_ACP,
MB_PRECOMPOSED,
(LPCSTR) cd->lpData,
cd->dwData,
buf,
wsize + 1 );
if (term->ldisc)
luni_send(term->ldisc, buf, wsize, 0);
free( buf );
}
At this point I have a working program - it connects to my designated host, and sends the username and password of my choice to the host, connecting me to the system.
I should add a note of caution here. Automating logins is a tempting time saver, but in general this is a really bad idea. Any time you hard code credentials into a program, you open the door to all sorts of new attacks on your system.
In my demo program, the user has to enter a name and password, so nothing is hardcoded, but even this adds security holes to a system. I encourage you to think of this as a demonstration only.
For a better view, go to full screen and select 720p
Source Code
I’ve included the complete source code for PuttyDriver, the MFC project that controls AutoPutty. It was built with Visual Studio 2010, so you may have a little work to do if you backport it to earlier versions. My use of language features and classes should be compatible with much earlier versions - this is all very simple code.
Because PuTTY is always changing, I am not redistributing a snapshot of the version I used.
Instead, I’m including before and after copies of the two source files I modified:
window.c
and terminal.c
. If you build with Putty 0.61, you should be
able to drop these two files right on top of the files included with the distribution and be on
your way. With later versions of PuTTY you will have to perform an intelligent merge of the
changes, which I hope will be a fairly effortless process.
Downloads
- PuttyDriver.zip. The PuttyDriver source and project. You will need to add the PuTTY project to this solution as described in the article.
- putty.zip. This contains the two PuTTY source files modified for this project. Both the original 0.61 source and my modified source are supplied. Executables are supplied as well, which may or may not work on your system.