可移动设备中带日期过滤的WMI文件事件查询异常问题咨询
我来帮你拆解并解决这两个头疼的问题:
问题1:设备插入时触发所有已有文件的创建通知
这其实是WMI的正常行为——当可移动设备挂载时,WMI会一次性把设备上所有文件的实例加载到CIM_DataFile类中,所以__InstanceCreationEvent会把这些“新注册”的实例都当成刚创建的文件来触发事件。
解决思路是先捕获设备挂载的时刻,提前枚举该驱动器下的所有现有文件,然后在事件回调中排除这些已存在的文件:
- 先监听可移动设备的挂载事件(比如通过
Win32_VolumeChangeEvent),当检测到设备插入时,立即枚举该驱动器下的所有文件,把它们的完整路径存入一个集合。 - 在
__InstanceCreationEvent的回调方法里,先检查当前触发的TargetInstance是否在这个初始集合里,如果不在,才判定为真正的新创建文件。
举个伪代码示例:
// 存储设备挂载时已存在的文件路径 HashSet<string> existingFiles = new HashSet<string>(); // 设备插入时初始化现有文件列表 void OnDeviceInserted(string driveLetter) { var query = new ObjectQuery($"SELECT * FROM CIM_DataFile WHERE Drive='{driveLetter}'"); using (var searcher = new ManagementObjectSearcher(query)) { foreach (var file in searcher.Get()) { string fullPath = $"{file["Drive"]}{file["Path"]}{file["FileName"]}{file["Extension"]}"; existingFiles.Add(fullPath); } } } // 处理文件创建事件 void OnFileCreated(ManagementBaseObject instance) { var file = (ManagementObject)instance["TargetInstance"]; string fullPath = $"{file["Drive"]}{file["Path"]}{file["FileName"]}{file["Extension"]}"; if (!existingFiles.Contains(fullPath)) { // 处理真正的新文件 Console.WriteLine($"新文件创建:{fullPath}"); } }
问题2:可移动设备文件CreationDate的时区格式问题
你猜的没错,这个问题确实出在时区偏移的+***上。像FAT32这类文件系统的可移动设备,不会存储文件的时区信息,所以WMI返回的CreationDate时区部分会用+***表示未知偏移;而NTFS格式的本地磁盘会存储时区,所以能返回正确的偏移值(比如+0800)。
直接在WMI查询里用CreationDate > [带时区的DMTF时间]会因为格式不匹配失效,有两个可行的解决方法:
方法1:在查询中忽略时区部分,只比较日期时间主体
DMTF时间的格式是yyyymmddHHMMSS.ffffff+ooo,前14位是年月日时分秒,我们可以只取这部分来比较。修改你的查询语句,把TargetInstance.CreationDate > "{dmtfDate}"改成匹配前14位的形式:
string dmtfDate = ManagementDateTimeConverter.ToDmtfDateTime(markfInstallation); // 提取不带时区的前14位(比如20240520153000) string dateWithoutTz = dmtfDate.Substring(0, 14); // 修改查询,用LIKE匹配前14位大于目标值 string query = $"SELECT * From __InstanceCreationEvent WITHIN 5 Where TargetInstance ISA \"CIM_DATAFile\" And TargetInstance.Drive=\"{driveUnit.Name.Replace("\\", "")}\" And TargetInstance.CreationDate LIKE \"{dateWithoutTz}%\"";
注意:这种方法会匹配所有在目标时间之后创建的文件,不管时区,但因为可移动设备的时区本身未知,这是比较折衷的方案。
方法2:不在WMI查询里过滤,拿到实例后再处理
放弃在WMI查询中过滤CreationDate,先获取所有触发的事件,然后在回调中解析CreationDate并和目标时间比较:
void OnFileCreated(ManagementBaseObject instance) { var file = (ManagementObject)instance["TargetInstance"]; string creationDateStr = file["CreationDate"].ToString(); // 处理+***的情况:把时区替换为本地时区偏移 if (creationDateStr.EndsWith("+***")) { string localTzOffset = ManagementDateTimeConverter.ToDmtfDateTime(DateTime.Now).Substring(22, 4); creationDateStr = creationDateStr.Replace("+***", localTzOffset); } DateTime creationDate = ManagementDateTimeConverter.ToDateTime(creationDateStr); if (creationDate > markfInstallation) { // 处理符合条件的新文件 } }
这个方法更准确,因为我们把未知时区替换成了本地时区(可移动设备的文件时间通常是本地时间),然后再做时间比较。
内容的提问来源于stack exchange,提问作者andrés matínez rodríguez

