第一次写Windows服务,虽说只是一个小程序,但也够我忙活了几天。本来前段时间就要写的,后来有些其他的事情,给耽搁了。在写这个程序的过程中,碰到了一些问题,现记录下来,希望对一些朋友有些帮助。
我做的这个服务是带界面的,其实服务跟界面是两个不同的项目,只是放在同一个解决方案下而已。
1、启动/停止服务
别看着好像挺简单,一两句代码就能搞定。
添加引用System.ServiceProcess.dll
- ServiceController sc = new ServiceController();
- sc.ServiceName = "服务名";
以下是启动服务和停止服务的按钮事件
- private void btnStart_Click(object sender, EventArgs e)
- {
- sc.Start();//启动
- }
- private void btnStop_Click(object sender, EventArgs e)
- {
- sc.Stop();//停止
- }
如果以为这样就可以,那可就错了。服务的启动或停止可能需要一定的时间,如果在这过程中再去点击启动或停止服务的按钮,程序将会报错。因此,我又加了一些代码,以启动服务事件为例:
- private void btnStart_Click(object sender, EventArgs e)
- {
- sc.Start();//启动
- sc.WaitForStatus(ServiceControllerStatus.Running);//等待服务达到指定状态
- }
然而,如果服务已经通过其他方式启动了,点击启动按钮,还是会错,于是还得判断一下:
- private void btnStart_Click(object sender, EventArgs e)
- {
- if(sc.Status==ServiceControllerStatus.Stopped)
- sc.Start();//启动
- sc.WaitForStatus(ServiceControllerStatus.Running);//等待服务达到指定状态
- }
可是问题还没有解决,经过调试,发现sc.Status并没有取到最新的状态,还得加句代码,最后版本如下:
- private void btnStart_Click(object sender, EventArgs e)
- {
- sc.Refresh();//刷新属性值
- if(sc.Status==ServiceControllerStatus.Stopped)
- sc.Start();//启动
- sc.WaitForStatus(ServiceControllerStatus.Running);//等待服务达到指定状态
- }
当然,最好还要加上try_catch,以免因服务登录密码错误或其他一些原因而导致服务在启动时出现异常。
2、读取config文件
如果是自身项目中的config文件,那么只需要:
- using System.Configuration;
- string _value = ConfigurationSettings.AppSettings["Key值"];
注意: ConfigurationSettings.AppSettings[""]获取的是程序初始化时的数据,如果此后config文件作了修改,需要在下次初始化时才能获取到新的值(换句话说:程序需要重新启动)。因此如果要使用新值,需要声明全局变量将新值存储起来。
但我是在界面项目读取服务项目的config文件,那就得采用其他的方法了:
- using System.Xml;
- XmlDocument xmlDoc = new XmlDocument();
- xmlDoc.Load(configPath);//configPath是config文件的路径,对于这个路径的获取,将会在后面说明
- XmlNodeList nodes = xmlDoc.GetElementsByTagName("add");
- Hashtable hash = new Hashtable();
- foreach (XmlNode node in nodes)
- {
- hash.Add(node.Attributes["key"].Value.ToString(), node.Attributes["value"].Value.ToString());
- }
- //通过hash["Key值"].ToString()就能获取到某一个key所对应的value了
3、修改config配置文件指定key的value
与第2点类似,若是自身项目的config文件,只需要 ConfigurationSettings.AppSettings.Set(key,value)方法就可以搞定的。
但我不得不用读写XML文件来做,具体代码如下:
- XmlDocument xmlDoc=new XmlDocument();
- xmlDoc.Load(configPath);//configPath为config文件的路径
- XmlNode xmlNode=xmlDoc.SelectSingleNode("configuration/appSettings/add[@key="+_key+"]");//_key为需要修改其value的key值
- xmlNode.Attributes["value"].InnerText=_value;//_value为新值
- xmlDoc.Save(configPath);
4、获取服务的文件路径
个人觉得通过读取注册表的相应项的方法不错,具体实现代码如下:
- using Microsoft.Win32;
- RegistryKey rk = Registry.LocalMachine;
- RegistryKey rkSub = rk.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\服务名");
- string servicePath = rkSub.GetValue("ImagePath").ToString();
- //那么我们就可以轻易地得到第2点所需要的config文件路径
- string configPath = servicePath + ".config";
5、添加服务的描述
本来以为要给某个服务设置“描述”信息应该就像是给服务设置名称一样简单,结果找来找去,竟没有发现可以设置的地方。
搜索了一下,发现以下这个方法不错(保留代码的注释):
把以下的代码插入到ProjectInstaller 类的代码中就可以了。
代码出处:http://www.codeproject.com/dotnet/dotnetscmdescription.asp
- //This code should be inserted into your ProjectInstaller class code
- public override void Install(IDictionary stateServer)
- {
- Microsoft.Win32.RegistryKey system,
- //HKEY_LOCAL_MACHINE\Services\CurrentControlSet
- currentControlSet,
- //...\Services
- services,
- //...\<Service Name>
- service,
- //...\Parameters - this is where you can put service-specific configuration
- config;
- try
- {
- //Let the project installer do its job
- base.Install(stateServer);
- //Open the HKEY_LOCAL_MACHINE\SYSTEM key
- system = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("System");
- //Open CurrentControlSet
- currentControlSet = system.OpenSubKey("CurrentControlSet");
- //Go to the services key
- services = currentControlSet.OpenSubKey("Services");
- //Open the key for your service, and allow writing
- service = services.OpenSubKey(this.BakServiceInstaller.ServiceName, true);
- //Add your services description as a REG_SZ value named "Description"
- service.SetValue("Description", "你的描述写在这里!");
- //(Optional) Add some custom information your service will use...
- config = service.CreateSubKey("Parameters");
- }
- catch(Exception e)
- {
- Console.WriteLine("An exception was thrown during service installation:\n" + e.ToString());
- }
- }
- public override void Uninstall(IDictionary stateServer)
- {
- Microsoft.Win32.RegistryKey system,
- currentControlSet,
- services,
- service;
- try
- {
- //Drill down to the service key and open it with write permission
- system = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("System");
- currentControlSet = system.OpenSubKey("CurrentControlSet");
- services = currentControlSet.OpenSubKey("Services");
- service = services.OpenSubKey(this.BakServiceInstaller.ServiceName, true);
- //Delete any keys you created during installation (or that your service created)
- service.DeleteSubKeyTree("Parameters");
- //...
- }
- catch(Exception e)
- {
- Console.WriteLine("Exception encountered while uninstalling service:\n" + e.ToString());
- }
- finally
- {
- //Let the project installer do its job
- base.Uninstall(stateServer);
- }
- }
6、打包Windows服务(以下打包方法来自互联网)
A、首先要添加一个安装项目:
在“文件”菜单上点击“添加项目”,然后选择“新建项目”。
在“项目类型”窗格中选择“安装和部署项目”文件夹。
在“模板”窗格中选择“安装项目”。给安装项目命名(比如默认的Setup1)。
这样,安装项目就添加到解决方案了。
B、向安装项目添加服务文件
在解决方案资源管理器中,右键点击安装项目(Setup1),指向“添加”,然后选择“项目输出”。
在“添加项目输出组”对话框,在“项目”下拉框中选择需要打包的服务项目(在没有多个项目的情况下,无需选择)
在列表框中,选择“主输出”,然后单击“确定”。
这样就能把服务的主输出添加到安装项目中。
C、添加自定义操作以安装服务文件
在解决方案资源管理器中右击安装项目,指向“视图”,然后选择“自定义操作”,出现自定义操作编辑器。
在“自定义操作”编辑器中右击“自定义操作”节点,然后选择“添加自定义操作”。出现“选择项目中的项”对话框。
在列表框中双击“应用程序文件夹”将其打开,选择“主输出来自XXX(活动)”,然后单击“确定”。
主输出被添加到自定义操作的所有四个节点,即“安装”、“提交”、“回滚”和“卸载”。
D、生成安装包
在解决方案资源管理器中,右击安装项目(Setup1)并选择“生成”。