【问题标题】:Find out username(who) modified file in C#在 C# 中找出用户名(谁)修改的文件
【发布时间】:2012-07-25 23:31:35
【问题描述】:

我正在使用 FileSystemWatcher 来监视文件夹。但是当目录中发生某些事件时,我不知道如何搜索谁对该文件产生了影响。我尝试使用事件日志。它只是行不通。还有另一种方法吗?

【问题讨论】:

标签: c# file-io event-log filesystemwatcher


【解决方案1】:

我不记得我在哪里找到了这段代码,但它是使用 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 上进行了测试。除非按照Exception when using Shell32 to get File extended properties 在 STA 中运行,否则它将无法正常工作,您将看到以下错误:

无法将“Shell32.ShellClass”类型的 COM 对象转换为接口类型“Shell32.IShellDispatch6”

【讨论】:

  • 据我所知,GetDetailsOf 的索引 10 获取的是文档的标题,而不是“最后一个用户”。
  • @Jeremy Thompson 如何使用 Shell32.shell?好像不是参考问题。导入东西没有帮助
  • Shell32.shell 可通过 Microsoft Shell Controls And Automation - 它是一个 COM 引用。
  • 我发现这种方法存在问题,当删除文件时 objFolder.Items() 不再包含该文件,因此无法检索任何信息..?这种方法可以以任何方式用于检测和跟踪文件删除吗?请帮忙
  • 这将为所有用户返回“BUILTIN\\Administrators”。
【解决方案2】:

您需要在文件系统上启用审核(并且审核仅在 NTFS 上可用)。您可以通过应用组策略或本地安全策略来做到这一点。您还必须对要监控的文件启用审核。您的操作方式与修改文件的权限相同。

然后将审核事件写入安全事件日志。您必须监视此事件日志以查找您感兴趣的审计事件。执行此操作的一种方法是创建一个计划任务,该任务在记录您感兴趣的事件时启动应用程序。仅当事件没有以非常高的速率记录时,为每个事件启动一个新进程才是可行的。否则您可能会遇到性能问题。

基本上,您不想查看文件的内容或属性(shell 函数GetFileDetails 会这样做)。此外,您不想使用文件共享 API 来获取打开文件的网络用户(NetGetFileInfo 会这样做)。您想知道上次修改文件的进程的用户。 Windows 通常不会记录此信息,因为它需要太多资源来执行所有文件活动。相反,您可以有选择地为对特定文件(和文件夹)执行特定操作的特定用户启用审核。

【讨论】:

  • 你是说自己写eventLog到安全偶数日志吗?
  • @KevinQu:当您在 NTFS 上启用审核时,操作系统会将审核事件写入安全事件日志。您必须正确配置审核,然后监控这些审核事件的安全事件日志。
  • 我确实浏览了安全日志。即使是一次文件的创建也会导致超过 10 个日志。而 C# 中的 EventLog 类对此并没有太大帮助
  • @KevinQu:您可以控制要审核文件的内容,就像您可以控制谁可以读取和修改文件一样。如果您只审核写入数据而不是完全控制,那么审核事件的数量可能会减少。无论如何,我不知道有任何其他方法可以获取有关谁修改了文件的信息。
【解决方案3】:

您似乎需要调用 Windows API 函数来获得所需的内容,这涉及到 PInvoke。另一个论坛上的一些人一直在研究它并想出了一些东西,你可以找到他们的solution here。但是,它似乎只适用于网络共享上的文件(不适用于您的本地计算机)。

为了将来参考,这是code posted by 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);
}

【讨论】:

  • 有没有办法可以为本地机器做?
  • 我认为不可能。网上有很多人问同样的问题,但没有一个人有答案。
【解决方案4】:

这已经讨论过很多次了。我对同一个问题的回答:

您不能使用 FileSystemWatcher 异步执行此操作,但是您可以使用文件系统过滤器驱动程序同步执行此操作。驱动程序允许您获取执行操作的帐户的用户名。

【讨论】:

    【解决方案5】:

    使用代码posted by dave4dl 并更新声明结构 FILE_INFO_3 如下, 您可以监控创建和更新文件操作的用户名(就像文件共享服务器的 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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-16
      • 2015-09-09
      • 1970-01-01
      相关资源
      最近更新 更多