123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- """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 sys, os, imp, shutil, stat
- import operator
- from win32com.client import GetObject, Dispatch
- from win32com.client.gencache import EnsureModule, EnsureDispatch
- import win32api
- import pythoncom
- import winerror
- import traceback
- _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)
|