PageRenderTime 53ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/python/Lib/site-packages/isapi/install.py

https://github.com/beiske/play
Python | 585 lines | 566 code | 11 blank | 8 comment | 6 complexity | 00ce95e69b1aa51eba7d8bd6d4ccfa66 MD5 | raw file
  1. """Installation utilities for Python ISAPI filters and extensions."""
  2. # this code adapted from "Tomcat JK2 ISAPI redirector", part of Apache
  3. # Created July 2004, Mark Hammond.
  4. import sys, os, imp, shutil, stat
  5. from win32com.client import GetObject, Dispatch
  6. from win32com.client.gencache import EnsureModule, EnsureDispatch
  7. import pythoncom
  8. import winerror
  9. import traceback
  10. _APP_INPROC = 0
  11. _APP_OUTPROC = 1
  12. _APP_POOLED = 2
  13. _IIS_OBJECT = "IIS://LocalHost/W3SVC"
  14. _IIS_SERVER = "IIsWebServer"
  15. _IIS_WEBDIR = "IIsWebDirectory"
  16. _IIS_WEBVIRTUALDIR = "IIsWebVirtualDir"
  17. _IIS_FILTERS = "IIsFilters"
  18. _IIS_FILTER = "IIsFilter"
  19. _DEFAULT_SERVER_NAME = "Default Web Site"
  20. _DEFAULT_HEADERS = "X-Powered-By: Python"
  21. _DEFAULT_PROTECTION = _APP_POOLED
  22. # Default is for 'execute' only access - ie, only the extension
  23. # can be used. This can be overridden via your install script.
  24. _DEFAULT_ACCESS_EXECUTE = True
  25. _DEFAULT_ACCESS_READ = False
  26. _DEFAULT_ACCESS_WRITE = False
  27. _DEFAULT_ACCESS_SCRIPT = False
  28. _DEFAULT_CONTENT_INDEXED = False
  29. _DEFAULT_ENABLE_DIR_BROWSING = False
  30. _DEFAULT_ENABLE_DEFAULT_DOC = False
  31. is_debug_build = False
  32. for imp_ext, _, _ in imp.get_suffixes():
  33. if imp_ext == "_d.pyd":
  34. is_debug_build = True
  35. break
  36. this_dir = os.path.abspath(os.path.dirname(__file__))
  37. class FilterParameters:
  38. Name = None
  39. Description = None
  40. Path = None
  41. Server = None
  42. # Params that control if/how AddExtensionFile is called.
  43. AddExtensionFile = True
  44. AddExtensionFile_Enabled = True
  45. AddExtensionFile_GroupID = None # defaults to Name
  46. AddExtensionFile_CanDelete = True
  47. AddExtensionFile_Description = None # defaults to Description.
  48. def __init__(self, **kw):
  49. self.__dict__.update(kw)
  50. class VirtualDirParameters:
  51. Name = None # Must be provided.
  52. Description = None # defaults to Name
  53. AppProtection = _DEFAULT_PROTECTION
  54. Headers = _DEFAULT_HEADERS
  55. Path = None # defaults to WWW root.
  56. AccessExecute = _DEFAULT_ACCESS_EXECUTE
  57. AccessRead = _DEFAULT_ACCESS_READ
  58. AccessWrite = _DEFAULT_ACCESS_WRITE
  59. AccessScript = _DEFAULT_ACCESS_SCRIPT
  60. ContentIndexed = _DEFAULT_CONTENT_INDEXED
  61. EnableDirBrowsing = _DEFAULT_ENABLE_DIR_BROWSING
  62. EnableDefaultDoc = _DEFAULT_ENABLE_DEFAULT_DOC
  63. DefaultDoc = None # Only set in IIS if not None
  64. ScriptMaps = []
  65. ScriptMapUpdate = "end" # can be 'start', 'end', 'replace'
  66. Server = None
  67. def __init__(self, **kw):
  68. self.__dict__.update(kw)
  69. class ScriptMapParams:
  70. Extension = None
  71. Module = None
  72. Flags = 5
  73. Verbs = ""
  74. # Params that control if/how AddExtensionFile is called.
  75. AddExtensionFile = True
  76. AddExtensionFile_Enabled = True
  77. AddExtensionFile_GroupID = None # defaults to Name
  78. AddExtensionFile_CanDelete = True
  79. AddExtensionFile_Description = None # defaults to Description.
  80. def __init__(self, **kw):
  81. self.__dict__.update(kw)
  82. class ISAPIParameters:
  83. ServerName = _DEFAULT_SERVER_NAME
  84. # Description = None
  85. Filters = []
  86. VirtualDirs = []
  87. def __init__(self, **kw):
  88. self.__dict__.update(kw)
  89. verbose = 1 # The level - 0 is quiet.
  90. def log(level, what):
  91. if verbose >= level:
  92. print what
  93. # Convert an ADSI COM exception to the Win32 error code embedded in it.
  94. def _GetWin32ErrorCode(com_exc):
  95. hr, msg, exc, narg = com_exc
  96. # If we have more details in the 'exc' struct, use it.
  97. if exc:
  98. hr = exc[-1]
  99. if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32:
  100. raise
  101. return winerror.SCODE_CODE(hr)
  102. class InstallationError(Exception): pass
  103. class ItemNotFound(InstallationError): pass
  104. class ConfigurationError(InstallationError): pass
  105. def FindPath(options, server, name):
  106. if name.lower().startswith("iis://"):
  107. return name
  108. else:
  109. if name and name[0] != "/":
  110. name = "/"+name
  111. return FindWebServer(options, server)+"/ROOT"+name
  112. def FindWebServer(options, server_desc):
  113. # command-line options get first go, and are assumed in 'mbcs' encoding
  114. # (well, assumed MBCS by the time they got to sys.argv...)
  115. if options.server:
  116. server_desc = options.server
  117. # but someone may have explicitly already set unicode...
  118. if type(server_desc) != unicode:
  119. server_desc = server_desc.decode("mbcs")
  120. # If the config passed by the caller doesn't specify one, use the default
  121. if not server_desc:
  122. server = _IIS_OBJECT+"/1"
  123. else:
  124. # Assume the user has passed either the instance_id or "server
  125. # description" - loop over all objects until we find it.
  126. ob = GetObject(_IIS_OBJECT)
  127. look = server_desc.lower().strip()
  128. for sub in ob:
  129. # ID is generally a number, but no need to assume that.
  130. this_id = getattr(sub, "Name", "").lower().strip()
  131. this_comment = getattr(sub, "ServerComment", "").lower().strip()
  132. if this_id == look or this_comment == look:
  133. server = sub.AdsPath
  134. break
  135. else:
  136. raise ItemNotFound, \
  137. "No web sites match the description '%s'" % (server_desc,)
  138. # Check it is good.
  139. try:
  140. GetObject(server)
  141. except pythoncom.com_error, details:
  142. hr, msg, exc, arg_err = details
  143. if exc and exc[2]:
  144. msg = exc[2]
  145. raise ItemNotFound, \
  146. "WebServer %s: %s" % (server, msg)
  147. return server
  148. def CreateDirectory(params, options):
  149. _CallHook(params, "PreInstall", options)
  150. if not params.Name:
  151. raise ConfigurationError, "No Name param"
  152. slash = params.Name.rfind("/")
  153. if slash >= 0:
  154. parent = params.Name[:slash]
  155. name = params.Name[slash+1:]
  156. else:
  157. parent = ""
  158. name = params.Name
  159. webDir = GetObject(FindPath(options, params.Server, parent))
  160. if parent:
  161. # Note that the directory won't be visible in the IIS UI
  162. # unless the directory exists on the filesystem.
  163. keyType = _IIS_WEBDIR
  164. else:
  165. keyType = _IIS_WEBVIRTUALDIR
  166. # We used to go to lengths to keep an existing virtual directory
  167. # in place. However, in some cases the existing directories got
  168. # into a bad state, and an update failed to get them working.
  169. # So we nuke it first. If this is a problem, we could consider adding
  170. # a --keep-existing option.
  171. try:
  172. # Also seen the Class change to a generic IISObject - so nuke
  173. # *any* existing object, regardless of Class
  174. existing = GetObject(FindPath(options, params.Server, params.Name))
  175. webDir.Delete(existing.Class, existing.Name)
  176. log(2, "Deleted old directory '%s'" % (params.Name,))
  177. except pythoncom.com_error:
  178. pass
  179. newDir = webDir.Create(keyType, name)
  180. log(2, "Creating new directory '%s'..." % (params.Name,))
  181. friendly = params.Description or params.Name
  182. newDir.AppFriendlyName = friendly
  183. try:
  184. path = params.Path or webDir.Path
  185. newDir.Path = path
  186. except AttributeError:
  187. pass
  188. newDir.AppCreate2(params.AppProtection)
  189. newDir.HttpCustomHeaders = params.Headers
  190. log(2, "Setting directory options...")
  191. newDir.AccessExecute = params.AccessExecute
  192. newDir.AccessRead = params.AccessRead
  193. newDir.AccessWrite = params.AccessWrite
  194. newDir.AccessScript = params.AccessScript
  195. newDir.ContentIndexed = params.ContentIndexed
  196. newDir.EnableDirBrowsing = params.EnableDirBrowsing
  197. newDir.EnableDefaultDoc = params.EnableDefaultDoc
  198. if params.DefaultDoc is not None:
  199. newDir.DefaultDoc = params.DefaultDoc
  200. newDir.SetInfo()
  201. smp_items = []
  202. for smp in params.ScriptMaps:
  203. item = "%s,%s,%s" % (smp.Extension, smp.Module, smp.Flags)
  204. # IIS gets upset if there is a trailing verb comma, but no verbs
  205. if smp.Verbs:
  206. item += "," + smp.Verbs
  207. smp_items.append(item)
  208. if params.ScriptMapUpdate == "replace":
  209. newDir.ScriptMaps = smp_items
  210. elif params.ScriptMapUpdate == "end":
  211. for item in smp_items:
  212. if item not in newDir.ScriptMaps:
  213. newDir.ScriptMaps = newDir.ScriptMaps + (item,)
  214. elif params.ScriptMapUpdate == "start":
  215. for item in smp_items:
  216. if item not in newDir.ScriptMaps:
  217. newDir.ScriptMaps = (item,) + newDir.ScriptMaps
  218. else:
  219. raise ConfigurationError, \
  220. "Unknown ScriptMapUpdate option '%s'" % (params.ScriptMapUpdate,)
  221. newDir.SetInfo()
  222. _CallHook(params, "PostInstall", options, newDir)
  223. log(1, "Configured Virtual Directory: %s" % (params.Name,))
  224. return newDir
  225. def CreateISAPIFilter(filterParams, options):
  226. server = FindWebServer(options, filterParams.Server)
  227. _CallHook(filterParams, "PreInstall", options)
  228. try:
  229. filters = GetObject(server+"/Filters")
  230. except pythoncom.com_error, (hr, msg, exc, arg):
  231. # Brand new sites don't have the '/Filters' collection - create it.
  232. # Any errors other than 'not found' we shouldn't ignore.
  233. if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32 or \
  234. winerror.HRESULT_CODE(hr) != winerror.ERROR_PATH_NOT_FOUND:
  235. raise
  236. server_ob = GetObject(server)
  237. filters = server_ob.Create(_IIS_FILTERS, "Filters")
  238. filters.FilterLoadOrder = ""
  239. filters.SetInfo()
  240. # As for VirtualDir, delete an existing one.
  241. try:
  242. filters.Delete(_IIS_FILTER, filterParams.Name)
  243. log(2, "Deleted old filter '%s'" % (filterParams.Name,))
  244. except pythoncom.com_error:
  245. pass
  246. newFilter = filters.Create(_IIS_FILTER, filterParams.Name)
  247. log(2, "Created new ISAPI filter...")
  248. assert os.path.isfile(filterParams.Path)
  249. newFilter.FilterPath = filterParams.Path
  250. newFilter.FilterDescription = filterParams.Description
  251. newFilter.SetInfo()
  252. load_order = [b.strip() for b in filters.FilterLoadOrder.split(",") if b]
  253. if filterParams.Name not in load_order:
  254. load_order.append(filterParams.Name)
  255. filters.FilterLoadOrder = ",".join(load_order)
  256. filters.SetInfo()
  257. _CallHook(filterParams, "PostInstall", options, newFilter)
  258. log (1, "Configured Filter: %s" % (filterParams.Name,))
  259. return newFilter
  260. def DeleteISAPIFilter(filterParams, options):
  261. _CallHook(filterParams, "PreRemove", options)
  262. server = FindWebServer(options, filterParams.Server)
  263. ob_path = server+"/Filters"
  264. try:
  265. filters = GetObject(ob_path)
  266. except pythoncom.com_error, details:
  267. # failure to open the filters just means a totally clean IIS install
  268. # (IIS5 at least has no 'Filters' key when freshly installed).
  269. log(2, "ISAPI filter path '%s' did not exist." % (ob_path,))
  270. return
  271. try:
  272. filters.Delete(_IIS_FILTER, filterParams.Name)
  273. log(2, "Deleted ISAPI filter '%s'" % (filterParams.Name,))
  274. except pythoncom.com_error, details:
  275. rc = _GetWin32ErrorCode(details)
  276. if rc != winerror.ERROR_PATH_NOT_FOUND:
  277. raise
  278. log(2, "ISAPI filter '%s' did not exist." % (filterParams.Name,))
  279. # Remove from the load order
  280. load_order = [b.strip() for b in filters.FilterLoadOrder.split(",") if b]
  281. if filterParams.Name in load_order:
  282. load_order.remove(filterParams.Name)
  283. filters.FilterLoadOrder = ",".join(load_order)
  284. filters.SetInfo()
  285. _CallHook(filterParams, "PostRemove", options)
  286. log (1, "Deleted Filter: %s" % (filterParams.Name,))
  287. def _AddExtensionFile(module, def_groupid, def_desc, params, options):
  288. group_id = params.AddExtensionFile_GroupID or def_groupid
  289. desc = params.AddExtensionFile_Description or def_desc
  290. try:
  291. ob = GetObject(_IIS_OBJECT)
  292. ob.AddExtensionFile(module,
  293. params.AddExtensionFile_Enabled,
  294. group_id,
  295. params.AddExtensionFile_CanDelete,
  296. desc)
  297. log(2, "Added extension file '%s' (%s)" % (module, desc))
  298. except (pythoncom.com_error, AttributeError), details:
  299. # IIS5 always fails. Probably should upgrade this to
  300. # complain more loudly if IIS6 fails.
  301. log(2, "Failed to add extension file '%s': %s" % (module, details))
  302. def AddExtensionFiles(params, options):
  303. """Register the modules used by the filters/extensions as a trusted
  304. 'extension module' - required by the default IIS6 security settings."""
  305. # Add each module only once.
  306. added = {}
  307. for vd in params.VirtualDirs:
  308. for smp in vd.ScriptMaps:
  309. if not added.has_key(smp.Module) and smp.AddExtensionFile:
  310. _AddExtensionFile(smp.Module, vd.Name, vd.Description, smp,
  311. options)
  312. added[smp.Module] = True
  313. for fd in params.Filters:
  314. if not added.has_key(fd.Path) and fd.AddExtensionFile:
  315. _AddExtensionFile(fd.Path, fd.Name, fd.Description, fd, options)
  316. added[fd.Path] = True
  317. def _DeleteExtensionFileRecord(module, options):
  318. try:
  319. ob = GetObject(_IIS_OBJECT)
  320. ob.DeleteExtensionFileRecord(module)
  321. log(2, "Deleted extension file record for '%s'" % module)
  322. except (pythoncom.com_error, AttributeError), details:
  323. log(2, "Failed to remove extension file '%s': %s" % (module, details))
  324. def DeleteExtensionFileRecords(params, options):
  325. deleted = {} # only remove each .dll once.
  326. for vd in params.VirtualDirs:
  327. for smp in vd.ScriptMaps:
  328. if not deleted.has_key(smp.Module) and smp.AddExtensionFile:
  329. _DeleteExtensionFileRecord(smp.Module, options)
  330. deleted[smp.Module] = True
  331. for filter_def in params.Filters:
  332. if not deleted.has_key(filter_def.Path) and filter_def.AddExtensionFile:
  333. _DeleteExtensionFileRecord(filter_def.Path, options)
  334. deleted[filter_def.Path] = True
  335. def CheckLoaderModule(dll_name):
  336. suffix = ""
  337. if is_debug_build: suffix = "_d"
  338. template = os.path.join(this_dir,
  339. "PyISAPI_loader" + suffix + ".dll")
  340. if not os.path.isfile(template):
  341. raise ConfigurationError, \
  342. "Template loader '%s' does not exist" % (template,)
  343. # We can't do a simple "is newer" check, as the DLL is specific to the
  344. # Python version. So we check the date-time and size are identical,
  345. # and skip the copy in that case.
  346. src_stat = os.stat(template)
  347. try:
  348. dest_stat = os.stat(dll_name)
  349. except os.error:
  350. same = 0
  351. else:
  352. same = src_stat[stat.ST_SIZE]==dest_stat[stat.ST_SIZE] and \
  353. src_stat[stat.ST_MTIME]==dest_stat[stat.ST_MTIME]
  354. if not same:
  355. log(2, "Updating %s->%s" % (template, dll_name))
  356. shutil.copyfile(template, dll_name)
  357. shutil.copystat(template, dll_name)
  358. else:
  359. log(2, "%s is up to date." % (dll_name,))
  360. def _CallHook(ob, hook_name, options, *extra_args):
  361. func = getattr(ob, hook_name, None)
  362. if func is not None:
  363. args = (ob,options) + extra_args
  364. func(*args)
  365. def Install(params, options):
  366. _CallHook(params, "PreInstall", options)
  367. for vd in params.VirtualDirs:
  368. CreateDirectory(vd, options)
  369. for filter_def in params.Filters:
  370. CreateISAPIFilter(filter_def, options)
  371. AddExtensionFiles(params, options)
  372. _CallHook(params, "PostInstall", options)
  373. def Uninstall(params, options):
  374. _CallHook(params, "PreRemove", options)
  375. DeleteExtensionFileRecords(params, options)
  376. for vd in params.VirtualDirs:
  377. _CallHook(vd, "PreRemove", options)
  378. try:
  379. directory = GetObject(FindPath(options, vd.Server, vd.Name))
  380. except pythoncom.com_error, details:
  381. rc = _GetWin32ErrorCode(details)
  382. if rc != winerror.ERROR_PATH_NOT_FOUND:
  383. raise
  384. log(2, "VirtualDirectory '%s' did not exist" % vd.Name)
  385. directory = None
  386. if directory is not None:
  387. # Be robust should IIS get upset about unloading.
  388. try:
  389. directory.AppUnLoad()
  390. except:
  391. exc_val = sys.exc_info()[1]
  392. log(2, "AppUnLoad() for %s failed: %s" % (vd.Name, exc_val))
  393. # Continue trying to delete it.
  394. try:
  395. parent = GetObject(directory.Parent)
  396. parent.Delete(directory.Class, directory.Name)
  397. log (1, "Deleted Virtual Directory: %s" % (vd.Name,))
  398. except:
  399. exc_val = sys.exc_info()[1]
  400. log(1, "Failed to remove directory %s: %s" % (vd.Name, exc_val))
  401. _CallHook(vd, "PostRemove", options)
  402. for filter_def in params.Filters:
  403. DeleteISAPIFilter(filter_def, options)
  404. _CallHook(params, "PostRemove", options)
  405. # Patch up any missing module names in the params, replacing them with
  406. # the DLL name that hosts this extension/filter.
  407. def _PatchParamsModule(params, dll_name, file_must_exist = True):
  408. if file_must_exist:
  409. if not os.path.isfile(dll_name):
  410. raise ConfigurationError, "%s does not exist" % (dll_name,)
  411. # Patch up all references to the DLL.
  412. for f in params.Filters:
  413. if f.Path is None: f.Path = dll_name
  414. for d in params.VirtualDirs:
  415. for sm in d.ScriptMaps:
  416. if sm.Module is None: sm.Module = dll_name
  417. def GetLoaderModuleName(mod_name, check_module = None):
  418. # find the name of the DLL hosting us.
  419. # By default, this is "_{module_base_name}.dll"
  420. if hasattr(sys, "frozen"):
  421. # What to do? The .dll knows its name, but this is likely to be
  422. # executed via a .exe, which does not know.
  423. base, ext = os.path.splitext(mod_name)
  424. path, base = os.path.split(base)
  425. # handle the common case of 'foo.exe'/'foow.exe'
  426. if base.endswith('w'):
  427. base = base[:-1]
  428. # For py2exe, we have '_foo.dll' as the standard pyisapi loader - but
  429. # 'foo.dll' is what we use (it just delegates).
  430. # So no leading '_' on the installed name.
  431. dll_name = os.path.abspath(os.path.join(path, base + ".dll"))
  432. else:
  433. base, ext = os.path.splitext(mod_name)
  434. path, base = os.path.split(base)
  435. dll_name = os.path.abspath(os.path.join(path, "_" + base + ".dll"))
  436. # Check we actually have it.
  437. if check_module is None: check_module = not hasattr(sys, "frozen")
  438. if check_module:
  439. CheckLoaderModule(dll_name)
  440. return dll_name
  441. def InstallModule(conf_module_name, params, options):
  442. if not hasattr(sys, "frozen"):
  443. conf_module_name = os.path.abspath(conf_module_name)
  444. if not os.path.isfile(conf_module_name):
  445. raise ConfigurationError, "%s does not exist" % (conf_module_name,)
  446. loader_dll = GetLoaderModuleName(conf_module_name)
  447. _PatchParamsModule(params, loader_dll)
  448. Install(params, options)
  449. def UninstallModule(conf_module_name, params, options):
  450. loader_dll = GetLoaderModuleName(conf_module_name, False)
  451. _PatchParamsModule(params, loader_dll, False)
  452. Uninstall(params, options)
  453. standard_arguments = {
  454. "install" : "Install the extension",
  455. "remove" : "Remove the extension"
  456. }
  457. # We support 2 ways of extending our command-line/install support.
  458. # * Many of the installation items allow you to specify "PreInstall",
  459. # "PostInstall", "PreRemove" and "PostRemove" hooks
  460. # All hooks are called with the 'params' object being operated on, and
  461. # the 'optparser' options for this session (ie, the command-line options)
  462. # PostInstall for VirtualDirectories and Filters both have an additional
  463. # param - the ADSI object just created.
  464. # * You can pass your own option parser for us to use, and/or define a map
  465. # with your own custom arg handlers. It is a map of 'arg'->function.
  466. # The function is called with (options, log_fn, arg). The function's
  467. # docstring is used in the usage output.
  468. def HandleCommandLine(params, argv=None, conf_module_name = None,
  469. default_arg = "install",
  470. opt_parser = None, custom_arg_handlers = {}):
  471. """Perform installation or removal of an ISAPI filter or extension.
  472. This module handles standard command-line options and configuration
  473. information, and installs, removes or updates the configuration of an
  474. ISAPI filter or extension.
  475. You must pass your configuration information in params - all other
  476. arguments are optional, and allow you to configure the installation
  477. process.
  478. """
  479. global verbose
  480. from optparse import OptionParser
  481. argv = argv or sys.argv
  482. conf_module_name = conf_module_name or sys.argv[0]
  483. if opt_parser is None:
  484. # Build our own parser.
  485. parser = OptionParser(usage='')
  486. else:
  487. # The caller is providing their own filter, presumably with their
  488. # own options all setup.
  489. parser = opt_parser
  490. # build a usage string if we don't have one.
  491. if not parser.get_usage():
  492. all_args = standard_arguments.copy()
  493. for arg, handler in custom_arg_handlers.items():
  494. all_args[arg] = handler.__doc__
  495. arg_names = "|".join(all_args.keys())
  496. usage_string = "%prog [options] [" + arg_names + "]\n"
  497. usage_string += "commands:\n"
  498. for arg, desc in all_args.items():
  499. usage_string += " %-10s: %s" % (arg, desc) + "\n"
  500. parser.set_usage(usage_string[:-1])
  501. parser.add_option("-q", "--quiet",
  502. action="store_false", dest="verbose", default=True,
  503. help="don't print status messages to stdout")
  504. parser.add_option("-v", "--verbosity", action="count",
  505. dest="verbose", default=1,
  506. help="increase the verbosity of status messages")
  507. parser.add_option("", "--server", action="store",
  508. help="Specifies the IIS server to install/uninstall on." \
  509. " Default is '%s/1'" % (_IIS_OBJECT,))
  510. (options, args) = parser.parse_args(argv[1:])
  511. verbose = options.verbose
  512. if not args:
  513. args = [default_arg]
  514. try:
  515. for arg in args:
  516. if arg == "install":
  517. InstallModule(conf_module_name, params, options)
  518. log(1, "Installation complete.")
  519. elif arg in ["remove", "uninstall"]:
  520. UninstallModule(conf_module_name, params, options)
  521. log(1, "Uninstallation complete.")
  522. else:
  523. handler = custom_arg_handlers.get(arg, None)
  524. if handler is None:
  525. parser.error("Invalid arg '%s'" % (arg,))
  526. handler(options, log, arg)
  527. except (ItemNotFound, InstallationError), details:
  528. if options.verbose > 1:
  529. traceback.print_exc()
  530. print "%s: %s" % (details.__class__.__name__, details)