Python Windows 服务问题 - 使用调试参数有效,但作为服务启动时无效?
希望这里有人能帮我解决我的问题 :D
我正在用Python创建一个Windows XP服务,目的是监控和修复一些特定的Windows、应用程序和服务设置,目前我主要关注的是默认的DCOM设置。
我的想法是把默认配置备份到另一个注册表键里,以便参考。每30分钟(现在测试时是每30秒)我希望这个服务能从注册表中查询当前的Windows默认DCOM设置,并将结果与默认配置进行比较。如果发现不一致,服务就会用自定义的配置设置替换当前的Windows设置。
我已经创建并测试了一个类来处理注册表的检查和修复,到目前为止运行得很顺利……直到我把它编译成exe文件并作为服务运行。
这个服务本身启动得很好,似乎每30秒循环一次,但我用来处理注册表检查和修复的模块似乎没有按预期运行。
我创建了一个日志文件,并获得了以下错误信息:
追踪信息(最近的调用在前):
文件 "DCOMMon.pyc",第52行,在 RepairDCOM
文件 "DCOMMon.pyc",第97行,在 GetDefaultDCOM
文件 "pywmi.pyc",第396行,在 call
文件 "pywmi.pyc",第189行,在 handle_com_error
x_wmi: -0x7ffdfff7 - 发生异常。
错误在:SWbemObjectEx
-0x7ffbfe10 -
当我停止服务并手动运行exe,指定调试参数:DCOMMon.exe debug时,服务启动并运行得很好,执行所有预期的任务。我能看到的唯一不同之处是,服务以SYSTEM用户的身份启动进程,而不是当前登录的用户,这让我猜测(只是猜测)可能是SYSTEM用户的某种权限或策略问题?我也尝试过以其他用户身份运行服务,但没有发现任何不同。
我还考虑过将wmi服务添加到我的服务的依赖项中,但老实说,我不知道那会有什么效果 :P 这是我第一次尝试在Python中创建Windows服务,而没有使用像srvany.exe这样的工具。
我昨晚和今天大部分时间都在谷歌上搜索有关py2exe和wmi兼容性的信息,但到目前为止找到的建议都没有解决上述问题。
任何建议都将不胜感激。
PS:别因为我糟糕的日志记录而讨厌我,我是从其他脚本中复制粘贴的日志记录器,没做适当的修改,可能会重复每一行 :P。日志文件可以在这里找到:“%WINDIR%\system32\DCOMMon.log”
更新
我尝试将这个项目拆分成两个exe文件,而不是一个。让服务调用另一个exe来运行wmi注册表部分。同样,当使用debug参数运行时,它运行得很好,但当我作为服务启动时,它记录了相同的错误信息。越来越多地,这看起来像是权限问题,而不是程序问题 :(
更新
DCOMMon.py - 需要pywin32,wmi(重命名为pywmi),
# DCOMMon.py
import win32api, win32service, win32serviceutil, win32event, win32evtlogutil, win32traceutil
import logging, logging.handlers, os, re, sys, thread, time, traceback, pywmi # pywmi == wmi module renamed as suggested in online post
import _winreg as reg
DCOM_DEFAULT_CONFIGURATION = ["EnableDCOM", "EnableRemoteConnect", "LegacyAuthenticationLevel", "LegacyImpersonationLevel", "DefaultAccessPermission",
"DefaultLaunchPermission", "MachineAccessRestriction", "MachineLaunchRestriction"]
DCOM_DEFAULT_ACCESS_PERMISSION = [1, 0, 4, 128, 92, 0, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 2, 0, 72, 0, 3, 0, 0, 0, 0, 0, 24, 0, 7, 0, 0, 0, 1, 2,
0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0, 0, 0, 20, 0, 7, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 7, 0, 0, 0, 0, 0, 20, 0, 7,
0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0, 1, 2, 0, 0, 0, 0, 0, 5, 32,
0, 0, 0, 32, 2, 0, 0]
DCOM_DEFAULT_LAUNCH_PERMISSION = [1, 0, 4, 128, 132, 0, 0, 0, 148, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 2, 0, 112, 0, 5, 0, 0, 0, 0, 0, 24, 0, 31, 0, 0, 0, 1,
2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0, 0, 0, 20, 0, 31, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 7, 0, 0, 0, 0, 0, 20, 0,
31, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 20, 0, 31, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 4, 0, 0, 0, 0, 0, 20, 0,
31, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0, 1, 2, 0, 0, 0, 0, 0, 5,
32, 0, 0, 0, 32, 2, 0, 0]
DCOM_MACHINE_ACCESS_RESTRICTION = [1, 0, 4, 128, 68, 0, 0, 0, 84, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 2, 0, 48, 0, 2, 0, 0, 0, 0, 0, 20, 0, 3, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 5, 7, 0, 0, 0, 0, 0, 20, 0, 7, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 5, 32, 0,
0, 0, 32, 2, 0, 0, 1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
DCOM_MACHINE_LAUNCH_RESTRICTION = [1, 0, 4, 128, 72, 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 2, 0, 52, 0, 2, 0, 0, 0, 0, 0, 24, 0, 31, 0, 0, 0, 1,
2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0, 0, 0, 20, 0, 31, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 2, 0, 0,
0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0, 1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
COMPUTER = os.environ["COMPUTERNAME"]
REGISTRY = pywmi.WMI(COMPUTER, namespace="root/default").StdRegProv
LOGFILE = os.getcwd() + "\\DCOMMon.log"
def Logger(title, filename):
logger = logging.getLogger(title)
logger.setLevel(logging.DEBUG)
handler = logging.handlers.RotatingFileHandler(filename, maxBytes=0, backupCount=0)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def LogIt(filename=LOGFILE):
#try:
# if os.path.exists(filename):
# os.remove(filename)
#except:
# pass
log = Logger("DCOMMon", filename)
tb = str(traceback.format_exc()).split("\n")
log.error("")
for i, a in enumerate(tb):
if a.strip() != "":
log.error(a)
class Monitor:
def RepairDCOM(self):
try:
repaired = {}
dict1 = self.GetDefaultDCOM()
dict2 = self.GetCurrentDCOM()
compared = self.CompareDCOM(dict1, dict2)
for dobj in DCOM_DEFAULT_CONFIGURATION:
try:
compared[dobj]
if dobj == "LegacyAuthenticationLevel" or dobj == "LegacyImpersonationLevel":
REGISTRY.SetDWORDValue(hDefKey=reg.HKEY_LOCAL_MACHINE, sSubKeyName="SOFTWARE\\Microsoft\\Ole", sValueName=dobj, uValue=dict1[dobj])
elif dobj == "DefaultAccessPermission" or dobj == "DefaultLaunchPermission" or \
dobj == "MachineAccessRestriction" or dobj == "MachineLaunchRestriction":
REGISTRY.SetBinaryValue(hDefKey=reg.HKEY_LOCAL_MACHINE, sSubKeyName="SOFTWARE\\Microsoft\\Ole", sValueName=dobj, uValue=dict1[dobj])
elif dobj == "EnableDCOM" or dobj == "EnableRemoteConnect":
REGISTRY.SetStringValue(hDefKey=reg.HKEY_LOCAL_MACHINE, sSubKeyName="SOFTWARE\\Microsoft\\Ole", sValueName=dobj, sValue=dict1[dobj])
except KeyError:
pass
except:
LogIt(LOGFILE)
def CompareDCOM(self, dict1, dict2):
compare = {}
for (key, value) in dict2.iteritems():
try:
if dict1[key] != value:
compare[key] = value
except KeyError:
compare[key] = value
return compare
def GetCurrentDCOM(self):
current = {}
for name in REGISTRY.EnumValues(hDefKey=reg.HKEY_LOCAL_MACHINE, sSubKeyName="SOFTWARE\\Microsoft\\Ole")[1]:
value = REGISTRY.GetStringValue(hDefKey=reg.HKEY_LOCAL_MACHINE, sSubKeyName="SOFTWARE\\Microsoft\\Ole", sValueName=str(name))[1]
if value:
current[str(name)] = str(value)
else:
value = REGISTRY.GetDWORDValue(hDefKey=reg.HKEY_LOCAL_MACHINE, sSubKeyName="SOFTWARE\\Microsoft\\Ole", sValueName=str(name))[1]
if not value:
value = REGISTRY.GetBinaryValue(hDefKey=reg.HKEY_LOCAL_MACHINE, sSubKeyName="SOFTWARE\\Microsoft\\Ole", sValueName=str(name))[1]
current[str(name)] = value
return current
def GetDefaultDCOM(self):
default = {}
# Get Default DCOM Settings
for name in REGISTRY.EnumValues(hDefKey=reg.HKEY_CURRENT_USER, sSubKeyName="Software\\DCOMMon")[1]:
value = REGISTRY.GetStringValue(hDefKey=reg.HKEY_CURRENT_USER, sSubKeyName="Software\\DCOMMon", sValueName=str(name))[1]
if value:
default[str(name)] = str(value)
else:
value = REGISTRY.GetDWORDValue(hDefKey=reg.HKEY_CURRENT_USER, sSubKeyName="Software\\DCOMMon", sValueName=str(name))[1]
if not value:
value = REGISTRY.GetBinaryValue(hDefKey=reg.HKEY_CURRENT_USER, sSubKeyName="Software\\DCOMMon", sValueName=str(name))[1]
default[str(name)] = value
return default
class DCOMMon(win32serviceutil.ServiceFramework):
_svc_name_ = "DCOMMon"
_svc_display_name_ = "DCOM Monitoring Service"
_svc_description_ = "DCOM Monitoring Service"
_svc_deps_ = ["EventLog"]
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
self.isAlive = True
def SvcDoRun(self):
import servicemanager
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ': DCOM Monitoring Service - Service Started'))
self.timeout=30000 # In milliseconds
while self.isAlive:
rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout)
if rc == win32event.WAIT_OBJECT_0:
break
else:
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ': DCOM Monitoring Service - Examining DCOM Configuration'))
Monitor().RepairDCOM()
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STOPPED,
(self._svc_name_, ': DCOM Monitoring Service - Service Stopped'))
return
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
LOG.close()
self.isAlive = False
return
#def ctrlHandler(ctrlType):
# return True
if __name__ == '__main__':
# win32api.SetConsoleCtrlHandler(ctrlHandler, True)
#print Monitor().RepairDCOM()
win32serviceutil.HandleCommandLine(DCOMMon)
DCOMMon_setup.py - 需要py2exe(自执行,不需要py2exe参数)
# DCOMMon_setup.py (self executable, no need for py2exe arg)
# Usage:
# DCOMMon.exe install
# DCOMMon.exe start
# DCOMMon.exe stop
# DCOMMon.exe remove
# DCOMMon.exe debug
# you can see output of this program running python site-packages\win32\lib\win32traceutil
try:
# (snippet I found somewhere, searching something??)
# if this doesn't work, try import modulefinder
import py2exe.mf as modulefinder
import win32com, sys
for p in win32com.__path__[1:]:
modulefinder.AddPackagePath("win32com", p)
for extra in ["win32com.shell"]: #,"win32com.mapi"
__import__(extra)
m = sys.modules[extra]
for p in m.__path__[1:]:
modulefinder.AddPackagePath(extra, p)
except ImportError:
print "NOT FOUND"
from distutils.core import setup
import py2exe, sys
if len(sys.argv) == 1:
sys.argv.append("py2exe")
#sys.argv.append("-q")
class Target:
def __init__(self, **kw):
self.__dict__.update(kw)
# for the versioninfo resources
self.version = "1.0.0.1"
self.language = "English (Canada)"
self.company_name = "Whoever"
self.copyright = "Nobody"
self.name = "Nobody Home"
myservice = Target(
description = 'DCOM Monitoring Service',
modules = ['DCOMMon'],
cmdline_style='pywin32'
#dest_base = 'DCOMMon'
)
setup(
options = {"py2exe": {"compressed": 1, "bundle_files": 1, "ascii": 1, "packages": ["encodings"]} },
console=["DCOMMon.py"],
zipfile = None,
service=[myservice]
)
2 个回答
我不是这方面的专家,但我想分享一些我的看法:
这篇文章告诉我,你可能需要以拥有目标系统权限的用户身份登录。
不过,我觉得这个要求有点过了。你有没有试过在命令行中编译你的脚本,同时以管理员身份运行命令提示符?这样可以解锁所有权限(在Windows Vista和Windows 7中,你可以右键点击开始菜单中的命令提示符图标,然后选择“以管理员身份运行”)。
希望这对你有帮助。
我想我该承认我有点傻了.... :P
结果发现这并不是Python的问题,也不是py2exe的问题,更不是WMI的问题。 :(
这其实只是一个简单的权限问题。简单到我差不多一个月都没注意到。 :(
一般来说,如果你想创建一个服务来调用特定的注册表键以获取(在这个情况下)默认的配置设置....
也许... 也许吧....
你应该把默认的键放在"HKEY_LOCAL_MACHINE"里,而不是"HKEY_CURRENT_USER"?? :P
是的,就是这么简单...
我应该记得这个规则,因为我之前做过的项目里也有类似的情况。当你以本地系统账户运行时,简单来说,是没有"HKEY_CURRENT_USER"这个子键可以访问的。如果你真的需要访问某个特定用户的"HKEY_CURRENT_USER"子键,我想唯一的方法就是模拟这个用户。不过幸运的是,我现在做的事情并不需要这样。
下面这个链接帮我想起了我需要的东西,解决了这个问题:
http://msdn.microsoft.com/en-us/library/ms684190%28VS.85%29.aspx
总之,我把所有的默认值都迁移到了"HKEY_LOCAL_MACHINE\SOFTWARE"子键下,现在我的服务运行得非常好。 :D
谢谢大家的帮助,这个问题解决了;)