【python】使用python寫windows服務


 

背景

    運維windows服務器的同學都知道,windows服務器進行批量管理的時候非常麻煩,沒有比較順手的工具,雖然saltstack和ansible都能夠支持windows操作,但是使用起來總感覺不太舒服,比如ansible使用的winrm進行遠程操作,需要提前在windows上進行設置,並且要求操作系統版本和powershell版本,根據個人使用經驗,經常存在鏈接不上,連接慢,或者推送失敗的問題,較高的失敗率對於自動化運維體系的建立不利,所以與其受制於人,不如自己動手寫一個agent,完全自己控制。

技術平台

    服務器:windows服務器,測試過win2008r2、win2012、win7和win10.其他版本不知道怎么樣。

    開發語言:python

    python模塊:pywin32(python中的windows庫),pyinstaller(用於將py文件生成exe程序),flask(web服務器)。以上三個模塊都可以通過pip安裝。以及一些python內置模塊

    優勢:完全自己控制,根據自己需要開發功能。服務器端不需要安裝任何其他依賴(不需要安裝python環境,exe自帶運行環境)

流程結構

代碼實現

agent的實現代碼如下,代碼有詳細的注釋,就不解釋了。

  1 # -*- coding:utf-8 -*-
  2 import win32serviceutil
  3 import win32service
  4 import win32event
  5 from flask import Flask
  6 from flask import request
  7 import sys
  8 import os
  9 import zipfile
 10 import requests
 11 import shutil
 12 import re
 13 import time
 14 #設置編碼
 15 reload(sys)
 16 sys.setdefaultencoding('utf-8')
 17 ################################################################
 18 ##########自定義函數區##########################################
 19 ################################################################
 20 #下載文件函數
 21 def get_url_file(url):
 22     #下載到臨時目錄,如果臨時目錄不存在就創建一個
 23     download_path = "D:\\tmp\\%s" % (time.time())
 24     if os.path.exists(download_path):
 25         pass
 26     else:
 27         os.makedirs(download_path)
 28     r = requests.get(url, stream=True)
 29     #解析文件名,如url為:http://www.ftp.com/test.zip那么文件名就是test.zip,並下載和寫入文件
 30     filename = "%s\\%s" % (download_path, url.split('/')[-1])
 31     with open(filename, "wb") as pdf:
 32         for chunk in r.iter_content(chunk_size=1024):
 33             if chunk:
 34                 pdf.write(chunk)
 35         return {'filename':filename,'download_path':download_path}
 36 
 37 
 38 def config_zabbix(hostname):
 39     #復制一份C:\\zabbix\\zabbix_agentd_win.conf.bak的zabbix模板文件,並修改其中的Hostname=new為新的hostname,然后生成新的配置文件。
 40     try:
 41         open('C:\\zabbix\\zabbix_agentd_win.conf', 'w').write(re.sub(r'Hostname=new', 'Hostname=%s' % hostname, open('C:\\zabbix\\zabbix_agentd_win.conf.bak').read()))
 42     except:
 43         return "error"
 44     # 重啟zabbix agent服務
 45     service_list = os.popen('net start').read()
 46     if service_list.find('Zabbix Agent') == -1:
 47         try:
 48             os.system('net start "Zabbix Agent"')
 49         except:
 50             return "error"
 51     else:
 52         try:
 53             os.system('net stop "Zabbix Agent')
 54         except:
 55             return "error"
 56         try:
 57             os.system('net start "Zabbix Agent')
 58         except:
 59             return "error"
 60         return "ok"
 61 
 62 
 63 # 加壓縮文件函數
 64 def un_zip(file_name,dest_path):
 65     zip_file = zipfile.ZipFile(file_name)
 66     file_pre = file_name.split('\\')[-1].split('.')[0]
 67     for names in zip_file.namelist():
 68         zip_file.extract(names, dest_path)
 69     zip_file.close()
 70     return (dest_path + '\\' + file_pre)
 71 ###################################################################
 72 #############自定義函數區結束######################################
 73 ###################################################################
 74 
 75 #windows服務中顯示的名字
 76 class zlsService(win32serviceutil.ServiceFramework):
 77     _svc_name_ = 'zls_agent' ###可以根據自己喜好修改
 78     _svc_display_name_ = 'zls_agent'  ###可以根據自己喜好修改
 79     _svc_description_ = 'zls_agent'  ###可以根據自己喜好修改
 80 
 81 
 82     def __init__(self,args):
 83         win32serviceutil.ServiceFramework.__init__(self,args)
 84         self.stop_event = win32event.CreateEvent(None, 0, 0, None)
 85         self.run = True
 86 
 87     def SvcDoRun(self):
 88         app = Flask(__name__)
 89         ##############################################################
 90         #########flask路由設置區,自定義功能也放在這里################
 91         ##############################################################
 92         #推送文件服務,比如傳入一個url和目標地址,裝有本agent的客戶端就會下載這個url並把文件放在指定位置
 93         @app.route('/transferfile', methods=['GET', 'POST'])
 94         def transferfile():
 95             if request.method == "GET":
 96                 url = request.args.get('url')
 97                 dest = request.args.get('dest')
 98                 try:
 99                     down_data = get_url_file(url)
