Home Too Many Computers
« Home »
Mar 13th, 2013 Comments: 0

Too Many Computers

Tags

Between work and home, including real hardware and VM instances, I find myself hopping backwards and forwards between 5 or 6 different machines at any given time. I also happen to use the PuTTY SSH client quite a lot. I’ve got a bunch of PuTTY shortcuts configured across my little ‘herd’ of computers to give me quick access to the systems that I access most frequently.The problem is, the shortcuts aren’t exactly the same on all of my machines.

I use Windows-based computers with both 32 and 64 bit versions of the OS installed, which means that the PuTTY binary will be in a different location between, for example, a 32-bit Windows XP computer, a 32-bit Windows7 computer and a 64-bit Windows7 computer. This makes synchronisation of my folder of SSH shortcuts across all machines slightly problematical – if I change a shortcut on the 32-bit XP machine, I can’t just copy the changed .lnk file across to one of my 64-bit Windows7 boxes and expect it to work. With this in mind, I wrote a short Python script to deal with this for me. It will recursively scan a folder containing .lnk files and update each one so that it points off to the right place to launch a PuTTY session.

Note that this script depends upon you having Mark Hammonds ‘Python for Windows’ tool kit installed. If you haven’t got this, you can download it from SourceForge. For my script, either copy and paste the code below or scroll down for a direct download link.

This version (v 1.2) incorporates a bug fix which ensures that it only updates shortcuts which refer to ‘putty.exe’, otherwise it can end up overwriting your entire Start menu if you give it the right path.

#!/usr/bin/python
# Check and adjust PuTTY SSH shortcuts
# vim: ai et sw=4 ts=4
#
# Copyright (C)2012 Phil Edwards <phil 'at' linux2000.com>
#
# License: This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at your
# option) any later version. This program is distributed in the hope that it
# will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# CHANGELOG:
#
# Ver    Date       Who  Changes
# -----  ---------  ---  ---------------------------------------------------
# 1.0    21SEP2012  PKE  First version
# 1.1    06MAR2013  PKE  Enhanced command line option processing
# 1.2    16MAY2013  PKE  Bug fix - only update shortcuts that contain
#                        putty.exe in the target path

import os
import sys
import time
from optparse import OptionParser
import pythoncom
from win32com.shell import shell

