Spaces:
Sleeping
Sleeping
"""Installation utilities for Python ISAPI filters and extensions.""" | |
# this code adapted from "Tomcat JK2 ISAPI redirector", part of Apache | |
# Created July 2004, Mark Hammond. | |
import imp | |
import os | |
import shutil | |
import stat | |
import sys | |
import traceback | |
import pythoncom | |
import win32api | |
import winerror | |
from win32com.client import Dispatch, GetObject | |
from win32com.client.gencache import EnsureDispatch, EnsureModule | |
_APP_INPROC = 0 | |
_APP_OUTPROC = 1 | |
_APP_POOLED = 2 | |
_IIS_OBJECT = "IIS://LocalHost/W3SVC" | |
_IIS_SERVER = "IIsWebServer" | |
_IIS_WEBDIR = "IIsWebDirectory" | |
_IIS_WEBVIRTUALDIR = "IIsWebVirtualDir" | |
_IIS_FILTERS = "IIsFilters" | |
_IIS_FILTER = "IIsFilter" | |
_DEFAULT_SERVER_NAME = "Default Web Site" | |
_DEFAULT_HEADERS = "X-Powered-By: Python" | |
_DEFAULT_PROTECTION = _APP_POOLED | |
# Default is for 'execute' only access - ie, only the extension | |
# can be used. This can be overridden via your install script. | |
_DEFAULT_ACCESS_EXECUTE = True | |
_DEFAULT_ACCESS_READ = False | |
_DEFAULT_ACCESS_WRITE = False | |
_DEFAULT_ACCESS_SCRIPT = False | |
_DEFAULT_CONTENT_INDEXED = False | |
_DEFAULT_ENABLE_DIR_BROWSING = False | |
_DEFAULT_ENABLE_DEFAULT_DOC = False | |
_extensions = [ext for ext, _, _ in imp.get_suffixes()] | |
is_debug_build = "_d.pyd" in _extensions | |
this_dir = os.path.abspath(os.path.dirname(__file__)) | |
class FilterParameters: | |
Name = None | |
Description = None | |
Path = None | |
Server = None | |
# Params that control if/how AddExtensionFile is called. | |
AddExtensionFile = True | |
AddExtensionFile_Enabled = True | |
AddExtensionFile_GroupID = None # defaults to Name | |
AddExtensionFile_CanDelete = True | |
AddExtensionFile_Description = None # defaults to Description. | |
def __init__(self, **kw): | |
self.__dict__.update(kw) | |
class VirtualDirParameters: | |
Name = None # Must be provided. | |
Description = None # defaults to Name | |
AppProtection = _DEFAULT_PROTECTION | |
Headers = _DEFAULT_HEADERS | |
Path = None # defaults to WWW root. | |
Type = _IIS_WEBVIRTUALDIR | |
AccessExecute = _DEFAULT_ACCESS_EXECUTE | |
AccessRead = _DEFAULT_ACCESS_READ | |
AccessWrite = _DEFAULT_ACCESS_WRITE | |
AccessScript = _DEFAULT_ACCESS_SCRIPT | |
ContentIndexed = _DEFAULT_CONTENT_INDEXED | |
EnableDirBrowsing = _DEFAULT_ENABLE_DIR_BROWSING | |
EnableDefaultDoc = _DEFAULT_ENABLE_DEFAULT_DOC | |
DefaultDoc = None # Only set in IIS if not None | |
ScriptMaps = [] | |
ScriptMapUpdate = "end" # can be 'start', 'end', 'replace' | |
Server = None | |
def __init__(self, **kw): | |
self.__dict__.update(kw) | |
def is_root(self): | |
"This virtual directory is a root directory if parent and name are blank" | |
parent, name = self.split_path() | |
return not parent and not name | |
def split_path(self): | |
return split_path(self.Name) | |
class ScriptMapParams: | |
Extension = None | |
Module = None | |
Flags = 5 | |
Verbs = "" | |
# Params that control if/how AddExtensionFile is called. | |
AddExtensionFile = True | |
AddExtensionFile_Enabled = True | |
AddExtensionFile_GroupID = None # defaults to Name | |
AddExtensionFile_CanDelete = True | |
AddExtensionFile_Description = None # defaults to Description. | |
def __init__(self, **kw): | |
self.__dict__.update(kw) | |
def __str__(self): | |
"Format this parameter suitable for IIS" | |
items = [self.Extension, self.Module, self.Flags] | |
# IIS gets upset if there is a trailing verb comma, but no verbs | |
if self.Verbs: | |
items.append(self.Verbs) | |
items = [str(item) for item in items] | |
return ",".join(items) | |
class ISAPIParameters: | |
ServerName = _DEFAULT_SERVER_NAME | |
# Description = None | |
Filters = [] | |
VirtualDirs = [] | |
def __init__(self, **kw): | |
self.__dict__.update(kw) | |
verbose = 1 # The level - 0 is quiet. | |
def log(level, what): | |
if verbose >= level: | |
print(what) | |
# Convert an ADSI COM exception to the Win32 error code embedded in it. | |
def _GetWin32ErrorCode(com_exc): | |
hr = com_exc.hresult | |
# If we have more details in the 'excepinfo' struct, use it. | |
if com_exc.excepinfo: | |
hr = com_exc.excepinfo[-1] | |
if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32: | |
raise | |
return winerror.SCODE_CODE(hr) | |
class InstallationError(Exception): | |
pass | |
class ItemNotFound(InstallationError): | |
pass | |
class ConfigurationError(InstallationError): | |
pass | |
def FindPath(options, server, name): | |
if name.lower().startswith("iis://"): | |
return name | |
else: | |
if name and name[0] != "/": | |
name = "/" + name | |
return FindWebServer(options, server) + "/ROOT" + name | |
def LocateWebServerPath(description): | |
""" | |
Find an IIS web server whose name or comment matches the provided | |
description (case-insensitive). | |
>>> LocateWebServerPath('Default Web Site') # doctest: +SKIP | |
or | |
>>> LocateWebServerPath('1') #doctest: +SKIP | |
""" | |
assert len(description) >= 1, "Server name or comment is required" | |
iis = GetObject(_IIS_OBJECT) | |
description = description.lower().strip() | |
for site in iis: | |
# Name is generally a number, but no need to assume that. | |
site_attributes = [ | |
getattr(site, attr, "").lower().strip() | |
for attr in ("Name", "ServerComment") | |
] | |
if description in site_attributes: | |
return site.AdsPath | |
msg = "No web sites match the description '%s'" % description | |
raise ItemNotFound(msg) | |
def GetWebServer(description=None): | |
""" | |
Load the web server instance (COM object) for a given instance | |
or description. | |
If None is specified, the default website is retrieved (indicated | |
by the identifier 1. | |
""" | |
description = description or "1" | |
path = LocateWebServerPath(description) | |
server = LoadWebServer(path) | |
return server | |
def LoadWebServer(path): | |
try: | |
server = GetObject(path) | |
except pythoncom.com_error as details: | |
msg = details.strerror | |
if exc.excepinfo and exc.excepinfo[2]: | |
msg = exc.excepinfo[2] | |
msg = "WebServer %s: %s" % (path, msg) | |
raise ItemNotFound(msg) | |
return server | |
def FindWebServer(options, server_desc): | |
""" | |
Legacy function to allow options to define a .server property | |
to override the other parameter. Use GetWebServer instead. | |
""" | |
# options takes precedence | |
server_desc = options.server or server_desc | |
# make sure server_desc is unicode (could be mbcs if passed in | |
# sys.argv). | |
if server_desc and not isinstance(server_desc, str): | |
server_desc = server_desc.decode("mbcs") | |
# get the server (if server_desc is None, the default site is acquired) | |
server = GetWebServer(server_desc) | |
return server.adsPath | |
def split_path(path): | |
""" | |
Get the parent path and basename. | |
>>> split_path('/') | |
['', ''] | |
>>> split_path('') | |
['', ''] | |
>>> split_path('foo') | |
['', 'foo'] | |
>>> split_path('/foo') | |
['', 'foo'] | |
>>> split_path('/foo/bar') | |
['/foo', 'bar'] | |
>>> split_path('foo/bar') | |
['/foo', 'bar'] | |
""" | |
if not path.startswith("/"): | |
path = "/" + path | |
return path.rsplit("/", 1) | |
def _CreateDirectory(iis_dir, name, params): | |
# We used to go to lengths to keep an existing virtual directory | |
# in place. However, in some cases the existing directories got | |
# into a bad state, and an update failed to get them working. | |
# So we nuke it first. If this is a problem, we could consider adding | |
# a --keep-existing option. | |
try: | |
# Also seen the Class change to a generic IISObject - so nuke | |
# *any* existing object, regardless of Class | |
assert name.strip("/"), "mustn't delete the root!" | |
iis_dir.Delete("", name) | |
log(2, "Deleted old directory '%s'" % (name,)) | |
except pythoncom.com_error: | |
pass | |
newDir = iis_dir.Create(params.Type, name) | |
log(2, "Creating new directory '%s' in %s..." % (name, iis_dir.Name)) | |
friendly = params.Description or params.Name | |
newDir.AppFriendlyName = friendly | |
# Note that the new directory won't be visible in the IIS UI | |
# unless the directory exists on the filesystem. | |
try: | |
path = params.Path or iis_dir.Path | |
newDir.Path = path | |
except AttributeError: | |
# If params.Type is IIS_WEBDIRECTORY, an exception is thrown | |
pass | |
newDir.AppCreate2(params.AppProtection) | |
# XXX - note that these Headers only work in IIS6 and earlier. IIS7 | |
# only supports them on the w3svc node - not even on individial sites, | |
# let alone individual extensions in the site! | |
if params.Headers: | |
newDir.HttpCustomHeaders = params.Headers | |
log(2, "Setting directory options...") | |
newDir.AccessExecute = params.AccessExecute | |
newDir.AccessRead = params.AccessRead | |
newDir.AccessWrite = params.AccessWrite | |
newDir.AccessScript = params.AccessScript | |
newDir.ContentIndexed = params.ContentIndexed | |
newDir.EnableDirBrowsing = params.EnableDirBrowsing | |
newDir.EnableDefaultDoc = params.EnableDefaultDoc | |
if params.DefaultDoc is not None: | |
newDir.DefaultDoc = params.DefaultDoc | |
newDir.SetInfo() | |
return newDir | |
def CreateDirectory(params, options): | |
_CallHook(params, "PreInstall", options) | |
if not params.Name: | |
raise ConfigurationError("No Name param") | |
parent, name = params.split_path() | |
target_dir = GetObject(FindPath(options, params.Server, parent)) | |
if not params.is_root(): | |
target_dir = _CreateDirectory(target_dir, name, params) | |
AssignScriptMaps(params.ScriptMaps, target_dir, params.ScriptMapUpdate) | |
_CallHook(params, "PostInstall", options, target_dir) | |
log(1, "Configured Virtual Directory: %s" % (params.Name,)) | |
return target_dir | |
def AssignScriptMaps(script_maps, target, update="replace"): | |
"""Updates IIS with the supplied script map information. | |
script_maps is a list of ScriptMapParameter objects | |
target is an IIS Virtual Directory to assign the script maps to | |
update is a string indicating how to update the maps, one of ('start', | |
'end', or 'replace') | |
""" | |
# determine which function to use to assign script maps | |
script_map_func = "_AssignScriptMaps" + update.capitalize() | |
try: | |
script_map_func = eval(script_map_func) | |
except NameError: | |
msg = "Unknown ScriptMapUpdate option '%s'" % update | |
raise ConfigurationError(msg) | |
# use the str method to format the script maps for IIS | |
script_maps = [str(s) for s in script_maps] | |
# call the correct function | |
script_map_func(target, script_maps) | |
target.SetInfo() | |
def get_unique_items(sequence, reference): | |
"Return items in sequence that can't be found in reference." | |
return tuple([item for item in sequence if item not in reference]) | |
def _AssignScriptMapsReplace(target, script_maps): | |
target.ScriptMaps = script_maps | |
def _AssignScriptMapsEnd(target, script_maps): | |
unique_new_maps = get_unique_items(script_maps, target.ScriptMaps) | |
target.ScriptMaps = target.ScriptMaps + unique_new_maps | |
def _AssignScriptMapsStart(target, script_maps): | |
unique_new_maps = get_unique_items(script_maps, target.ScriptMaps) | |
target.ScriptMaps = unique_new_maps + target.ScriptMaps | |
def CreateISAPIFilter(filterParams, options): | |
server = FindWebServer(options, filterParams.Server) | |
_CallHook(filterParams, "PreInstall", options) | |
try: | |
filters = GetObject(server + "/Filters") | |
except pythoncom.com_error as exc: | |
# Brand new sites don't have the '/Filters' collection - create it. | |
# Any errors other than 'not found' we shouldn't ignore. | |
if ( | |
winerror.HRESULT_FACILITY(exc.hresult) != winerror.FACILITY_WIN32 | |
or winerror.HRESULT_CODE(exc.hresult) != winerror.ERROR_PATH_NOT_FOUND | |
): | |
raise | |
server_ob = GetObject(server) | |
filters = server_ob.Create(_IIS_FILTERS, "Filters") | |
filters.FilterLoadOrder = "" | |
filters.SetInfo() | |
# As for VirtualDir, delete an existing one. | |
assert filterParams.Name.strip("/"), "mustn't delete the root!" | |
try: | |
filters.Delete(_IIS_FILTER, filterParams.Name) | |
log(2, "Deleted old filter '%s'" % (filterParams.Name,)) | |
except pythoncom.com_error: | |
pass | |
newFilter = filters.Create(_IIS_FILTER, filterParams.Name) | |
log(2, "Created new ISAPI filter...") | |
assert os.path.isfile(filterParams.Path) | |
newFilter.FilterPath = filterParams.Path | |
newFilter.FilterDescription = filterParams.Description | |
newFilter.SetInfo() | |
load_order = [b.strip() for b in filters.FilterLoadOrder.split(",") if b] | |
if filterParams.Name not in load_order: | |
load_order.append(filterParams.Name) | |
filters.FilterLoadOrder = ",".join(load_order) | |
filters.SetInfo() | |
_CallHook(filterParams, "PostInstall", options, newFilter) | |
log(1, "Configured Filter: %s" % (filterParams.Name,)) | |
return newFilter | |
def DeleteISAPIFilter(filterParams, options): | |
_CallHook(filterParams, "PreRemove", options) | |
server = FindWebServer(options, filterParams.Server) | |
ob_path = server + "/Filters" | |
try: | |
filters = GetObject(ob_path) | |
except pythoncom.com_error as details: | |
# failure to open the filters just means a totally clean IIS install | |
# (IIS5 at least has no 'Filters' key when freshly installed). | |
log(2, "ISAPI filter path '%s' did not exist." % (ob_path,)) | |
return | |
try: | |
assert filterParams.Name.strip("/"), "mustn't delete the root!" | |
filters.Delete(_IIS_FILTER, filterParams.Name) | |
log(2, "Deleted ISAPI filter '%s'" % (filterParams.Name,)) | |
except pythoncom.com_error as details: | |
rc = _GetWin32ErrorCode(details) | |
if rc != winerror.ERROR_PATH_NOT_FOUND: | |
raise | |
log(2, "ISAPI filter '%s' did not exist." % (filterParams.Name,)) | |
# Remove from the load order | |
load_order = [b.strip() for b in filters.FilterLoadOrder.split(",") if b] | |
if filterParams.Name in load_order: | |
load_order.remove(filterParams.Name) | |
filters.FilterLoadOrder = ",".join(load_order) | |
filters.SetInfo() | |
_CallHook(filterParams, "PostRemove", options) | |
log(1, "Deleted Filter: %s" % (filterParams.Name,)) | |
def _AddExtensionFile(module, def_groupid, def_desc, params, options): | |
group_id = params.AddExtensionFile_GroupID or def_groupid | |
desc = params.AddExtensionFile_Description or def_desc | |
try: | |
ob = GetObject(_IIS_OBJECT) | |
ob.AddExtensionFile( | |
module, | |
params.AddExtensionFile_Enabled, | |
group_id, | |
params.AddExtensionFile_CanDelete, | |
desc, | |
) | |
log(2, "Added extension file '%s' (%s)" % (module, desc)) | |
except (pythoncom.com_error, AttributeError) as details: | |
# IIS5 always fails. Probably should upgrade this to | |
# complain more loudly if IIS6 fails. | |
log(2, "Failed to add extension file '%s': %s" % (module, details)) | |
def AddExtensionFiles(params, options): | |
"""Register the modules used by the filters/extensions as a trusted | |
'extension module' - required by the default IIS6 security settings.""" | |
# Add each module only once. | |
added = {} | |
for vd in params.VirtualDirs: | |
for smp in vd.ScriptMaps: | |
if smp.Module not in added and smp.AddExtensionFile: | |
_AddExtensionFile(smp.Module, vd.Name, vd.Description, smp, options) | |
added[smp.Module] = True | |
for fd in params.Filters: | |
if fd.Path not in added and fd.AddExtensionFile: | |
_AddExtensionFile(fd.Path, fd.Name, fd.Description, fd, options) | |
added[fd.Path] = True | |
def _DeleteExtensionFileRecord(module, options): | |
try: | |
ob = GetObject(_IIS_OBJECT) | |
ob.DeleteExtensionFileRecord(module) | |
log(2, "Deleted extension file record for '%s'" % module) | |
except (pythoncom.com_error, AttributeError) as details: | |
log(2, "Failed to remove extension file '%s': %s" % (module, details)) | |
def DeleteExtensionFileRecords(params, options): | |
deleted = {} # only remove each .dll once. | |
for vd in params.VirtualDirs: | |
for smp in vd.ScriptMaps: | |
if smp.Module not in deleted and smp.AddExtensionFile: | |
_DeleteExtensionFileRecord(smp.Module, options) | |
deleted[smp.Module] = True | |
for filter_def in params.Filters: | |
if filter_def.Path not in deleted and filter_def.AddExtensionFile: | |
_DeleteExtensionFileRecord(filter_def.Path, options) | |
deleted[filter_def.Path] = True | |
def CheckLoaderModule(dll_name): | |
suffix = "" | |
if is_debug_build: | |
suffix = "_d" | |
template = os.path.join(this_dir, "PyISAPI_loader" + suffix + ".dll") | |
if not os.path.isfile(template): | |
raise ConfigurationError("Template loader '%s' does not exist" % (template,)) | |
# We can't do a simple "is newer" check, as the DLL is specific to the | |
# Python version. So we check the date-time and size are identical, | |
# and skip the copy in that case. | |
src_stat = os.stat(template) | |
try: | |
dest_stat = os.stat(dll_name) | |
except os.error: | |
same = 0 | |
else: | |
same = ( | |
src_stat[stat.ST_SIZE] == dest_stat[stat.ST_SIZE] | |
and src_stat[stat.ST_MTIME] == dest_stat[stat.ST_MTIME] | |
) | |
if not same: | |
log(2, "Updating %s->%s" % (template, dll_name)) | |
shutil.copyfile(template, dll_name) | |
shutil.copystat(template, dll_name) | |
else: | |
log(2, "%s is up to date." % (dll_name,)) | |
def _CallHook(ob, hook_name, options, *extra_args): | |
func = getattr(ob, hook_name, None) | |
if func is not None: | |
args = (ob, options) + extra_args | |
func(*args) | |
def Install(params, options): | |
_CallHook(params, "PreInstall", options) | |
for vd in params.VirtualDirs: | |
CreateDirectory(vd, options) | |
for filter_def in params.Filters: | |
CreateISAPIFilter(filter_def, options) | |
AddExtensionFiles(params, options) | |
_CallHook(params, "PostInstall", options) | |
def RemoveDirectory(params, options): | |
if params.is_root(): | |
return | |
try: | |
directory = GetObject(FindPath(options, params.Server, params.Name)) | |
except pythoncom.com_error as details: | |
rc = _GetWin32ErrorCode(details) | |
if rc != winerror.ERROR_PATH_NOT_FOUND: | |
raise | |
log(2, "VirtualDirectory '%s' did not exist" % params.Name) | |
directory = None | |
if directory is not None: | |
# Be robust should IIS get upset about unloading. | |
try: | |
directory.AppUnLoad() | |
except: | |
exc_val = sys.exc_info()[1] | |
log(2, "AppUnLoad() for %s failed: %s" % (params.Name, exc_val)) | |
# Continue trying to delete it. | |
try: | |
parent = GetObject(directory.Parent) | |
parent.Delete(directory.Class, directory.Name) | |
log(1, "Deleted Virtual Directory: %s" % (params.Name,)) | |
except: | |
exc_val = sys.exc_info()[1] | |
log(1, "Failed to remove directory %s: %s" % (params.Name, exc_val)) | |
def RemoveScriptMaps(vd_params, options): | |
"Remove script maps from the already installed virtual directory" | |
parent, name = vd_params.split_path() | |
target_dir = GetObject(FindPath(options, vd_params.Server, parent)) | |
installed_maps = list(target_dir.ScriptMaps) | |
for _map in map(str, vd_params.ScriptMaps): | |
if _map in installed_maps: | |
installed_maps.remove(_map) | |
target_dir.ScriptMaps = installed_maps | |
target_dir.SetInfo() | |
def Uninstall(params, options): | |
_CallHook(params, "PreRemove", options) | |
DeleteExtensionFileRecords(params, options) | |
for vd in params.VirtualDirs: | |
_CallHook(vd, "PreRemove", options) | |
RemoveDirectory(vd, options) | |
if vd.is_root(): | |
# if this is installed to the root virtual directory, we can't delete it | |
# so remove the script maps. | |
RemoveScriptMaps(vd, options) | |
_CallHook(vd, "PostRemove", options) | |
for filter_def in params.Filters: | |
DeleteISAPIFilter(filter_def, options) | |
_CallHook(params, "PostRemove", options) | |
# Patch up any missing module names in the params, replacing them with | |
# the DLL name that hosts this extension/filter. | |
def _PatchParamsModule(params, dll_name, file_must_exist=True): | |
if file_must_exist: | |
if not os.path.isfile(dll_name): | |
raise ConfigurationError("%s does not exist" % (dll_name,)) | |
# Patch up all references to the DLL. | |
for f in params.Filters: | |
if f.Path is None: | |
f.Path = dll_name | |
for d in params.VirtualDirs: | |
for sm in d.ScriptMaps: | |
if sm.Module is None: | |
sm.Module = dll_name | |
def GetLoaderModuleName(mod_name, check_module=None): | |
# find the name of the DLL hosting us. | |
# By default, this is "_{module_base_name}.dll" | |
if hasattr(sys, "frozen"): | |
# What to do? The .dll knows its name, but this is likely to be | |
# executed via a .exe, which does not know. | |
base, ext = os.path.splitext(mod_name) | |
path, base = os.path.split(base) | |
# handle the common case of 'foo.exe'/'foow.exe' | |
if base.endswith("w"): | |
base = base[:-1] | |
# For py2exe, we have '_foo.dll' as the standard pyisapi loader - but | |
# 'foo.dll' is what we use (it just delegates). | |
# So no leading '_' on the installed name. | |
dll_name = os.path.abspath(os.path.join(path, base + ".dll")) | |
else: | |
base, ext = os.path.splitext(mod_name) | |
path, base = os.path.split(base) | |
dll_name = os.path.abspath(os.path.join(path, "_" + base + ".dll")) | |
# Check we actually have it. | |
if check_module is None: | |
check_module = not hasattr(sys, "frozen") | |
if check_module: | |
CheckLoaderModule(dll_name) | |
return dll_name | |
# Note the 'log' params to these 'builtin' args - old versions of pywin32 | |
# didn't log at all in this function (by intent; anyone calling this was | |
# responsible). So existing code that calls this function with the old | |
# signature (ie, without a 'log' param) still gets the same behaviour as | |
# before... | |
def InstallModule(conf_module_name, params, options, log=lambda *args: None): | |
"Install the extension" | |
if not hasattr(sys, "frozen"): | |
conf_module_name = os.path.abspath(conf_module_name) | |
if not os.path.isfile(conf_module_name): | |
raise ConfigurationError("%s does not exist" % (conf_module_name,)) | |
loader_dll = GetLoaderModuleName(conf_module_name) | |
_PatchParamsModule(params, loader_dll) | |
Install(params, options) | |
log(1, "Installation complete.") | |
def UninstallModule(conf_module_name, params, options, log=lambda *args: None): | |
"Remove the extension" | |
loader_dll = GetLoaderModuleName(conf_module_name, False) | |
_PatchParamsModule(params, loader_dll, False) | |
Uninstall(params, options) | |
log(1, "Uninstallation complete.") | |
standard_arguments = { | |
"install": InstallModule, | |
"remove": UninstallModule, | |
} | |
def build_usage(handler_map): | |
docstrings = [handler.__doc__ for handler in handler_map.values()] | |
all_args = dict(zip(iter(handler_map.keys()), docstrings)) | |
arg_names = "|".join(iter(all_args.keys())) | |
usage_string = "%prog [options] [" + arg_names + "]\n" | |
usage_string += "commands:\n" | |
for arg, desc in all_args.items(): | |
usage_string += " %-10s: %s" % (arg, desc) + "\n" | |
return usage_string[:-1] | |
def MergeStandardOptions(options, params): | |
""" | |
Take an options object generated by the command line and merge | |
the values into the IISParameters object. | |
""" | |
pass | |
# We support 2 ways of extending our command-line/install support. | |
# * Many of the installation items allow you to specify "PreInstall", | |
# "PostInstall", "PreRemove" and "PostRemove" hooks | |
# All hooks are called with the 'params' object being operated on, and | |
# the 'optparser' options for this session (ie, the command-line options) | |
# PostInstall for VirtualDirectories and Filters both have an additional | |
# param - the ADSI object just created. | |
# * You can pass your own option parser for us to use, and/or define a map | |
# with your own custom arg handlers. It is a map of 'arg'->function. | |
# The function is called with (options, log_fn, arg). The function's | |
# docstring is used in the usage output. | |
def HandleCommandLine( | |
params, | |
argv=None, | |
conf_module_name=None, | |
default_arg="install", | |
opt_parser=None, | |
custom_arg_handlers={}, | |
): | |
"""Perform installation or removal of an ISAPI filter or extension. | |
This module handles standard command-line options and configuration | |
information, and installs, removes or updates the configuration of an | |
ISAPI filter or extension. | |
You must pass your configuration information in params - all other | |
arguments are optional, and allow you to configure the installation | |
process. | |
""" | |
global verbose | |
from optparse import OptionParser | |
argv = argv or sys.argv | |
if not conf_module_name: | |
conf_module_name = sys.argv[0] | |
# convert to a long name so that if we were somehow registered with | |
# the "short" version but unregistered with the "long" version we | |
# still work (that will depend on exactly how the installer was | |
# started) | |
try: | |
conf_module_name = win32api.GetLongPathName(conf_module_name) | |
except win32api.error as exc: | |
log( | |
2, | |
"Couldn't determine the long name for %r: %s" % (conf_module_name, exc), | |
) | |
if opt_parser is None: | |
# Build our own parser. | |
parser = OptionParser(usage="") | |
else: | |
# The caller is providing their own filter, presumably with their | |
# own options all setup. | |
parser = opt_parser | |
# build a usage string if we don't have one. | |
if not parser.get_usage(): | |
all_handlers = standard_arguments.copy() | |
all_handlers.update(custom_arg_handlers) | |
parser.set_usage(build_usage(all_handlers)) | |
# allow the user to use uninstall as a synonym for remove if it wasn't | |
# defined by the custom arg handlers. | |
all_handlers.setdefault("uninstall", all_handlers["remove"]) | |
parser.add_option( | |
"-q", | |
"--quiet", | |
action="store_false", | |
dest="verbose", | |
default=True, | |
help="don't print status messages to stdout", | |
) | |
parser.add_option( | |
"-v", | |
"--verbosity", | |
action="count", | |
dest="verbose", | |
default=1, | |
help="increase the verbosity of status messages", | |
) | |
parser.add_option( | |
"", | |
"--server", | |
action="store", | |
help="Specifies the IIS server to install/uninstall on." | |
" Default is '%s/1'" % (_IIS_OBJECT,), | |
) | |
(options, args) = parser.parse_args(argv[1:]) | |
MergeStandardOptions(options, params) | |
verbose = options.verbose | |
if not args: | |
args = [default_arg] | |
try: | |
for arg in args: | |
handler = all_handlers[arg] | |
handler(conf_module_name, params, options, log) | |
except (ItemNotFound, InstallationError) as details: | |
if options.verbose > 1: | |
traceback.print_exc() | |
print("%s: %s" % (details.__class__.__name__, details)) | |
except KeyError: | |
parser.error("Invalid arg '%s'" % arg) | |