123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614 |
- """Utilities for registering objects.
- This module contains utility functions to register Python objects as
- valid COM Servers. The RegisterServer function provides all information
- necessary to allow the COM framework to respond to a request for a COM object,
- construct the necessary Python object, and dispatch COM events.
- """
- import sys
- import win32api
- import win32con
- import pythoncom
- import winerror
- import os
- CATID_PythonCOMServer = "{B3EF80D0-68E2-11D0-A689-00C04FD658FF}"
- def _set_subkeys(keyName, valueDict, base=win32con.HKEY_CLASSES_ROOT):
- hkey = win32api.RegCreateKey(base, keyName)
- try:
- for key, value in valueDict.items():
- win32api.RegSetValueEx(hkey, key, None, win32con.REG_SZ, value)
- finally:
- win32api.RegCloseKey(hkey)
- def _set_string(path, value, base=win32con.HKEY_CLASSES_ROOT):
- "Set a string value in the registry."
- win32api.RegSetValue(base,
- path,
- win32con.REG_SZ,
- value)
- def _get_string(path, base=win32con.HKEY_CLASSES_ROOT):
- "Get a string value from the registry."
- try:
- return win32api.RegQueryValue(base, path)
- except win32api.error:
- return None
- def _remove_key(path, base=win32con.HKEY_CLASSES_ROOT):
- "Remove a string from the registry."
- try:
- win32api.RegDeleteKey(base, path)
- except win32api.error as xxx_todo_changeme1:
- (code, fn, msg) = xxx_todo_changeme1.args
- if code != winerror.ERROR_FILE_NOT_FOUND:
- raise win32api.error(code, fn, msg)
- def recurse_delete_key(path, base=win32con.HKEY_CLASSES_ROOT):
- """Recursively delete registry keys.
- This is needed since you can't blast a key when subkeys exist.
- """
- try:
- h = win32api.RegOpenKey(base, path)
- except win32api.error as xxx_todo_changeme2:
- (code, fn, msg) = xxx_todo_changeme2.args
- if code != winerror.ERROR_FILE_NOT_FOUND:
- raise win32api.error(code, fn, msg)
- else:
- # parent key found and opened successfully. do some work, making sure
- # to always close the thing (error or no).
- try:
- # remove all of the subkeys
- while 1:
- try:
- subkeyname = win32api.RegEnumKey(h, 0)
- except win32api.error as xxx_todo_changeme:
- (code, fn, msg) = xxx_todo_changeme.args
- if code != winerror.ERROR_NO_MORE_ITEMS:
- raise win32api.error(code, fn, msg)
- break
- recurse_delete_key(path + '\\' + subkeyname, base)
- # remove the parent key
- _remove_key(path, base)
- finally:
- win32api.RegCloseKey(h)
- def _cat_registrar():
- return pythoncom.CoCreateInstance(
- pythoncom.CLSID_StdComponentCategoriesMgr,
- None,
- pythoncom.CLSCTX_INPROC_SERVER,
- pythoncom.IID_ICatRegister
- )
-
- def _find_localserver_exe(mustfind):
- if not sys.platform.startswith("win32"):
- return sys.executable
- if pythoncom.__file__.find("_d") < 0:
- exeBaseName = "pythonw.exe"
- else:
- exeBaseName = "pythonw_d.exe"
- # First see if in the same directory as this .EXE
- exeName = os.path.join( os.path.split(sys.executable)[0], exeBaseName )
- if not os.path.exists(exeName):
- # See if in our sys.prefix directory
- exeName = os.path.join( sys.prefix, exeBaseName )
- if not os.path.exists(exeName):
- # See if in our sys.prefix/pcbuild directory (for developers)
- if "64 bit" in sys.version:
- exeName = os.path.join( sys.prefix, "PCbuild", "amd64", exeBaseName )
- else:
- exeName = os.path.join( sys.prefix, "PCbuild", exeBaseName )
- if not os.path.exists(exeName):
- # See if the registry has some info.
- try:
- key = "SOFTWARE\\Python\\PythonCore\\%s\\InstallPath" % sys.winver
- path = win32api.RegQueryValue( win32con.HKEY_LOCAL_MACHINE, key )
- exeName = os.path.join( path, exeBaseName )
- except (AttributeError,win32api.error):
- pass
- if not os.path.exists(exeName):
- if mustfind:
- raise RuntimeError("Can not locate the program '%s'" % exeBaseName)
- return None
- return exeName
- def _find_localserver_module():
- import win32com.server
- path = win32com.server.__path__[0]
- baseName = "localserver"
- pyfile = os.path.join(path, baseName + ".py")
- try:
- os.stat(pyfile)
- except os.error:
- # See if we have a compiled extension
- if __debug__:
- ext = ".pyc"
- else:
- ext = ".pyo"
- pyfile = os.path.join(path, baseName + ext)
- try:
- os.stat(pyfile)
- except os.error:
- raise RuntimeError("Can not locate the Python module 'win32com.server.%s'" % baseName)
- return pyfile
- def RegisterServer(clsid,
- pythonInstString=None,
- desc=None,
- progID=None, verProgID=None,
- defIcon=None,
- threadingModel="both",
- policy=None,
- catids=[], other={},
- addPyComCat=None,
- dispatcher = None,
- clsctx = None,
- addnPath = None,
- ):
- """Registers a Python object as a COM Server. This enters almost all necessary
- information in the system registry, allowing COM to use the object.
- clsid -- The (unique) CLSID of the server.
- pythonInstString -- A string holding the instance name that will be created
- whenever COM requests a new object.
- desc -- The description of the COM object.
- progID -- The user name of this object (eg, Word.Document)
- verProgId -- The user name of this version's implementation (eg Word.6.Document)
- defIcon -- The default icon for the object.
- threadingModel -- The threading model this object supports.
- policy -- The policy to use when creating this object.
- catids -- A list of category ID's this object belongs in.
- other -- A dictionary of extra items to be registered.
- addPyComCat -- A flag indicating if the object should be added to the list
- of Python servers installed on the machine. If None (the default)
- then it will be registered when running from python source, but
- not registered if running in a frozen environment.
- dispatcher -- The dispatcher to use when creating this object.
- clsctx -- One of the CLSCTX_* constants.
- addnPath -- An additional path the COM framework will add to sys.path
- before attempting to create the object.
- """
- ### backwards-compat check
- ### Certain policies do not require a "class name", just the policy itself.
- if not pythonInstString and not policy:
- raise TypeError('You must specify either the Python Class or Python Policy which implement the COM object.')
- keyNameRoot = "CLSID\\%s" % str(clsid)
- _set_string(keyNameRoot, desc)
- # Also register as an "Application" so DCOM etc all see us.
- _set_string("AppID\\%s" % clsid, progID)
- # Depending on contexts requested, register the specified server type.
- # Set default clsctx.
- if not clsctx:
- clsctx = pythoncom.CLSCTX_INPROC_SERVER | pythoncom.CLSCTX_LOCAL_SERVER
- # And if we are frozen, ignore the ones that don't make sense in this
- # context.
- if pythoncom.frozen:
- assert sys.frozen, "pythoncom is frozen, but sys.frozen is not set - don't know the context!"
- if sys.frozen == "dll":
- clsctx = clsctx & pythoncom.CLSCTX_INPROC_SERVER
- else:
- clsctx = clsctx & pythoncom.CLSCTX_LOCAL_SERVER
- # Now setup based on the clsctx left over.
- if clsctx & pythoncom.CLSCTX_INPROC_SERVER:
- # get the module to use for registration.
- # nod to Gordon's installer - if sys.frozen and sys.frozendllhandle
- # exist, then we are being registered via a DLL - use this DLL as the
- # file name.
- if pythoncom.frozen:
- if hasattr(sys, "frozendllhandle"):
- dllName = win32api.GetModuleFileName(sys.frozendllhandle)
- else:
- raise RuntimeError("We appear to have a frozen DLL, but I don't know the DLL to use")
- else:
- # Normal case - running from .py file, so register pythoncom's DLL.
- # Although now we prefer a 'loader' DLL if it exists to avoid some
- # manifest issues (the 'loader' DLL has a manifest, but pythoncom does not)
- pythoncom_dir = os.path.dirname(pythoncom.__file__)
- suffix = "_d" if "_d" in pythoncom.__file__ else ""
- # Always register with the full path to the DLLs.
- loadername = os.path.join(
- pythoncom_dir,
- "pythoncomloader%d%d%s.dll" % (sys.version_info[0], sys.version_info[1], suffix)
- )
- dllName = loadername if os.path.isfile(loadername) else pythoncom.__file__
- _set_subkeys(keyNameRoot + "\\InprocServer32",
- { None : dllName,
- "ThreadingModel" : threadingModel,
- })
- else: # Remove any old InProcServer32 registrations
- _remove_key(keyNameRoot + "\\InprocServer32")
- if clsctx & pythoncom.CLSCTX_LOCAL_SERVER:
- if pythoncom.frozen:
- # If we are frozen, we write "{exe} /Automate", just
- # like "normal" .EXEs do
- exeName = win32api.GetShortPathName(sys.executable)
- command = '%s /Automate' % (exeName,)
- else:
- # Running from .py sources - we need to write
- # 'python.exe win32com\server\localserver.py {clsid}"
- exeName = _find_localserver_exe(1)
- exeName = win32api.GetShortPathName(exeName)
- pyfile = _find_localserver_module()
- command = '%s "%s" %s' % (exeName, pyfile, str(clsid))
- _set_string(keyNameRoot + '\\LocalServer32', command)
- else: # Remove any old LocalServer32 registrations
- _remove_key(keyNameRoot + "\\LocalServer32")
- if pythonInstString:
- _set_string(keyNameRoot + '\\PythonCOM', pythonInstString)
- else:
- _remove_key(keyNameRoot + '\\PythonCOM')
- if policy:
- _set_string(keyNameRoot + '\\PythonCOMPolicy', policy)
- else:
- _remove_key(keyNameRoot + '\\PythonCOMPolicy')
- if dispatcher:
- _set_string(keyNameRoot + '\\PythonCOMDispatcher', dispatcher)
- else:
- _remove_key(keyNameRoot + '\\PythonCOMDispatcher')
- if defIcon:
- _set_string(keyNameRoot + '\\DefaultIcon', defIcon)
- else:
- _remove_key(keyNameRoot + '\\DefaultIcon')
- if addnPath:
- _set_string(keyNameRoot + "\\PythonCOMPath", addnPath)
- else:
- _remove_key(keyNameRoot + "\\PythonCOMPath")
- if addPyComCat is None:
- addPyComCat = pythoncom.frozen == 0
- if addPyComCat:
- catids = catids + [ CATID_PythonCOMServer ]
- # Set up the implemented categories
- if catids:
- regCat = _cat_registrar()
- regCat.RegisterClassImplCategories(clsid, catids)
- # set up any other reg values they might have
- if other:
- for key, value in other.items():
- _set_string(keyNameRoot + '\\' + key, value)
- if progID:
- # set the progID as the most specific that was given to us
- if verProgID:
- _set_string(keyNameRoot + '\\ProgID', verProgID)
- else:
- _set_string(keyNameRoot + '\\ProgID', progID)
- # Set up the root entries - version independent.
- if desc:
- _set_string(progID, desc)
- _set_string(progID + '\\CLSID', str(clsid))
- # Set up the root entries - version dependent.
- if verProgID:
- # point from independent to the current version
- _set_string(progID + '\\CurVer', verProgID)
- # point to the version-independent one
- _set_string(keyNameRoot + '\\VersionIndependentProgID', progID)
- # set up the versioned progID
- if desc:
- _set_string(verProgID, desc)
- _set_string(verProgID + '\\CLSID', str(clsid))
- def GetUnregisterServerKeys(clsid, progID=None, verProgID=None, customKeys = None):
- """Given a server, return a list of of ("key", root), which are keys recursively
- and uncondtionally deleted at unregister or uninstall time.
- """
- # remove the main CLSID registration
- ret = [("CLSID\\%s" % str(clsid), win32con.HKEY_CLASSES_ROOT)]
- # remove the versioned ProgID registration
- if verProgID:
- ret.append((verProgID, win32con.HKEY_CLASSES_ROOT))
- # blow away the independent ProgID. we can't leave it since we just
- # torched the class.
- ### could potentially check the CLSID... ?
- if progID:
- ret.append((progID, win32con.HKEY_CLASSES_ROOT))
- # The DCOM config tool may write settings to the AppID key for our CLSID
- ret.append( ("AppID\\%s" % str(clsid), win32con.HKEY_CLASSES_ROOT) )
- # Any custom keys?
- if customKeys:
- ret = ret + customKeys
-
- return ret
-
- def UnregisterServer(clsid, progID=None, verProgID=None, customKeys = None):
- """Unregisters a Python COM server."""
- for args in GetUnregisterServerKeys(clsid, progID, verProgID, customKeys ):
- recurse_delete_key(*args)
- ### it might be nice at some point to "roll back" the independent ProgID
- ### to an earlier version if one exists, and just blowing away the
- ### specified version of the ProgID (and its corresponding CLSID)
- ### another time, though...
- ### NOTE: ATL simply blows away the above three keys without the
- ### potential checks that I describe. Assuming that defines the
- ### "standard" then we have no additional changes necessary.
- def GetRegisteredServerOption(clsid, optionName):
- """Given a CLSID for a server and option name, return the option value
- """
- keyNameRoot = "CLSID\\%s\\%s" % (str(clsid), str(optionName))
- return _get_string(keyNameRoot)
- def _get(ob, attr, default=None):
- try:
- return getattr(ob, attr)
- except AttributeError:
- pass
- # look down sub-classes
- try:
- bases = ob.__bases__
- except AttributeError:
- # ob is not a class - no probs.
- return default
- for base in bases:
- val = _get(base, attr, None)
- if val is not None:
- return val
- return default
- def RegisterClasses(*classes, **flags):
- quiet = 'quiet' in flags and flags['quiet']
- debugging = 'debug' in flags and flags['debug']
- for cls in classes:
- clsid = cls._reg_clsid_
- progID = _get(cls, '_reg_progid_')
- desc = _get(cls, '_reg_desc_', progID)
- spec = _get(cls, '_reg_class_spec_')
- verProgID = _get(cls, '_reg_verprogid_')
- defIcon = _get(cls, '_reg_icon_')
- threadingModel = _get(cls, '_reg_threading_', 'both')
- catids = _get(cls, '_reg_catids_', [])
- options = _get(cls, '_reg_options_', {})
- policySpec = _get(cls, '_reg_policy_spec_')
- clsctx = _get(cls, '_reg_clsctx_')
- tlb_filename = _get(cls, '_reg_typelib_filename_')
- # default to being a COM category only when not frozen.
- addPyComCat = not _get(cls, '_reg_disable_pycomcat_', pythoncom.frozen!=0)
- addnPath = None
- if debugging:
- # If the class has a debugging dispatcher specified, use it, otherwise
- # use our default dispatcher.
- dispatcherSpec = _get(cls, '_reg_debug_dispatcher_spec_')
- if dispatcherSpec is None:
- dispatcherSpec = "win32com.server.dispatcher.DefaultDebugDispatcher"
- # And remember the debugging flag as servers may wish to use it at runtime.
- debuggingDesc = "(for debugging)"
- options['Debugging'] = "1"
- else:
- dispatcherSpec = _get(cls, '_reg_dispatcher_spec_')
- debuggingDesc = ""
- options['Debugging'] = "0"
- if spec is None:
- moduleName = cls.__module__
- if moduleName == '__main__':
- # Use argv[0] to determine the module name.
- try:
- # Use the win32api to find the case-sensitive name
- moduleName = os.path.splitext(win32api.FindFiles(sys.argv[0])[0][8])[0]
- except (IndexError, win32api.error):
- # Can't find the script file - the user must explicitely set the _reg_... attribute.
- raise TypeError("Can't locate the script hosting the COM object - please set _reg_class_spec_ in your object")
- spec = moduleName + "." + cls.__name__
- # Frozen apps don't need their directory on sys.path
- if not pythoncom.frozen:
- scriptDir = os.path.split(sys.argv[0])[0]
- if not scriptDir: scriptDir = "."
- addnPath = win32api.GetFullPathName(scriptDir)
- RegisterServer(clsid, spec, desc, progID, verProgID, defIcon,
- threadingModel, policySpec, catids, options,
- addPyComCat, dispatcherSpec, clsctx, addnPath)
- if not quiet:
- print('Registered:', progID or spec, debuggingDesc)
- # Register the typelibrary
- if tlb_filename:
- tlb_filename = os.path.abspath(tlb_filename)
- typelib = pythoncom.LoadTypeLib(tlb_filename)
- pythoncom.RegisterTypeLib(typelib, tlb_filename)
- if not quiet:
- print('Registered type library:', tlb_filename)
- extra = flags.get('finalize_register')
- if extra:
- extra()
- def UnregisterClasses(*classes, **flags):
- quiet = 'quiet' in flags and flags['quiet']
- for cls in classes:
- clsid = cls._reg_clsid_
- progID = _get(cls, '_reg_progid_')
- verProgID = _get(cls, '_reg_verprogid_')
- customKeys = _get(cls, '_reg_remove_keys_')
- unregister_typelib = _get(cls, '_reg_typelib_filename_') is not None
- UnregisterServer(clsid, progID, verProgID, customKeys)
- if not quiet:
- print('Unregistered:', progID or str(clsid))
- if unregister_typelib:
- tlb_guid = _get(cls, "_typelib_guid_")
- if tlb_guid is None:
- # I guess I could load the typelib, but they need the GUID anyway.
- print("Have typelib filename, but no GUID - can't unregister")
- else:
- major, minor = _get(cls, "_typelib_version_", (1,0))
- lcid = _get(cls, "_typelib_lcid_", 0)
- try:
- pythoncom.UnRegisterTypeLib(tlb_guid, major, minor, lcid)
- if not quiet:
- print('Unregistered type library')
- except pythoncom.com_error:
- pass
- extra = flags.get('finalize_unregister')
- if extra:
- extra()
- #
- # Unregister info is for installers or external uninstallers.
- # The WISE installer, for example firstly registers the COM server,
- # then queries for the Unregister info, appending it to its
- # install log. Uninstalling the package will the uninstall the server
- def UnregisterInfoClasses(*classes, **flags):
- ret = []
- for cls in classes:
- clsid = cls._reg_clsid_
- progID = _get(cls, '_reg_progid_')
- verProgID = _get(cls, '_reg_verprogid_')
- customKeys = _get(cls, '_reg_remove_keys_')
- ret = ret + GetUnregisterServerKeys(clsid, progID, verProgID, customKeys)
- return ret
- # Attempt to 're-execute' our current process with elevation.
- def ReExecuteElevated(flags):
- from win32com.shell.shell import ShellExecuteEx
- from win32com.shell import shellcon
- import win32process, win32event
- import winxpgui # we've already checked we are running XP above
- import tempfile
- if not flags['quiet']:
- print("Requesting elevation and retrying...")
- new_params = " ".join(['"' + a + '"' for a in sys.argv])
- # If we aren't already in unattended mode, we want our sub-process to
- # be.
- if not flags['unattended']:
- new_params += " --unattended"
- # specifying the parent means the dialog is centered over our window,
- # which is a good usability clue.
- # hwnd is unlikely on the command-line, but flags may come from elsewhere
- hwnd = flags.get('hwnd', None)
- if hwnd is None:
- try:
- hwnd = winxpgui.GetConsoleWindow()
- except winxpgui.error:
- hwnd = 0
- # Redirect output so we give the user some clue what went wrong. This
- # also means we need to use COMSPEC. However, the "current directory"
- # appears to end up ignored - so we execute things via a temp batch file.
- tempbase = tempfile.mktemp("pycomserverreg")
- outfile = tempbase + ".out"
- batfile = tempbase + ".bat"
- # If registering from pythonwin, need to run python console instead since
- # pythonwin will just open script for editting
- current_exe = os.path.split(sys.executable)[1].lower()
- exe_to_run = None
- if current_exe == 'pythonwin.exe':
- exe_to_run = os.path.join(sys.prefix, 'python.exe')
- elif current_exe == 'pythonwin_d.exe':
- exe_to_run = os.path.join(sys.prefix, 'python_d.exe')
- if not exe_to_run or not os.path.exists(exe_to_run):
- exe_to_run = sys.executable
-
- try:
- batf = open(batfile, "w")
- try:
- cwd = os.getcwd()
- print("@echo off", file=batf)
- # nothing is 'inherited' by the elevated process, including the
- # environment. I wonder if we need to set more?
- print("set PYTHONPATH=%s" % os.environ.get('PYTHONPATH', ''), file=batf)
- # may be on a different drive - select that before attempting to CD.
- print(os.path.splitdrive(cwd)[0], file=batf)
- print('cd "%s"' % os.getcwd(), file=batf)
- print('%s %s > "%s" 2>&1' % (win32api.GetShortPathName(exe_to_run), new_params, outfile), file=batf)
- finally:
- batf.close()
- executable = os.environ.get('COMSPEC', 'cmd.exe')
- rc = ShellExecuteEx(hwnd=hwnd,
- fMask=shellcon.SEE_MASK_NOCLOSEPROCESS,
- lpVerb="runas",
- lpFile=executable,
- lpParameters='/C "%s"' % batfile,
- nShow=win32con.SW_SHOW)
- hproc = rc['hProcess']
- win32event.WaitForSingleObject(hproc, win32event.INFINITE)
- exit_code = win32process.GetExitCodeProcess(hproc)
- outf = open(outfile)
- try:
- output = outf.read()
- finally:
- outf.close()
- if exit_code:
- # Even if quiet you get to see this message.
- print("Error: registration failed (exit code %s)." % exit_code)
- # if we are quiet then the output if likely to already be nearly
- # empty, so always print it.
- print(output, end=' ')
- finally:
- for f in (outfile, batfile):
- try:
- os.unlink(f)
- except os.error as exc:
- print("Failed to remove tempfile '%s': %s" % (f, exc))
-
- def UseCommandLine(*classes, **flags):
- unregisterInfo = '--unregister_info' in sys.argv
- unregister = '--unregister' in sys.argv
- flags['quiet'] = flags.get('quiet',0) or '--quiet' in sys.argv
- flags['debug'] = flags.get('debug',0) or '--debug' in sys.argv
- flags['unattended'] = flags.get('unattended',0) or '--unattended' in sys.argv
- if unregisterInfo:
- return UnregisterInfoClasses(*classes, **flags)
- try:
- if unregister:
- UnregisterClasses(*classes, **flags)
- else:
- RegisterClasses(*classes, **flags)
- except win32api.error as exc:
- # If we are on xp+ and have "access denied", retry using
- # ShellExecuteEx with 'runas' verb to force elevation (vista) and/or
- # admin login dialog (vista/xp)
- if flags['unattended'] or exc.winerror != winerror.ERROR_ACCESS_DENIED \
- or sys.getwindowsversion()[0] < 5:
- raise
- ReExecuteElevated(flags)
- def RegisterPyComCategory():
- """ Register the Python COM Server component category.
- """
- regCat = _cat_registrar()
- regCat.RegisterCategories( [ (CATID_PythonCOMServer,
- 0x0409,
- "Python COM Server") ] )
- if not pythoncom.frozen:
- try:
- win32api.RegQueryValue(win32con.HKEY_CLASSES_ROOT,
- 'Component Categories\\%s' % CATID_PythonCOMServer)
- except win32api.error:
- try:
- RegisterPyComCategory()
- except pythoncom.error: # Error with the COM category manager - oh well.
- pass
|