class Win32Shortcut:
    def __init__(self, lnkname):
        self.shortcut = pythoncom.CoCreateInstance(
            shell.CLSID_ShellLink, None,
            pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
        self.shortcut.QueryInterface(pythoncom.IID_IPersistFile).Load(lnkname)

    def __getattr__(self, name):
        return getattr(self.shortcut, name)

class RunTimeOptions:
    def __init__(self):
        self.__me__ = os.path.basename(sys.argv[0])
        self.__version__ = '1.2'
        self.__copyright__ = "Copyright (C)2013 Phil Edwards <phil 'at' linux2000.com> All Rights Reserved"
        self.__summary__ = 'Automatic PuTTY shortcut fixer'
       
        self.puttyPath = None
        self.found = 0
        self.updated = 0
        self.defaultPath = os.environ['appdata'] + "\Microsoft\Internet Explorer\Quick Launch"
       
        if os.path.exists("C:\Program Files (x86)\PuTTY\putty.exe"):
            self.puttyPath = 'C:\Program Files (x86)\PuTTY\putty.exe'

        if os.path.exists("C:\Program Files\PuTTY\putty.exe"):
            self.puttyPath = 'C:\Program Files\PuTTY\putty.exe'
   
        self.getCommandLineOptions()

        self.logMsg(txt = '%s v%s %s' % (self.__me__, self.__version__, self.__summary__))
        self.logMsg(txt = 'command line arguments: [%s]' % ' '.join(sys.argv[1:]))
        self.logMsg(txt = 'Executable found at [%s]' % self.puttyPath)

    def logMsg(self, **kwargs):
        txt = kwargs.get('txt', '')
        dateStamp = kwargs.get('dateStamp', True)
        msgLine = ''

        if txt != '':
            if dateStamp: msgLine += '[' + time.strftime("%Y%m%d-%H%M%S", time.localtime()) + '] '
            if not self.doUpdate: msgLine += 'NOUPDATE: '
            msgLine += txt
            print msgLine

    def debugMsg(self, **kwargs):
        txt = kwargs.get('txt', '')
        dateStamp = kwargs.get('dateStamp', True)
        pause = kwargs.get('pause', False)
        msgLine = ''

        if self.debugMode and txt != '':
            if dateStamp: msgLine += '[' + time.strftime("%Y%m%d-%H%M%S", time.localtime()) + '] '
            msgLine += 'DEBUG: ' + txt
            if pause:
                bull = raw_input(msgLine + ': Press any key to continue : ')
            else:
                print msgLine

    def getCommandLineOptions(self):
        vString = '%%prog (%s) v%s' % (self.__summary__, self.__version__)

        dString = 'Script to examine PuTTY SSH shortcuts found in the folder '
        dString += 'referenced by BASEPATH and update them so that '
        dString += 'they contain the correct path to the PuTTY executable'

        eString = ''
       
        uString = 'usage: %prog [options] BASEPATH'

        parser = OptionParser(version = vString, description = dString,
            epilog = eString, usage = uString)

        parser.add_option('-d', '--dry-run',
            action = 'store_false', dest = 'doupdate', default = True,
            help = 'process as normal, but do not update any files')

        parser.add_option('-x', '--debug',
            action = 'store_true', dest = 'debugmode', default = False,
            help = 'emit debug messages for troubleshooting')

        parser.add_option('-v', '--verbose',
            action = 'store_true', dest = 'verbose', default = False,
            help = 'be more verbose when processing')

        (options, args) = parser.parse_args()
       
        if len(args) == 0:
            self.basePath = self.defaultPath
        elif len(args) == 1:
            self.basePath = args[0]
        else:
            print 'ERROR: ambiguous command line parameters'
            sys.exit(0)

        if self.puttyPath is None:
            print 'ERROR: Unable to locate PuTTY executable'
            sys.exit(0)
           
        if not os.path.exists(self.basePath):
            print 'ERROR: base path does not exist'
            sys.exit(0)

        self.doUpdate = options.doupdate
        self.debugMode = options.debugmode
        self.verbose = options.verbose

        self.debugMsg(txt = 'options = %s' % options)
        self.debugMsg(txt = 'args = %s' % args)

def processItem(my, path):
    my.found += 1
   
    s = Win32Shortcut(path)
    iconPath = s.GetIconLocation()[0]
    itemPath = s.GetPath(0)[0]
    my.debugMsg(txt = 'itemPath is %s' % itemPath, pause = True)

    if itemPath != my.puttyPath and 'putty.exe' in itemPath.lower():
        s.SetPath(my.puttyPath)
        pf = s.QueryInterface (pythoncom.IID_IPersistFile)
        if my.doUpdate: pf.Save(path, 0)
        my.updated += 1
        my.logMsg(txt = '%s updated' % path)
    else:
        if my.verbose:
            my.logMsg(txt = '%s OK' % path)

def processFolder(my, path):
    if not os.path.isdir(path): return
   
    if my.verbose:
        my.logMsg(txt = 'Processing folder [%s]' % path)
       
    flist = os.listdir(path)
    for item in flist:
        newPath = os.path.join(path, item)
        if os.path.isdir(newPath):
            processFolder(my, newPath)
        elif item.endswith('.lnk'):
            processItem(my, os.path.join(path, item))

def main():
    my = RunTimeOptions()

    my.logMsg(txt = 'Processing started')
    processFolder(my, my.basePath)
    my.logMsg(txt = '%s shortcuts found' % my.found)
    my.logMsg(txt = '%s shortcuts updated' % my.updated)
    my.logMsg(txt = 'Processing complete')

if __name__ == '__main__':
    main()
Download1395 downloads

Leave a Reply