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) | |