我正在使用 FileSystemWatcher 来监视文件夹。但是当目录中发生某些事件时,我不知道如何搜索谁对该文件产生了影响。我尝试使用事件日志。它只是行不通。还有另一种方法吗?
5 回答
我不记得我在哪里找到了这段代码,但它是使用 pInvoke 的替代方法,我认为这对于这项任务来说有点矫枉过正。使用FileSystemWatcher
来查看文件夹,当事件触发时,您可以使用以下代码确定是哪个用户更改了文件:
private string GetSpecificFileProperties(string file, params int[] indexes)
{
string fileName = Path.GetFileName(file);
string folderName = Path.GetDirectoryName(file);
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder;
objFolder = shell.NameSpace(folderName);
StringBuilder sb = new StringBuilder();
foreach (Shell32.FolderItem2 item in objFolder.Items())
{
if (fileName == item.Name)
{
for (int i = 0; i < indexes.Length; i++)
{
sb.Append(objFolder.GetDetailsOf(item, indexes[i]) + ",");
}
break;
}
}
string result = sb.ToString().Trim();
//Protection for no results causing an exception on the `SubString` method
if (result.Length == 0)
{
return string.Empty;
}
return result.Substring(0, result.Length - 1);
}
Shell32 是对 DLL 的引用:Microsoft Shell Controls And Automation - 它是一个 COM 引用
以下是您如何调用该方法的一些示例:
string Type = GetSpecificFileProperties(filePath, 2);
string ObjectKind = GetSpecificFileProperties(filePath, 11);
DateTime CreatedDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 4));
DateTime LastModifiedDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 3));
DateTime LastAccessDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 5));
string LastUser = GetSpecificFileProperties(filePath, 10);
string ComputerName = GetSpecificFileProperties(filePath, 53);
string FileSize = GetSpecificFileProperties(filePath, 1);
或者将多个逗号分隔的属性放在一起:
string SizeTypeAndLastModDate = GetSpecificFileProperties(filePath, new int[] {1, 2, 3});
注意:此解决方案已在 Windows 7 和 Windows 10 上进行了测试。除非在使用 Shell32 获取文件扩展属性时按照异常在STA 中运行,否则它将无法正常工作,您将看到以下错误:
无法将“Shell32.ShellClass”类型的 COM 对象转换为接口类型“Shell32.IShellDispatch6”
您需要在文件系统上启用审核(并且审核仅在 NTFS 上可用)。您可以通过应用组策略或本地安全策略来做到这一点。您还必须对要监控的文件启用审核。您执行此操作的方式与修改文件的权限相同。
然后将审核事件写入安全事件日志。您必须监视此事件日志以查找您感兴趣的审计事件。执行此操作的一种方法是创建一个计划任务,该任务在记录您感兴趣的事件时启动应用程序。仅当事件没有以非常高的速率记录时,为每个事件启动一个新进程才是可行的。否则您可能会遇到性能问题。
基本上,您不想查看文件的内容或属性(shell 函数会这样做GetFileDetails
)。此外,您不想使用文件共享 API 来获取打开文件的网络用户(NetGetFileInfo
确实如此)。您想知道上次修改文件的进程的用户。Windows 通常不会记录此信息,因为它需要太多资源来执行所有文件活动。相反,您可以有选择地为对特定文件(和文件夹)执行特定操作的特定用户启用审核。
看来您需要调用 Windows API 函数才能获得所需的内容,这涉及到 PInvoke。另一个论坛上的一些人一直在研究它并想出了一些办法,你可以在这里找到他们的解决方案。但是,它似乎只适用于网络共享上的文件(而不是本地机器上的文件)。
供将来参考,这是dave4dl 发布的代码:
[DllImport("Netapi32.dll", SetLastError = true)]
static extern int NetApiBufferFree(IntPtr Buffer);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
struct FILE_INFO_3
{
public int fi3_id;
public int fi3_permission;
public int fi3_num_locks;
public string fi3_pathname;
public string fi3_username;
}
[DllImport("netapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern int NetFileEnum(
string servername,
string basepath,
string username,
int level,
ref IntPtr bufptr,
int prefmaxlen,
out int entriesread,
out int totalentries,
IntPtr resume_handle
);
[DllImport("netapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern int NetFileGetInfo(
string servername,
int fileid,
int level,
ref IntPtr bufptr
);
private int GetFileIdFromPath(string filePath)
{
const int MAX_PREFERRED_LENGTH = -1;
int dwReadEntries;
int dwTotalEntries;
IntPtr pBuffer = IntPtr.Zero;
FILE_INFO_3 pCurrent = new FILE_INFO_3();
int dwStatus = NetFileEnum(null, filePath, null, 3, ref pBuffer, MAX_PREFERRED_LENGTH, out dwReadEntries, out dwTotalEntries, IntPtr.Zero);
if (dwStatus == 0)
{
for (int dwIndex = 0; dwIndex < dwReadEntries; dwIndex++)
{
IntPtr iPtr = new IntPtr(pBuffer.ToInt32() + (dwIndex * Marshal.SizeOf(pCurrent)));
pCurrent = (FILE_INFO_3)Marshal.PtrToStructure(iPtr, typeof(FILE_INFO_3));
int fileId = pCurrent.fi3_id;
//because of the path filter in the NetFileEnum function call, the first (and hopefully only) entry should be the correct one
NetApiBufferFree(pBuffer);
return fileId;
}
}
NetApiBufferFree(pBuffer);
return -1; //should probably do something else here like throw an error
}
private string GetUsernameHandlingFile(int fileId)
{
string defaultValue = "[Unknown User]";
if (fileId == -1)
{
return defaultValue;
}
IntPtr pBuffer_Info = IntPtr.Zero;
int dwStatus_Info = NetFileGetInfo(null, fileId, 3, ref pBuffer_Info);
if (dwStatus_Info == 0)
{
IntPtr iPtr_Info = new IntPtr(pBuffer_Info.ToInt32());
FILE_INFO_3 pCurrent_Info = (FILE_INFO_3)Marshal.PtrToStructure(iPtr_Info, typeof(FILE_INFO_3));
NetApiBufferFree(pBuffer_Info);
return pCurrent_Info.fi3_username;
}
NetApiBufferFree(pBuffer_Info);
return defaultValue; //default if not successfull above
}
private string GetUsernameHandlingFile(string filePath)
{
int fileId = GetFileIdFromPath(filePath);
return GetUsernameHandlingFile(fileId);
}
这已经讨论过很多次了。我对同一个问题的回答:
您不能使用 FileSystemWatcher 异步执行此操作,但是您可以使用文件系统过滤器驱动程序同步执行此操作。驱动程序允许您获取执行操作的帐户的用户名。
使用dave4dl 发布的代码并更新声明 struct FILE_INFO_3 如下,您可以监视创建和更新文件操作的用户名(类似于 FileSharing Server 的 FileSystemWatcher 和 OpenFiles.exe 的功能的组合)
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct FILE_INFO_3
{
public int fi3_id;
public int fi3_permission;
public int fi3_num_locks;
[MarshalAs(UnmanagedType.LPWStr)]
public string fi3_pathname;
[MarshalAs(UnmanagedType.LPWStr)]
public string fi3_username;
}