6

I am using a FileSystemWatcher to monitor a folder. But when there is some event happening in the directory, I don't know how to search who made a impact on that file. I tried to use EventLog. It just couldn't work. Is there another way to do it?

Kevin Qu
  • 107
  • 1
  • 1
  • 9
  • possible duplicate of [use the FileSystemWatcher class to document the user who is making changes](http://stackoverflow.com/questions/8649661/use-the-filesystemwatcher-class-to-document-the-user-who-is-making-changes) – Eugene Mayevski 'Callback Jul 26 '12 at 04:41

5 Answers5

9

I cant remember where I found this code but its an alternative to using pInvoke which I think is a bit overkill for this task. Use the FileSystemWatcher to watch the folder and when an event fires you can work out which user made the file change using this code:

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 is a reference to the DLL: Microsoft Shell Controls And Automation - its a COM reference

Here is some example's of how you call the method:

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);

Or get multiple comma separated properties together:

string SizeTypeAndLastModDate = GetSpecificFileProperties(filePath, new int[] {1, 2, 3});

Note: This solution has been tested on Windows 7 and Windows 10. It wont work unless running in a STA as per Exception when using Shell32 to get File extended properties and you will see the following error:

Unable to cast COM object of type 'Shell32.ShellClass' to interface type 'Shell32.IShellDispatch6'

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • As far as I know index 10 of `GetDetailsOf` gets the title of the document, not the "last user". – Martin Liversage Jul 26 '12 at 01:12
  • @Jeremy Thompson How can I use Shell32.shell? Seems like it is not a reference problem. Import thing doesn't help – Kevin Qu Jul 26 '12 at 17:42
  • 1
    `Shell32.shell` is avaiable via **Microsoft Shell Controls And Automation** - its a COM reference. – Jeremy Thompson Oct 23 '12 at 21:34
  • 1
    I'm seeing a problem with this approach, when a file is deleted objFolder.Items() no longer contains that file so no info can be retrieved..? can this approach be used in any way to detect and trace file deletions? please help – Dmitry Oct 23 '12 at 22:45
  • 1
    This returns "BUILTIN\\Administrators" for all users. – Arjun Prakash Feb 03 '16 at 05:54
  • `Unable to cast COM object of type 'Shell32.ShellClass' to interface type 'Shell32.IShellDispatch6'` – Elshan Aug 09 '16 at 23:11
  • @devopsEMK that comment isn't helpful if you have a new question feel free to ask it. – Jeremy Thompson Aug 09 '16 at 23:12
  • @JeremyThompson Yes you are right.But I got it.Any solution for that ? – Elshan Aug 09 '16 at 23:15
  • Ask a new question and link to it here, specify what line of code caused it? the version of the COM Microsoft Shell Controls And Automation DLL you referenced, your OS and version of Visual Studio. Then I'll take a look at it. – Jeremy Thompson Aug 09 '16 at 23:17
  • Cannot get who deleted the file. – Duc Filan Mar 09 '19 at 10:40
  • same error with Elshan. Does this marked answer really work? – anhtv13 Aug 15 '19 at 07:55
  • @anhtv13 what operating system? This does work on Win7 and below. Haven't tried Win10. Is your app running in a STA, it won't work otherwise: https://stackoverflow.com/q/31403956/495455 – Jeremy Thompson Aug 15 '19 at 08:34
  • @anhtv **I just tried and it works perfectly on Windows 10.** I also researched a solution which you did not confirm. Kindly do not continue comments, the question has been answered. If you have another issue ask a new question. – Jeremy Thompson Aug 15 '19 at 23:38
4

You need to enable auditing on the file system (and auditing is only available on NTFS). You do this by applying a group policy or local security policy. You will also have to enable auditing on the file you want to monitor. You do it the same way as you modify the permissions on the file.

Auditing events are then written to the security event log. You will have to monitor this event log for the auditing events you are interested in. One way to do this is to create a scheduled task that starts an application when the events you are interested in are logged. Starting a new process for each event is only viable if events aren't logged at a very high rate though. Otherwise you will likely experience performance problems.

Basically, you don't want to look at the contents or attributes of the file (which the shell function GetFileDetails does). Also, you don't want to use a file sharing API to get the network user that has the file open (which NetGetFileInfo does). You want to know the user of the process that last modified the file. This information is not normally recorded by Windows because it would require too many resources to do that for all file activities. Instead you can selectively enable auditing for specific users doing specifc actions on specific files (and folders).

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • are you saying write eventLog by myself to the security even log? – Kevin Qu Jul 25 '12 at 23:46
  • @KevinQu: When you enable auditing on NTFS the operating system will write auditing events to the security event log. You will have to configure auditing correctly and then monitor the security event log for those auditing events. – Martin Liversage Jul 25 '12 at 23:49
  • I did browse the security log. Even one single creation of file will cause more than 10 logs. And EventLog class in c# doesn't help too much on that – Kevin Qu Jul 25 '12 at 23:52
  • @KevinQu: You can control what you want to audit on the file just as you can control who can read and modify the file. The number of auditing events is probably reduced if you only audit write data instead of full control. Anyway, I don't know of any other method to get information about who modified a file. – Martin Liversage Jul 26 '12 at 00:02
2

It seems that you'll need to invoke Windows API functions to get what you want, which involves PInvoke. Some people on another forum have been looking into it and figured something out, you can find their solution here. However, it seems to work only with files on network shares (not on your local machine).

For future reference, this is the 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);
}
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
2

This has been discussed many times. My answer from the same question:

You can't do this asynchronously with FileSystemWatcher, however you can do this synchronously using file system filter driver. The driver lets you get the user name of the account performing the operation.

Eugene Mayevski 'Callback
  • 45,135
  • 8
  • 71
  • 121
1

Use code posted by dave4dl and update declare struct FILE_INFO_3 as following, you can monitor user name of create and update file action(It is like to combination of FileSystemWatcher and OpenFiles.exe's functions of FileSharing Server)

[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;
}

BenMorel
  • 34,448
  • 50
  • 182
  • 322