Skip to content

Instantly share code, notes, and snippets.

@EBNull
Created March 15, 2011 16:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EBNull/870992 to your computer and use it in GitHub Desktop.
Save EBNull/870992 to your computer and use it in GitHub Desktop.
Install script for https://sourceforge.net/projects/wphfforwin7 . Installs files, port monitor, port, driver, and printer.
#Run this script to install system files, port monitor, port, driver, and printer
#You can change settings in the main() function
#
#(c) 2011 CBWhiz
import os
import sys
import ctypes
import subprocess
import codecs
import tempfile
import platform
import logging
from ctypes import *
from ctypes.wintypes import *
class StrictStructure(Structure):
"""Class to disallow setting invalid structure attributes. Helps prevent typos."""
def __setattr__(self, attr, val):
if attr not in (x[0] for x in self._fields_):
raise AttributeError
super(StrictStructure, self).__setattr__(attr, val)
Structure = StrictStructure
#---------------------------------------
#-----Begin Endless defines
#---------------------------------------
class MONITOR_INFO_2(Structure):
_fields_ = (
('name', LPWSTR),
('environ', LPWSTR),
('dll', LPWSTR),
)
ERROR_SUCCESS = 0
MAX_PORTNAME_LEN = 64
MAX_NETWORKNAME_LEN = 49
MAX_SNMP_COMMUNITY_STR_LEN = 33
MAX_QUEUENAME_LEN = 33
MAX_IPADDR_STR_LEN = 16
RESERVED_BYTE_ARRAY_SIZE = 540
class PORT_DATA_1(Structure):
_fields_ = (
('portName', WCHAR*MAX_PORTNAME_LEN),
('version', DWORD),
('protocol', DWORD),
('cbSize', DWORD),
('dwReserved', DWORD),
('hostAddress', WCHAR*MAX_NETWORKNAME_LEN),
('snmpCommunity', WCHAR*MAX_SNMP_COMMUNITY_STR_LEN),
('doubleSpool', DWORD),
('queue', WCHAR*MAX_QUEUENAME_LEN),
('ipAddress', WCHAR*MAX_IPADDR_STR_LEN),
('reserved', BYTE*RESERVED_BYTE_ARRAY_SIZE),
('portNumber', DWORD),
('snmpEnabled', DWORD),
('snmpDevIndex', DWORD),
)
PRINTER_ACCESS_SERVER_ADMIN = 0x1
PRINTER_ACCESS_ADMINISTER = 0x4
SERVER_ALL_ACCESS = 0xF0003
PRINTER_ALL_ACCESS = 0xF000C
class PRINTER_DEFAULTS(Structure):
_fields_ = (
('datatype', LPWSTR),
('devMode', c_void_p), #Actually points to a DEVMODE
('desiredAccess', DWORD),
)
class PRINTER_INFO_2(Structure):
_fields_ = (
('serverName', LPWSTR),
('printerName', LPWSTR),
('shareName', LPWSTR),
('portName', LPWSTR),
('driverName', LPWSTR),
('comment', LPWSTR),
('location', LPWSTR),
('devMode', c_void_p), #Actually points to a DEVMODE
('sepFile', LPWSTR),
('printProcessor', LPWSTR),
('datatype', LPWSTR),
('parameters', LPWSTR),
('securityDescriptor', c_void_p),
('attributes', DWORD),
('priority', DWORD),
('defaultPriority', DWORD),
('startTime', DWORD),
('untilTime', DWORD),
('status', DWORD),
('cJobs', DWORD),
('averagePPM', DWORD),
)
PRINTER_ATTRIBUTE_LOCAL = 0x40
PRINTER_ATTRIBUTE_NETWORK = 0x10
PRINTER_ATTRIBUTE_FAX = 0x4000
#---------------------------------------
#-----End defines
#---------------------------------------
def nullcatch(res, func, args):
"""Simple error catcher, if res=0 raise WinError()"""
if not res:
raise WinError()
return res
#The code below does no error checking of these APIs and relies on ctypes to do it for us.
#ctypes will call our .errcheck handler after each call, which can then raise an exception.
ctypes.windll['winspool.drv'].AddMonitorW.errcheck = nullcatch
ctypes.windll['winspool.drv'].DeleteMonitorW.errcheck = nullcatch
ctypes.windll['winspool.drv'].AddPrinterW.errcheck = nullcatch
ctypes.windll['winspool.drv'].DeletePrinter.errcheck = nullcatch
ctypes.windll['winspool.drv'].OpenPrinterW.errcheck = nullcatch
ctypes.windll['winspool.drv'].ClosePrinter.errcheck = nullcatch
ctypes.windll['winspool.drv'].XcvDataW.errcheck = nullcatch
class PrinterPorts(object):
"""Manages Printer Ports (LPT1:. etc)"""
@classmethod
def add(cls, portName, portType):
"""Creates a new printer port. portName is generally in the format 'XXXN:'"""
return cls._add_rm_port(portName, portType, remove=False)
@classmethod
def delete(cls, portName, portType):
"""Removes an existing printer port. portName is generally in the format 'XXXN:'"""
return cls._add_rm_port(portName, portType, remove=True)
@classmethod
def _add_rm_port(cls, portName, portType, remove):
printersettings = PRINTER_DEFAULTS()
printersettings.datatype = None
printersettings.devMode = None
printersettings.desiredAccess = PRINTER_ACCESS_SERVER_ADMIN
printer = HANDLE()
ctypes.windll['winspool.drv'].OpenPrinterW(u",XcvMonitor %s"%(portType), byref(printer), byref(printersettings))
pd = PORT_DATA_1()
pd.version = 1
pd.protocol = 1 #1 = RAW, 515 = LPR
pd.portNumber = 9100 #9100 default for RAW
pd.portName = portName
pd.cbSize = sizeof(pd)
status = DWORD()
outputreq = DWORD()
output = DWORD()
if not remove:
ctypes.windll['winspool.drv'].XcvDataW(printer, u"AddPort", byref(pd), pd.cbSize, byref(output), sizeof(output), byref(outputreq), byref(status))
else:
ctypes.windll['winspool.drv'].XcvDataW(printer, u"DeletePort", byref(pd), pd.cbSize, byref(output), sizeof(output), byref(outputreq), byref(status))
if status.value:
raise WinError(status.value)
ctypes.windll['winspool.drv'].ClosePrinter(printer)
class Printers(object):
"""Manages printers"""
@classmethod
def add(cls, name, port, driver, is_fax=False):
pi = PRINTER_INFO_2()
pi.serverName = None
pi.printerName = name
pi.shareName = None
pi.portName = port
pi.driverName = driver
pi.printProcessor = "WinPrint"
pi.securityDescriptor=None
pi.attributes=PRINTER_ATTRIBUTE_LOCAL | (PRINTER_ATTRIBUTE_FAX if is_fax else 0)
printer = ctypes.windll['winspool.drv'].AddPrinterW(None, 2, byref(pi))
if printer:
printer = ctypes.windll['winspool.drv'].ClosePrinter(printer)
@classmethod
def delete(cls, name):
printersettings = PRINTER_DEFAULTS()
printersettings.datatype = None
printersettings.devMode = None
printersettings.desiredAccess = PRINTER_ALL_ACCESS
printer = HANDLE()
ctypes.windll['winspool.drv'].OpenPrinterW(unicode(name), byref(printer), byref(printersettings))
ctypes.windll['winspool.drv'].DeletePrinter(printer)
if printer:
printer = ctypes.windll['winspool.drv'].ClosePrinter(printer)
class PrinterDrivers(object):
"""Manages Printer Drivers"""
@classmethod
def add_system_driver(cls, driver):
p = subprocess.Popen(r'rundll32 printui.dll,PrintUIEntry /ia /m "%s" /f "%s\inf\ntprint.inf" /u'%(driver, os.environ['windir']))
p.wait()
class PortMonitors(object):
"""Manages Port Monitors (Port Types)"""
@classmethod
def add(cls, name, environ, dll):
moninfo = MONITOR_INFO_2()
moninfo.name = name
moninfo.environ = environ
moninfo.dll = dll
ctypes.windll['winspool.drv'].AddMonitorW(None, 2, byref(moninfo))
@classmethod
def delete(cls, name, environ):
ctypes.windll['winspool.drv'].DeleteMonitorW(None, unicode(environ), unicode(name))
class NoWow64Redirect(object):
def __enter__(self):
self._pold = c_void_p()
try:
ctypes.windll.kernel32.Wow64DisableWow64FsRedirection(byref(self._pold))
except:
pass
def __exit__(self, e0, e1, e2):
try:
ctypes.windll.kernel32.Wow64RevertWow64FsRedirection(byref(self._pold))
except:
pass
def setup_hylafax_port(port, monitor, server, username="fax", password="", email="", addresspath="", pagesize="US Letter"):
data = u"""Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\%s\Ports\%s]
"Description"="%s Port"
"Server"="%s"
"Username"="%s"
"Password"="%s"
"DefaultEmail"="%s"
"Modem"=""
"AddressBookPath"="%s"
"NotificationType"="Failure and Success"
"PageSize"="%s"
"IgnorePassiveIP"="0"
"AddressBookType"=""
"Resolution"="Fine"
"""
data = data%(monitor, port, monitor, server, username, password, email, addresspath.replace('\\', '\\\\'), pagesize)
outf = tempfile.NamedTemporaryFile(delete=False, suffix='.hylafax.reg')
outf.write(codecs.BOM_UTF16_LE + data.encode('utf-16le'))
outf.close()
p = subprocess.Popen(['regedit', '/s', outf.name])
p.wait()
os.unlink(outf.name)
CSIDL_SYSTEM = 0x25
CSIDL_SYSTEMX86 = 0x29
def is_system_64bit():
sys = create_unicode_buffer(260)
sys32 = create_unicode_buffer(260)
with NoWow64Redirect():
ctypes.windll.shell32.SHGetSpecialFolderPathW(None, byref(sys), CSIDL_SYSTEM, 0)
ctypes.windll.shell32.SHGetSpecialFolderPathW(None, byref(sys32), CSIDL_SYSTEMX86, 0)
sys = sys.value
sys32 = sys32.value
if all((sys, sys32)):
if sys == sys32:
return False
if sys != sys32:
return True
else:
return False
def copy_hylafax_files(source=''):
sys = create_unicode_buffer(260)
with NoWow64Redirect():
ctypes.windll.shell32.SHGetSpecialFolderPathW(None, byref(sys), CSIDL_SYSTEM, 0)
sys = sys.value
shutil.copy(os.path.join(source, "HylaPrintMon.dll"), sys)
shutil.copy(os.path.join(source, "HylaPrintUI.exe"), sys)
def delete_hylafax_files():
sys = create_unicode_buffer(260)
with NoWow64Redirect():
ctypes.windll.shell32.SHGetSpecialFolderPathW(None, byref(sys), CSIDL_SYSTEM, 0)
sys = sys.value
os.unlink(os.path.join(sys, "HylaPrintMon.dll"))
os.unlink(os.path.join(sys, "HylaPrintUI.exe"))
def main(argv):
log = logging.getLogger('main')
printer_name = "Fax"
port_name = u"HFAX1:"
mon_name = "Winprint Hylafax"
mon_dll = "HylaPrintMon.dll"
hylafax_server_opts = {
'server': '192.168.1.2',
'username': 'fax',
'password': '',
'email': '',
'addresspath': r''
}
import platform
if int(platform.win32_ver()[1].split('.')[0]) < 6:
#pre-vista
driver_name = u"Apple LaserWriter 12/640 PS"
else:
driver_name = u"Xerox Phaser 6250B PS"
sixfour = is_system_64bit()
if "--32" in argv:
sixfour = False
elif "--64" in argv:
sixfour = True
mon_environ = "Windows NT x86"
if sixfour:
mon_environ = "Windows x64"
if "--remove" not in argv:
log.info("Setting up %s on port %s using %s with driver %s", printer_name, port_name, mon_name, driver_name)
log.info("Copying hylafax files")
try:
if sixfour:
copy_hylafax_files(os.path.join(__dir__, 'Only-For-Windows-64bit'))
else:
copy_hylafax_files(os.path.join(__dir__, 'Only-For-Windows-32bit'))
except Exception as e:
log.error("Could not copy hylafax files: %s", e)
log.info("Creating port monitor %s", mon_name)
try:
PortMonitors.add(mon_name, mon_environ, mon_dll)
except WindowsError as e:
if e.winerror == 3006:
log.error("Could not create monitor: %s", e)
else:
raise
log.info("Creating port %s of type %s", port_name, mon_name)
try:
PrinterPorts.add(port_name, mon_name)
setup_hylafax_port(port_name, mon_name, **hylafax_server_opts)
except WindowsError as e:
if e.winerror == 183:
log.error("Could not create port: %s", e)
else:
raise
log.info("Adding system driver %s", driver_name)
PrinterDrivers.add_system_driver(driver_name)
log.info("Creating printer %s", printer_name)
try:
Printers.add(printer_name, port_name, driver_name, is_fax=True)
except WindowsError as e:
if e.winerror == 1802:
log.error("Could not create printer: %s", e)
else:
raise
log.info("Done.")
else:
log.info("Removing printer %s", printer_name)
try:
Printers.delete(printer_name)
except WindowsError as e:
if e.winerror == 1801:
log.error("Could not remove printer: %s", e)
else:
raise
log.warn("Not removing driver %s", driver_name)
log.info("Removing port %s", port_name)
try:
PrinterPorts.delete(port_name, mon_name)
except Exception as e:
log.error("Could not remove port: %s", e)
log.info("Removing port monitor %s", mon_name)
try:
PortMonitors.delete(mon_name, mon_environ)
except WindowsError as e:
if e.winerror == 3000:
log.error("Could not remove port monitor: %s", e)
else:
raise
log.info("Removing hylafax files")
try:
delete_hylafax_files()
except Exception as e:
log.error("Could not remove hylafax files: %s", e)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
main(sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment