codekingpro/portable-devtools
114k
1"""Installation utilities for Python ISAPI filters and extensions."""2 3# this code adapted from "Tomcat JK2 ISAPI redirector", part of Apache4# Created July 2004, Mark Hammond.5import imp6import os7import shutil8import stat9import sys10import traceback11 12import pythoncom13import win32api14import winerror15from win32com.client import Dispatch, GetObject16from win32com.client.gencache import EnsureDispatch, EnsureModule17 18_APP_INPROC = 019_APP_OUTPROC = 120_APP_POOLED = 221_IIS_OBJECT = "IIS://LocalHost/W3SVC"22_IIS_SERVER = "IIsWebServer"23_IIS_WEBDIR = "IIsWebDirectory"24_IIS_WEBVIRTUALDIR = "IIsWebVirtualDir"25_IIS_FILTERS = "IIsFilters"26_IIS_FILTER = "IIsFilter"27 28_DEFAULT_SERVER_NAME = "Default Web Site"29_DEFAULT_HEADERS = "X-Powered-By: Python"30_DEFAULT_PROTECTION = _APP_POOLED31 32# Default is for 'execute' only access - ie, only the extension33# can be used. This can be overridden via your install script.34_DEFAULT_ACCESS_EXECUTE = True35_DEFAULT_ACCESS_READ = False36_DEFAULT_ACCESS_WRITE = False37_DEFAULT_ACCESS_SCRIPT = False38_DEFAULT_CONTENT_INDEXED = False39_DEFAULT_ENABLE_DIR_BROWSING = False40_DEFAULT_ENABLE_DEFAULT_DOC = False41 42_extensions = [ext for ext, _, _ in imp.get_suffixes()]43is_debug_build = "_d.pyd" in _extensions44 45this_dir = os.path.abspath(os.path.dirname(__file__))46 47 48class FilterParameters:49 Name = None50 Description = None51 Path = None52 Server = None53 # Params that control if/how AddExtensionFile is called.54 AddExtensionFile = True55 AddExtensionFile_Enabled = True56 AddExtensionFile_GroupID = None # defaults to Name57 AddExtensionFile_CanDelete = True58 AddExtensionFile_Description = None # defaults to Description.59 60 def __init__(self, **kw):61 self.__dict__.update(kw)62 63 64class VirtualDirParameters:65 Name = None # Must be provided.66 Description = None # defaults to Name67 AppProtection = _DEFAULT_PROTECTION68 Headers = _DEFAULT_HEADERS69 Path = None # defaults to WWW root.70 Type = _IIS_WEBVIRTUALDIR71 AccessExecute = _DEFAULT_ACCESS_EXECUTE72 AccessRead = _DEFAULT_ACCESS_READ73 AccessWrite = _DEFAULT_ACCESS_WRITE74 AccessScript = _DEFAULT_ACCESS_SCRIPT75 ContentIndexed = _DEFAULT_CONTENT_INDEXED76 EnableDirBrowsing = _DEFAULT_ENABLE_DIR_BROWSING77 EnableDefaultDoc = _DEFAULT_ENABLE_DEFAULT_DOC78 DefaultDoc = None # Only set in IIS if not None79 ScriptMaps = []80 ScriptMapUpdate = "end" # can be 'start', 'end', 'replace'81 Server = None82 83 def __init__(self, **kw):84 self.__dict__.update(kw)85 86 def is_root(self):87 "This virtual directory is a root directory if parent and name are blank"88 parent, name = self.split_path()89 return not parent and not name90 91 def split_path(self):92 return split_path(self.Name)93 94 95class ScriptMapParams:96 Extension = None97 Module = None98 Flags = 599 Verbs = ""100 # Params that control if/how AddExtensionFile is called.101 AddExtensionFile = True102 AddExtensionFile_Enabled = True103 AddExtensionFile_GroupID = None # defaults to Name104 AddExtensionFile_CanDelete = True105 AddExtensionFile_Description = None # defaults to Description.106 107 def __init__(self, **kw):108 self.__dict__.update(kw)109 110 def __str__(self):111 "Format this parameter suitable for IIS"112 items = [self.Extension, self.Module, self.Flags]113 # IIS gets upset if there is a trailing verb comma, but no verbs114 if self.Verbs:115 items.append(self.Verbs)116 items = [str(item) for item in items]117 return ",".join(items)118 119 120class ISAPIParameters:121 ServerName = _DEFAULT_SERVER_NAME122 # Description = None123 Filters = []124 VirtualDirs = []125 126 def __init__(self, **kw):127 self.__dict__.update(kw)128 129 130verbose = 1 # The level - 0 is quiet.131 132 133def log(level, what):134 if verbose >= level:135 print(what)136 137 138# Convert an ADSI COM exception to the Win32 error code embedded in it.139def _GetWin32ErrorCode(com_exc):140 hr = com_exc.hresult141 # If we have more details in the 'excepinfo' struct, use it.142 if com_exc.excepinfo:143 hr = com_exc.excepinfo[-1]144 if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32:145 raise146 return winerror.SCODE_CODE(hr)147 148 149class InstallationError(Exception):150 pass151 152 153class ItemNotFound(InstallationError):154 pass155 156 157class ConfigurationError(InstallationError):158 pass159 160 161def FindPath(options, server, name):162 if name.lower().startswith("iis://"):163 return name164 else:165 if name and name[0] != "/":166 name = "/" + name167 return FindWebServer(options, server) + "/ROOT" + name168 169 170def LocateWebServerPath(description):171 """172 Find an IIS web server whose name or comment matches the provided173 description (case-insensitive).174 175 >>> LocateWebServerPath('Default Web Site') # doctest: +SKIP176 177 or178 179 >>> LocateWebServerPath('1') #doctest: +SKIP180 """181 assert len(description) >= 1, "Server name or comment is required"182 iis = GetObject(_IIS_OBJECT)183 description = description.lower().strip()184 for site in iis:185 # Name is generally a number, but no need to assume that.186 site_attributes = [187 getattr(site, attr, "").lower().strip()188 for attr in ("Name", "ServerComment")189 ]190 if description in site_attributes:191 return site.AdsPath192 msg = "No web sites match the description '%s'" % description193 raise ItemNotFound(msg)194 195 196def GetWebServer(description=None):197 """198 Load the web server instance (COM object) for a given instance199 or description.200 If None is specified, the default website is retrieved (indicated201 by the identifier 1.202 """203 description = description or "1"204 path = LocateWebServerPath(description)205 server = LoadWebServer(path)206 return server207 208 209def LoadWebServer(path):210 try:211 server = GetObject(path)212 except pythoncom.com_error as details:213 msg = details.strerror214 if exc.excepinfo and exc.excepinfo[2]:215 msg = exc.excepinfo[2]216 msg = "WebServer %s: %s" % (path, msg)217 raise ItemNotFound(msg)218 return server219 220 221def FindWebServer(options, server_desc):222 """223 Legacy function to allow options to define a .server property224 to override the other parameter. Use GetWebServer instead.225 """226 # options takes precedence227 server_desc = options.server or server_desc228 # make sure server_desc is unicode (could be mbcs if passed in229 # sys.argv).230 if server_desc and not isinstance(server_desc, str):231 server_desc = server_desc.decode("mbcs")232 233 # get the server (if server_desc is None, the default site is acquired)234 server = GetWebServer(server_desc)235 return server.adsPath236 237 238def split_path(path):239 """240 Get the parent path and basename.241 242 >>> split_path('/')243 ['', '']244 245 >>> split_path('')246 ['', '']247 248 >>> split_path('foo')249 ['', 'foo']250 251 >>> split_path('/foo')252 ['', 'foo']253 254 >>> split_path('/foo/bar')255 ['/foo', 'bar']256 257 >>> split_path('foo/bar')258 ['/foo', 'bar']259 """260 261 if not path.startswith("/"):262 path = "/" + path263 return path.rsplit("/", 1)264 265 266def _CreateDirectory(iis_dir, name, params):267 # We used to go to lengths to keep an existing virtual directory268 # in place. However, in some cases the existing directories got269 # into a bad state, and an update failed to get them working.270 # So we nuke it first. If this is a problem, we could consider adding271 # a --keep-existing option.272 try:273 # Also seen the Class change to a generic IISObject - so nuke274 # *any* existing object, regardless of Class275 assert name.strip("/"), "mustn't delete the root!"276 iis_dir.Delete("", name)277 log(2, "Deleted old directory '%s'" % (name,))278 except pythoncom.com_error:279 pass280 281 newDir = iis_dir.Create(params.Type, name)282 log(2, "Creating new directory '%s' in %s..." % (name, iis_dir.Name))283 284 friendly = params.Description or params.Name285 newDir.AppFriendlyName = friendly286 287 # Note that the new directory won't be visible in the IIS UI288 # unless the directory exists on the filesystem.289 try:290 path = params.Path or iis_dir.Path291 newDir.Path = path292 except AttributeError:293 # If params.Type is IIS_WEBDIRECTORY, an exception is thrown294 pass295 newDir.AppCreate2(params.AppProtection)296 # XXX - note that these Headers only work in IIS6 and earlier. IIS7297 # only supports them on the w3svc node - not even on individial sites,298 # let alone individual extensions in the site!299 if params.Headers:300 newDir.HttpCustomHeaders = params.Headers301 302 log(2, "Setting directory options...")303 newDir.AccessExecute = params.AccessExecute304 newDir.AccessRead = params.AccessRead305 newDir.AccessWrite = params.AccessWrite306 newDir.AccessScript = params.AccessScript307 newDir.ContentIndexed = params.ContentIndexed308 newDir.EnableDirBrowsing = params.EnableDirBrowsing309 newDir.EnableDefaultDoc = params.EnableDefaultDoc310 if params.DefaultDoc is not None:311 newDir.DefaultDoc = params.DefaultDoc312 newDir.SetInfo()313 return newDir314 315 316def CreateDirectory(params, options):317 _CallHook(params, "PreInstall", options)318 if not params.Name:319 raise ConfigurationError("No Name param")320 parent, name = params.split_path()321 target_dir = GetObject(FindPath(options, params.Server, parent))322 323 if not params.is_root():324 target_dir = _CreateDirectory(target_dir, name, params)325 326 AssignScriptMaps(params.ScriptMaps, target_dir, params.ScriptMapUpdate)327 328 _CallHook(params, "PostInstall", options, target_dir)329 log(1, "Configured Virtual Directory: %s" % (params.Name,))330 return target_dir331 332 333def AssignScriptMaps(script_maps, target, update="replace"):334 """Updates IIS with the supplied script map information.335 336 script_maps is a list of ScriptMapParameter objects337 338 target is an IIS Virtual Directory to assign the script maps to339 340 update is a string indicating how to update the maps, one of ('start',341 'end', or 'replace')342 """343 # determine which function to use to assign script maps344 script_map_func = "_AssignScriptMaps" + update.capitalize()345 try:346 script_map_func = eval(script_map_func)347 except NameError:348 msg = "Unknown ScriptMapUpdate option '%s'" % update349 raise ConfigurationError(msg)350 # use the str method to format the script maps for IIS351 script_maps = [str(s) for s in script_maps]352 # call the correct function353 script_map_func(target, script_maps)354 target.SetInfo()355 356 357def get_unique_items(sequence, reference):358 "Return items in sequence that can't be found in reference."359 return tuple([item for item in sequence if item not in reference])360 361 362def _AssignScriptMapsReplace(target, script_maps):363 target.ScriptMaps = script_maps364 365 366def _AssignScriptMapsEnd(target, script_maps):367 unique_new_maps = get_unique_items(script_maps, target.ScriptMaps)368 target.ScriptMaps = target.ScriptMaps + unique_new_maps369 370 371def _AssignScriptMapsStart(target, script_maps):372 unique_new_maps = get_unique_items(script_maps, target.ScriptMaps)373 target.ScriptMaps = unique_new_maps + target.ScriptMaps374 375 376def CreateISAPIFilter(filterParams, options):377 server = FindWebServer(options, filterParams.Server)378 _CallHook(filterParams, "PreInstall", options)379 try:380 filters = GetObject(server + "/Filters")381 except pythoncom.com_error as exc:382 # Brand new sites don't have the '/Filters' collection - create it.383 # Any errors other than 'not found' we shouldn't ignore.384 if (385 winerror.HRESULT_FACILITY(exc.hresult) != winerror.FACILITY_WIN32386 or winerror.HRESULT_CODE(exc.hresult) != winerror.ERROR_PATH_NOT_FOUND387 ):388 raise389 server_ob = GetObject(server)390 filters = server_ob.Create(_IIS_FILTERS, "Filters")391 filters.FilterLoadOrder = ""392 filters.SetInfo()393 394 # As for VirtualDir, delete an existing one.395 assert filterParams.Name.strip("/"), "mustn't delete the root!"396 try:397 filters.Delete(_IIS_FILTER, filterParams.Name)398 log(2, "Deleted old filter '%s'" % (filterParams.Name,))399 except pythoncom.com_error:400 pass401 newFilter = filters.Create(_IIS_FILTER, filterParams.Name)402 log(2, "Created new ISAPI filter...")403 assert os.path.isfile(filterParams.Path)404 newFilter.FilterPath = filterParams.Path405 newFilter.FilterDescription = filterParams.Description406 newFilter.SetInfo()407 load_order = [b.strip() for b in filters.FilterLoadOrder.split(",") if b]408 if filterParams.Name not in load_order:409 load_order.append(filterParams.Name)410 filters.FilterLoadOrder = ",".join(load_order)411 filters.SetInfo()412 _CallHook(filterParams, "PostInstall", options, newFilter)413 log(1, "Configured Filter: %s" % (filterParams.Name,))414 return newFilter415 416 417def DeleteISAPIFilter(filterParams, options):418 _CallHook(filterParams, "PreRemove", options)419 server = FindWebServer(options, filterParams.Server)420 ob_path = server + "/Filters"421 try:422 filters = GetObject(ob_path)423 except pythoncom.com_error as details:424 # failure to open the filters just means a totally clean IIS install425 # (IIS5 at least has no 'Filters' key when freshly installed).426 log(2, "ISAPI filter path '%s' did not exist." % (ob_path,))427 return428 try:429 assert filterParams.Name.strip("/"), "mustn't delete the root!"430 filters.Delete(_IIS_FILTER, filterParams.Name)431 log(2, "Deleted ISAPI filter '%s'" % (filterParams.Name,))432 except pythoncom.com_error as details:433 rc = _GetWin32ErrorCode(details)434 if rc != winerror.ERROR_PATH_NOT_FOUND:435 raise436 log(2, "ISAPI filter '%s' did not exist." % (filterParams.Name,))437 # Remove from the load order438 load_order = [b.strip() for b in filters.FilterLoadOrder.split(",") if b]439 if filterParams.Name in load_order:440 load_order.remove(filterParams.Name)441 filters.FilterLoadOrder = ",".join(load_order)442 filters.SetInfo()443 _CallHook(filterParams, "PostRemove", options)444 log(1, "Deleted Filter: %s" % (filterParams.Name,))445 446 447def _AddExtensionFile(module, def_groupid, def_desc, params, options):448 group_id = params.AddExtensionFile_GroupID or def_groupid449 desc = params.AddExtensionFile_Description or def_desc450 try:451 ob = GetObject(_IIS_OBJECT)452 ob.AddExtensionFile(453 module,454 params.AddExtensionFile_Enabled,455 group_id,456 params.AddExtensionFile_CanDelete,457 desc,458 )459 log(2, "Added extension file '%s' (%s)" % (module, desc))460 except (pythoncom.com_error, AttributeError) as details:461 # IIS5 always fails. Probably should upgrade this to462 # complain more loudly if IIS6 fails.463 log(2, "Failed to add extension file '%s': %s" % (module, details))464 465 466def AddExtensionFiles(params, options):467 """Register the modules used by the filters/extensions as a trusted468 'extension module' - required by the default IIS6 security settings."""469 # Add each module only once.470 added = {}471 for vd in params.VirtualDirs:472 for smp in vd.ScriptMaps:473 if smp.Module not in added and smp.AddExtensionFile:474 _AddExtensionFile(smp.Module, vd.Name, vd.Description, smp, options)475 added[smp.Module] = True476 477 for fd in params.Filters:478 if fd.Path not in added and fd.AddExtensionFile:479 _AddExtensionFile(fd.Path, fd.Name, fd.Description, fd, options)480 added[fd.Path] = True481 482 483def _DeleteExtensionFileRecord(module, options):484 try:485 ob = GetObject(_IIS_OBJECT)486 ob.DeleteExtensionFileRecord(module)487 log(2, "Deleted extension file record for '%s'" % module)488 except (pythoncom.com_error, AttributeError) as details:489 log(2, "Failed to remove extension file '%s': %s" % (module, details))490 491 492def DeleteExtensionFileRecords(params, options):493 deleted = {} # only remove each .dll once.494 for vd in params.VirtualDirs:495 for smp in vd.ScriptMaps:496 if smp.Module not in deleted and smp.AddExtensionFile:497 _DeleteExtensionFileRecord(smp.Module, options)498 deleted[smp.Module] = True499 500 for filter_def in params.Filters:501 if filter_def.Path not in deleted and filter_def.AddExtensionFile:502 _DeleteExtensionFileRecord(filter_def.Path, options)503 deleted[filter_def.Path] = True504 505 506def CheckLoaderModule(dll_name):507 suffix = ""508 if is_debug_build:509 suffix = "_d"510 template = os.path.join(this_dir, "PyISAPI_loader" + suffix + ".dll")511 if not os.path.isfile(template):512 raise ConfigurationError("Template loader '%s' does not exist" % (template,))513 # We can't do a simple "is newer" check, as the DLL is specific to the514 # Python version. So we check the date-time and size are identical,515 # and skip the copy in that case.516 src_stat = os.stat(template)517 try:518 dest_stat = os.stat(dll_name)519 except os.error:520 same = 0521 else:522 same = (523 src_stat[stat.ST_SIZE] == dest_stat[stat.ST_SIZE]524 and src_stat[stat.ST_MTIME] == dest_stat[stat.ST_MTIME]525 )526 if not same:527 log(2, "Updating %s->%s" % (template, dll_name))528 shutil.copyfile(template, dll_name)529 shutil.copystat(template, dll_name)530 else:531 log(2, "%s is up to date." % (dll_name,))532 533 534def _CallHook(ob, hook_name, options, *extra_args):535 func = getattr(ob, hook_name, None)536 if func is not None:537 args = (ob, options) + extra_args538 func(*args)539 540 541def Install(params, options):542 _CallHook(params, "PreInstall", options)543 for vd in params.VirtualDirs:544 CreateDirectory(vd, options)545 546 for filter_def in params.Filters:547 CreateISAPIFilter(filter_def, options)548 549 AddExtensionFiles(params, options)550 551 _CallHook(params, "PostInstall", options)552 553 554def RemoveDirectory(params, options):555 if params.is_root():556 return557 try:558 directory = GetObject(FindPath(options, params.Server, params.Name))559 except pythoncom.com_error as details:560 rc = _GetWin32ErrorCode(details)561 if rc != winerror.ERROR_PATH_NOT_FOUND:562 raise563 log(2, "VirtualDirectory '%s' did not exist" % params.Name)564 directory = None565 if directory is not None:566 # Be robust should IIS get upset about unloading.567 try:568 directory.AppUnLoad()569 except:570 exc_val = sys.exc_info()[1]571 log(2, "AppUnLoad() for %s failed: %s" % (params.Name, exc_val))572 # Continue trying to delete it.573 try:574 parent = GetObject(directory.Parent)575 parent.Delete(directory.Class, directory.Name)576 log(1, "Deleted Virtual Directory: %s" % (params.Name,))577 except:578 exc_val = sys.exc_info()[1]579 log(1, "Failed to remove directory %s: %s" % (params.Name, exc_val))580 581 582def RemoveScriptMaps(vd_params, options):583 "Remove script maps from the already installed virtual directory"584 parent, name = vd_params.split_path()585 target_dir = GetObject(FindPath(options, vd_params.Server, parent))586 installed_maps = list(target_dir.ScriptMaps)587 for _map in map(str, vd_params.ScriptMaps):588 if _map in installed_maps:589 installed_maps.remove(_map)590 target_dir.ScriptMaps = installed_maps591 target_dir.SetInfo()592 593 594def Uninstall(params, options):595 _CallHook(params, "PreRemove", options)596 597 DeleteExtensionFileRecords(params, options)598 599 for vd in params.VirtualDirs:600 _CallHook(vd, "PreRemove", options)601 602 RemoveDirectory(vd, options)603 if vd.is_root():604 # if this is installed to the root virtual directory, we can't delete it605 # so remove the script maps.606 RemoveScriptMaps(vd, options)607 608 _CallHook(vd, "PostRemove", options)609 610 for filter_def in params.Filters:611 DeleteISAPIFilter(filter_def, options)612 _CallHook(params, "PostRemove", options)613 614 615# Patch up any missing module names in the params, replacing them with616# the DLL name that hosts this extension/filter.617def _PatchParamsModule(params, dll_name, file_must_exist=True):618 if file_must_exist:619 if not os.path.isfile(dll_name):620 raise ConfigurationError("%s does not exist" % (dll_name,))621 622 # Patch up all references to the DLL.623 for f in params.Filters:624 if f.Path is None:625 f.Path = dll_name626 for d in params.VirtualDirs:627 for sm in d.ScriptMaps:628 if sm.Module is None:629 sm.Module = dll_name630 631 632def GetLoaderModuleName(mod_name, check_module=None):633 # find the name of the DLL hosting us.634 # By default, this is "_{module_base_name}.dll"635 if hasattr(sys, "frozen"):636 # What to do? The .dll knows its name, but this is likely to be637 # executed via a .exe, which does not know.638 base, ext = os.path.splitext(mod_name)639 path, base = os.path.split(base)640 # handle the common case of 'foo.exe'/'foow.exe'641 if base.endswith("w"):642 base = base[:-1]643 # For py2exe, we have '_foo.dll' as the standard pyisapi loader - but644 # 'foo.dll' is what we use (it just delegates).645 # So no leading '_' on the installed name.646 dll_name = os.path.abspath(os.path.join(path, base + ".dll"))647 else:648 base, ext = os.path.splitext(mod_name)649 path, base = os.path.split(base)650 dll_name = os.path.abspath(os.path.join(path, "_" + base + ".dll"))651 # Check we actually have it.652 if check_module is None:653 check_module = not hasattr(sys, "frozen")654 if check_module:655 CheckLoaderModule(dll_name)656 return dll_name657 658 659# Note the 'log' params to these 'builtin' args - old versions of pywin32660# didn't log at all in this function (by intent; anyone calling this was661# responsible). So existing code that calls this function with the old662# signature (ie, without a 'log' param) still gets the same behaviour as663# before...664 665 666def InstallModule(conf_module_name, params, options, log=lambda *args: None):667 "Install the extension"668 if not hasattr(sys, "frozen"):669 conf_module_name = os.path.abspath(conf_module_name)670 if not os.path.isfile(conf_module_name):671 raise ConfigurationError("%s does not exist" % (conf_module_name,))672 673 loader_dll = GetLoaderModuleName(conf_module_name)674 _PatchParamsModule(params, loader_dll)675 Install(params, options)676 log(1, "Installation complete.")677 678 679def UninstallModule(conf_module_name, params, options, log=lambda *args: None):680 "Remove the extension"681 loader_dll = GetLoaderModuleName(conf_module_name, False)682 _PatchParamsModule(params, loader_dll, False)683 Uninstall(params, options)684 log(1, "Uninstallation complete.")685 686 687standard_arguments = {688 "install": InstallModule,689 "remove": UninstallModule,690}691 692 693def build_usage(handler_map):694 docstrings = [handler.__doc__ for handler in handler_map.values()]695 all_args = dict(zip(iter(handler_map.keys()), docstrings))696 arg_names = "|".join(iter(all_args.keys()))697 usage_string = "%prog [options] [" + arg_names + "]\n"698 usage_string += "commands:\n"699 for arg, desc in all_args.items():700 usage_string += " %-10s: %s" % (arg, desc) + "\n"701 return usage_string[:-1]702 703 704def MergeStandardOptions(options, params):705 """706 Take an options object generated by the command line and merge707 the values into the IISParameters object.708 """709 pass710 711 712# We support 2 ways of extending our command-line/install support.713# * Many of the installation items allow you to specify "PreInstall",714# "PostInstall", "PreRemove" and "PostRemove" hooks715# All hooks are called with the 'params' object being operated on, and716# the 'optparser' options for this session (ie, the command-line options)717# PostInstall for VirtualDirectories and Filters both have an additional718# param - the ADSI object just created.719# * You can pass your own option parser for us to use, and/or define a map720# with your own custom arg handlers. It is a map of 'arg'->function.721# The function is called with (options, log_fn, arg). The function's722# docstring is used in the usage output.723def HandleCommandLine(724 params,725 argv=None,726 conf_module_name=None,727 default_arg="install",728 opt_parser=None,729 custom_arg_handlers={},730):731 """Perform installation or removal of an ISAPI filter or extension.732 733 This module handles standard command-line options and configuration734 information, and installs, removes or updates the configuration of an735 ISAPI filter or extension.736 737 You must pass your configuration information in params - all other738 arguments are optional, and allow you to configure the installation739 process.740 """741 global verbose742 from optparse import OptionParser743 744 argv = argv or sys.argv745 if not conf_module_name:746 conf_module_name = sys.argv[0]747 # convert to a long name so that if we were somehow registered with748 # the "short" version but unregistered with the "long" version we749 # still work (that will depend on exactly how the installer was750 # started)751 try:752 conf_module_name = win32api.GetLongPathName(conf_module_name)753 except win32api.error as exc:754 log(755 2,756 "Couldn't determine the long name for %r: %s" % (conf_module_name, exc),757 )758 759 if opt_parser is None:760 # Build our own parser.761 parser = OptionParser(usage="")762 else:763 # The caller is providing their own filter, presumably with their764 # own options all setup.765 parser = opt_parser766 767 # build a usage string if we don't have one.768 if not parser.get_usage():769 all_handlers = standard_arguments.copy()770 all_handlers.update(custom_arg_handlers)771 parser.set_usage(build_usage(all_handlers))772 773 # allow the user to use uninstall as a synonym for remove if it wasn't774 # defined by the custom arg handlers.775 all_handlers.setdefault("uninstall", all_handlers["remove"])776 777 parser.add_option(778 "-q",779 "--quiet",780 action="store_false",781 dest="verbose",782 default=True,783 help="don't print status messages to stdout",784 )785 parser.add_option(786 "-v",787 "--verbosity",788 action="count",789 dest="verbose",790 default=1,791 help="increase the verbosity of status messages",792 )793 parser.add_option(794 "",795 "--server",796 action="store",797 help="Specifies the IIS server to install/uninstall on."798 " Default is '%s/1'" % (_IIS_OBJECT,),799 )800 801 (options, args) = parser.parse_args(argv[1:])802 MergeStandardOptions(options, params)803 verbose = options.verbose804 if not args:805 args = [default_arg]806 try:807 for arg in args:808 handler = all_handlers[arg]809 handler(conf_module_name, params, options, log)810 except (ItemNotFound, InstallationError) as details:811 if options.verbose > 1:812 traceback.print_exc()813 print("%s: %s" % (details.__class__.__name__, details))814 except KeyError:815 parser.error("Invalid arg '%s'" % arg)816 