100                     filename = down_data['filename']
101                     download_path = down_data['download_path']
102                 except BaseException, e:
103                     return "download url file error : %s" % e
104                 # 加壓縮文件
105                 try:
106                     filepath = un_zip(filename,download_path)
107                 except BaseException, e:
108                     return "un zip error : %s" % e
109                 # 刪除壓縮文件
110                 try:
111                     os.remove(filename)
112                 except:
113                     pass
114                 # 復制文件內容到指定目錄
115                 try:
116                     shutil.copytree('%s' % filepath.replace('\\', '\\\\'), '%s' % dest)
117                 except BaseException, e:
118                     return "copy file error: %s" % e
119                 # d刪除解壓縮文件夾
120                 try:
121                     shutil.rmtree(filepath.replace('\\', '\\\\'))
122                 except:
123                     pass
124                 return "ok"
125         #修改配置文件路由,用來遠程修改zabbix配置文件,比如curl http://server_ip:50000/zabbix?hostname="zabbix_agent"就會把位於服務器上的zabbix_agentd配置文件中的Hostname修改為Hostname=zabbix_agent。
126         @app.route('/zabbix', methods=['GET', 'POST'])
127         def config_zabbix_func():
128             if request.method == "GET":
129                 hostname = request.args.get('hostname')
130                 result = config_zabbix(hostname)
131                 return result
132         #測試路由,用來測試agent是否正在運行,返回ok表示正在運行
133         @app.route('/', methods=['GET', 'POST'])
134         def test():
135             if request.method == "GET":
136                 return "ok"
137         #使用flask自帶的web服務器,監聽本地所有地址的50000端口
138         ##############################################################
139         #############自定義功能區結束#################################
140         ##############################################################
141         app.run(host='0.0.0.0', port=50000)
142     def SvcStop(self):
143         self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
144         win32event.SetEvent(self.stop_event)
145         self.ReportServiceStatus(win32service.SERVICE_STOPPED)
146         self.run = False
147 
148 if __name__ == '__main__':
149     import sys
150     import servicemanager
151     if len(sys.argv) == 1:
152         try:
153             evtsrc_dll = os.path.abspath(servicemanager.__file__)
154             servicemanager.PrepareToHostSingle(zlsService) #如果修改過名字,名字要統一
155             servicemanager.Initialize('zlsService',evtsrc_dll) #如果修改過名字,名字要統一
156             servicemanager.StartServiceCtrlDispatcher()
157         except win32service.error as details:
158             import winerror
159             if details == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
160                 win32serviceutil.usage()
161     else:
162         win32serviceutil.HandleCommandLine(zlsService) #如果修改過名字,名字要統一
View Code

 

生成exe程序

安裝完成pyinstaller以后,打開命令行窗口

輸入:pyinstaller -F windows_service.py

說明:-F表示生成一個單一的exe文件。其他選項使用pyinstaller --help查看。

執行完成后,提示生成exe程序

找到這個程序

將程序復制到測試服務器上,安裝

以管理員身份打開cmd,中輸入 windows_servcie.exe install

提示安裝成功

啟動服務,然后設置成自動啟動

 

 測試,瀏覽器中訪問IP:50000端口,返回ok表示agent運行正常

后續:

想要卸載服務,可以使用

 

 重要說明:

以上的測試,均在測試環境中進行,不要用在正式環境,因為這個agent沒有加密通信,存在被人攻擊的危險!!!

實際使用過程中,一定要對通信進行加密,可以使用https或者在命令中加入token,對token進行認證,只有正確的token才執行命令,或者使用rsa證書加密,這里就不再舉例。

 

此agent只是給了三個示例性應用,實際用的時候,可以根據自己需要在函數區和路由定義區加入自己的程序,比如把這個agent做成監控系統,獲取本地信息穿給服務端,在此拋磚引玉,剩下的就看自己的發揮了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM