C#编写文件监控服务
最近想写一个监控自己电脑上重要文件的服务,如果我要监控的目录或者文件被访问或者被修改,服务就会在第一时间给我发送一封邮件通知我,众所周知,很多应用软件都在我们不知道的情况下默默的搜集访问我们个人电脑上的私密文件,而我们却对此却一无所知,所以十分有必要自己写一个服务来监控我们重要的隐私文件,自己写的服务当然最放心。
1. FileSystemWatcher类讲解
侦听文件系统更改通知,并在目录或目录中的文件发生更改时引发事件。
这个是微软官方的定义,文档链接:FileSystemWatcher类官方文档
1.1 常用的几个基本属性
- Path :设置要监视的目录的路径。
- IncludeSubdirectories :设置是否级联监视指定路径中的子目录。
- Filter :设置筛选字符串,用于确定在目录中监视哪些类型的文件。
- NotifyFilter :设置文件的哪些属性的变动会触发Changed事件,同时监控多个属性变动可以按“或”组合。(默认值为 NotifyFilter.LastWrite | NotifyFilter.FileName | NotifyFilter.DirectoryName 组合)
- Attributes – 文件或文件夹的属性。
- CreationTime – 文件或文件夹的创建时间。
- DirectoryName – 目录名。
- FileName – 文件名。
- LastAccess – 文件或文件夹上一次打开的日期。
- LastWrite – 上一次向文件或文件夹写入内容的日期。
- Security – 文件或文件夹的安全设置。
- Size – 文件或文件夹的大小。
- EnableRaisingEvents :设置是否开始监控。(默认为false)
1.2 常用事件
-
Changed:当更改文件和目录时发生,可以通过NotifyFilter属性设置触发该事件的需要文件更改的属性。
-
Created:当创建文件和目录时发生。
-
Deleted :删除文件或目录时发生。
-
Renamed:重命名文件或目录时发生。
-
FileSystemEventArgs 对象:
Name:获取受影响的文件或目录的名称。
注意:如果是级联监控子目录的话,该值为文件的路径,而不只是受影响的文件名。
FullPath :获取受影响的文件或目录的完全限定的路径。
ChangeType :获取受影响的文件或目录的发生的事件类型。
- All --文件或文件夹的创建、删除、更改或重命名。
- Changed --文件或文件夹的更改。更改的类型包括大小、属性、安全设置、最近写入时间和最近访问时间方面的更改。
- Created --文件或文件夹的创建。
- Deleted – 文件或文件夹的删除。
- Renamed – 文件或文件夹的重命名。
- RenamedEventArgs 对象:
- Name: 获取受影响的文件或目录的新名称。
- OldName : 获取受影响的文件或目录的旧名称。
- FullPath : 获取受影响的文件或目录的完全限定的路径。
- OldFullPath : 获取受影响的文件或目录的前一个完全限定的路径。
- ChangeType :获取受影响的文件或目录的发生的事件类型。
2. 监控服务类编写
因为要写成一个服务,这个服务既可以是windows服务,也可以是其他形式的服务,所以我们要先把它单独写成一个类,这样不管以后什么形式的服务就都可以调用了。
先配置一个配置文件,方便以后改相关的监控配置,例如以后想更换监控的文件或目录了,只需要更改配置文件,然后重启一下服务就OK了。
- 配置文件 App.config
MonitorPATH:待监控的文件或目录,LogPATH:日志文件的目录,EmailUserName:发送Email的邮箱账户名,EmailUserPwd:发送Email的邮箱账户密码,ReceiveMails:接收通知邮件的邮箱账户,可以写多个,用英文逗号隔开。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="MonitorPATH" value="E:\\" />
<add key="LogPATH" value="D:\\" />
<add key="EmailUserName" value="xxxxx@163.com" />
<add key="EmailUserPwd" value="xxxxxxx" />
<add key="ReceiveMails" value="xxxxxx@qq.com," />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>
- 监控服务类源代码
原理其实挺简单的,就是当被监控的文件被修改时,会触发一个改变事件,将通知程序写在事件中即可。
public class ServerRunner
{
// 发送邮箱的登录信息
static string userName= ConfigurationManager.AppSettings["EmailUserName"];
static string userPassword = ConfigurationManager.AppSettings["EmailUserPwd"];
static EmailLab EmailHelp = new EmailLab(userName, userPassword);
// 报警邮件接收邮箱
static List<string> ReceiveMails = ConfigurationManager.AppSettings["ReceiveMails"].Split(',').ToList<string>();
// 监控的目标目录
string destinationPath = ConfigurationManager.AppSettings["MonitorPATH"];
// 日志的根目录
string logPath = ConfigurationManager.AppSettings["LogPATH"];
//创建一个新的FileSystemWatcher并设置其属性
FileSystemWatcher watcher = new FileSystemWatcher();
public ServerRunner()
{
// 声明监控追踪日志
Trace.Listeners.Clear();
Trace.Listeners.Add(new LogListener("fileWatcherLog", logPath));
Trace.WriteLine($"{DateTime.Now}: Monitor is startting! ");
// 当接收邮件为一个的时候,去除最后一个空字符串,否则会报错。
if(ReceiveMails.Count==2 && ReceiveMails[1].Length==0)
{
ReceiveMails.RemoveAt(1);
}
watcher.Path = destinationPath;
// 监视LastAcceSS和LastWrite时间的更改以及文件或目录的重命名
watcher.NotifyFilter = NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.FileName |
NotifyFilters.DirectoryName;
watcher.Filter = "*.*";
watcher.IncludeSubdirectories = true;
// 当由FileSystemWatcher所指定的路径中的文件或目录的大小、系统属性、最后写时间、
// 最后访问时间或安全权限发生更改时,更改事件就会发生
watcher.Changed += new FileSystemEventHandler(OnChangedAsync);
// 由FileSystemWatcher所指定的路径中文件或目录被创建时,创建事件就会发生
watcher.Created += new FileSystemEventHandler(OnChangedAsync);
// 当由FileSystemWatcher所指定的路径中文件或目录被删除时,删除事件就会发生
watcher.Deleted += new FileSystemEventHandler(OnChangedAsync);
// 当由FileSystemWatcher所指定的路径中文件或目录被重命名时,重命名事件就会发生
watcher.Renamed += new RenamedEventHandler(OnRenamedAsync);
// 当 FileSystemWatcher的实例无法继续监视更改或内部缓冲区溢出时发生。
watcher.Error += new ErrorEventHandler(OnErrorAsync);
}
public void Run()
{
try
{
//开始监视
watcher.EnableRaisingEvents = true;
// 防止程序退出,暂时没想到其他好办法
while (true)
{
Thread.Sleep(1000);
}
}
catch (FormatException ex)
{
string msg = $"{DateTime.Now} Message: { ex.Message }";
Trace.WriteLine(msg, "OnStart");
string htmlStr = EmailLab.AddEmailBody(msg);
EmailHelp.SendMail("监控文件服务报警(异常)", htmlStr, ReceiveMails);
}
}
//定义事件处理程序
public void OnChangedAsync(object sender, FileSystemEventArgs e)
{
string msg = $"file: {e.FullPath} is {e.ChangeType} ";
// 指定当文件被更改、创建或删除时要做的事
Trace.WriteLine(msg);
string htmlStr = EmailLab.AddEmailBody(msg);
EmailHelp.SendMail("监控文件服务报警(文件被改变)", htmlStr, ReceiveMails);
}
public void OnRenamedAsync(object sender, RenamedEventArgs e)
{
//指定当文件被重命名时发生的动作
string msg = $"file: {e.OldFullPath} is renamed to {e.FullPath}";
Trace.WriteLine(msg);
string htmlStr = EmailLab.AddEmailBody(msg);
EmailHelp.SendMail("监控文件服务报警(文件被改名)", htmlStr, ReceiveMails);
}
public void OnErrorAsync(object sender, ErrorEventArgs e)
{
//指定当文件被重命名时发生的动作
string msg = $"{DateTime.Now }Error Message : {e.GetException().Message} ";
Trace.WriteLine(msg);
string htmlStr = EmailLab.AddEmailBody(msg);
EmailHelp.SendMail("监控文件服务报警(文件被改名)", htmlStr, ReceiveMails);
}
}
3. 监控日志类编写
监控日志就是将监控的结果写入日志中,详细教程参见 《C# 调试日志封装》
public class LogListener : TraceListener
{
/// <summary>
/// 日志路径
/// </summary>
public string _filePath { get; private set; }
/// <summary>
/// 追踪日志构造方法
/// </summary>
/// <param name="name">日志名称</param>
public LogListener(string name,string directory="")
{
if (string.IsNullOrEmpty(directory))
{
_filePath =
AppDomain.CurrentDomain.BaseDirectory +
name +
".log";
}else
{
_filePath = Path.Combine(directory, name+".log");
}
}
/// <summary>
/// 行内写日志
/// </summary>
/// <param name="message">日志行内容</param>
public override void Write(string message)
{
File.AppendAllText(_filePath, $"{ DateTime.Now.ToLongTimeString()} {message}");
}
/// <summary>
/// 另起一行,写日志
/// </summary>
/// <param name="message">日志行内容</param>
public override void WriteLine(string message)
{
File.AppendAllText(_filePath, $"{ DateTime.Now.ToLongTimeString()} " +
$"{message} " +
$"{Environment.NewLine}");
}
/// <summary>
/// 行内写日志
/// </summary>
/// <param name="message">日志行内容</param>
/// <param name="category">日志分类</param>
public override void Write(string message, string category)
{
File.AppendAllText(_filePath, $"{ DateTime.Now.ToLongTimeString()} " +
$"{message} " +
$"{category}");
}
/// <summary>
/// 另起一行,写日志
/// </summary>
/// <param name="message">日志行内容</param>
/// <param name="category">日志分类</param>
public override void WriteLine(string message, string category)
{
File.AppendAllText(_filePath, $"{ DateTime.Now.ToLongTimeString()} " +
$"{message} " +
$"{category}" +
$"{Environment.NewLine}");
}
}
4. 邮件通知工具类编写
就是写一个发送通知邮件的工具类方法,我参考了一位大哥的博客,稍微改了一下。这里需要注意一下,如果你用的是163的邮箱,建议端口设置为25,如果写其他的端口会报命令错误。
public class EmailLab
{
// 发送邮件用的邮箱账户
public string UserName { get; set; }
// 发送邮件用的邮箱密码
public string UserPassword { get; set; }
public EmailLab(string name,string pwd)
{
UserName = name;
UserPassword = pwd;
}
/// <summary>
/// 邮件发送
/// </summary>
/// <param name="subject">邮件标题</param>
/// <param name="body">邮件内容,html格式</param>
/// <param name="toMailList">收件人,支持多人</param>
/// <returns></returns>
public void SendMail(string subject, string body, List<string> toMailList)
{
try
{
//默认端口25,163邮箱建议只用这个端口
SmtpClient smtpClient = new SmtpClient(); smtpClient.Port = 25;
//指定电子邮件发送方式
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
//指定SMTP服务器
smtpClient.Host = "smtp.163.com";
//用户名和密码
smtpClient.Credentials = new NetworkCredential(UserName, UserPassword);
smtpClient.EnableSsl = true;
MailAddress fromAddress = new MailAddress(UserName);
MailMessage mailMessage = new MailMessage();
mailMessage.From = fromAddress;
toMailList.ForEach(e =>
{
mailMessage.To.Add(e);
});
mailMessage.Subject = subject;//主题
mailMessage.Body = body;//内容
mailMessage.BodyEncoding = Encoding.Default;//正文编码
mailMessage.IsBodyHtml = true;//设置为HTML格式
mailMessage.Priority = MailPriority.Normal;//优先级
smtpClient.Send(mailMessage);
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 邮件内容组html串
/// </summary>
/// <param name="messageInfo"></param>
/// <returns></returns>
public static string AddEmailBody(string messageInfo)
{
StringBuilder sbBody = new StringBuilder();
sbBody.Append($"<div style=\"text-align:left\">文件监控服务,提示邮件<br/>");
sbBody.Append($"{ DateTime.Now } 告警信息:{messageInfo}<br/>");
sbBody.Append($"请您及时处理该信息,本邮件为系统邮件,请勿回复!<br/></div>");
sbBody.Append($"<div style=\"text-align:right\">{DateTime.Now.ToString("yyyy年MM月dd日")}</div>");
return sbBody.ToString();
}
}
5. windows服务编写
简单的用Visual Studio 2019写个Windows服务即可。
public partial class FileWatcherService : ServiceBase
{
public FileWatcherService()
{
InitializeComponent();
}
static EmailLab EmailHelp = new EmailLab("xxxxxxx@163.com", "xxxxxxxx");
static List<string> ReceiveMails = new List<string>()
{
"xxxxx@qq.com"
};
protected override void OnStart(string[] args)
{
ServerRunner runner = new ServerRunner();
runner.Run();
}
protected override void OnStop()
{
Trace.WriteLine($"{DateTime.Now}: Monitor is stopping ! ");
string htmlStr = EmailLab.AddEmailBody($"{DateTime.Now} 服务被停止!");
EmailHelp.SendMail("监控文件服务报警", htmlStr, ReceiveMails);
}
}
6. 服务安装与部署
VS中,右键FileWatcherServer,添加安装程序,写个服务名和添加一些服务描述。这里注意,要将Account的值改为LocalSystem,最后写一个安装批处理脚本和卸载批处理脚本,记住脚本文件必须是ANSI或者UTF-8无BOM的编码格式,而且脚本里最好写绝对路径,不要写专用的路径符号,因为管理员执行的时候会强制改变路径为C盘,然后报错。
- 安装脚本 Install.bat
%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe D:\xxxx\FileWatcherServer\bin\Debug\FileWatcherServer.exe
Net Start FileWatcherServer
sc config FileWatcherServer start= auto
pause
- 卸载脚本 UnInstall.bat
%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe /u D:\xxxx\FileWatcherServer\bin\Debug\FileWatcherServer.exe
pause
用管理员账户执行安装脚本即可安装服务,服务启动后,随便在E盘修改一个文件,看能否收到邮件,再查看D盘根目录中日志fileWatcherLog.log里是否有文本记录。
- 服务截图
- 日志截图
- 邮件截图