Collin Dauphinee
Collin Dauphinee

Reputation: 13973

How can I get around UAC when using ReadDirectoryChanges?

I have an application that needs to monitor the primary drive for file changes via ReadDirectoryChangesW. However, when UAC is enabled, it doesn't work.

All of the Windows API calls succeed, but I'm not notified of any changes.

I can work around this by individually monitoring each directory in the root, but this is a problem, because it can potentially cause a blue screen if there are too many directories.

Is there an acceptable way to get around UAC and receive file change notifications on the entire primary drive?

The relevant CreateFile and ReadDirectoryChangesW is below. In the case where it doesn't work, directory is C:\. If I monitor any secondary drive (i.e. E:\, F:\, G:\) it works as expected. None of the calls return errors.

HANDLE fileHandle = CreateFileW(directory.c_str(), FILE_LIST_DIRECTORY, 
    FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 
    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

BOOL success = ReadDirectoryChangesW(fileHandle, watched.buffer.data(),
    watched.buffer.size(), TRUE, 
    FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, 
    NULL, &watched.overlapped, NULL);

Interestingly, the .NET System.IO.FileSystemWatcher does work correctly, and it uses the exact same functions and parameters as I'm using, but it behaves correctly.

Upvotes: 3

Views: 985

Answers (3)

Harry Johnston
Harry Johnston

Reputation: 36318

Here's some working test code, for future reference.

#include <Windows.h>

#include <stdio.h>

int main(int argc, char ** argv)
{
    HANDLE filehandle;
    BYTE buffer[65536];
    DWORD dw;
    FILE_NOTIFY_INFORMATION * fni;
    OVERLAPPED overlapped = {0};

    overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (overlapped.hEvent == NULL)
    {
        printf("CreateEvent: %u\n", GetLastError());
        return 1;
    }

    filehandle = CreateFile(L"C:\\", 
        FILE_LIST_DIRECTORY, 
        FILE_SHARE_READ | FILE_SHARE_DELETE, 
        NULL, 
        OPEN_EXISTING, 
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 
        NULL);

    if (filehandle == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile: %u\n", GetLastError());
        return 1;
    }

    for (;;)
    {
        if (!ReadDirectoryChangesW(filehandle, buffer, sizeof(buffer),
            TRUE, 
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, 
            NULL, &overlapped, NULL))
        {
            printf("ReadDirectoryChangesW: %u\n", GetLastError());
            return 1;
        }

        printf("Queued OK.\n");

        if (!GetOverlappedResult(filehandle, &overlapped, &dw, TRUE))
        {
            printf("GetOverlappedResult: %u\n", GetLastError());
            return 1;
        }

        printf("%u bytes read.\n", dw);

        fni = (FILE_NOTIFY_INFORMATION *)buffer;

        for (;;)
        {
            printf("Next entry offset = %u\n", fni->NextEntryOffset);
            printf("Action = %u\n", fni->Action);
            printf("File name = %.*ws\n", 
                                  fni->FileNameLength / 2, 
                                  fni->FileName);

            if (fni->NextEntryOffset == 0) break;

            fni = (FILE_NOTIFY_INFORMATION *)
                               (((BYTE *)fni) + fni->NextEntryOffset);
        }
    }

    printf("All done\n");
    return 0;
}

Upvotes: 1

Stefan
Stefan

Reputation: 43575

You can adjust the privileges of your process yourself like this:

// enable the required privileges for this process

LPCTSTR arPrivelegeNames[] = {  SE_BACKUP_NAME,
                                SE_RESTORE_NAME,
                                SE_CHANGE_NOTIFY_NAME
                             };

for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)
{
    CAutoGeneralHandle hToken;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken.GetPointer()))
    {
        TOKEN_PRIVILEGES tp = { 1 };

        if (LookupPrivilegeValue(NULL, arPrivelegeNames[i],  &tp.Privileges[0].Luid))
        {
            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

            AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        }
    }
}

This works also for non-privileged processes (a.k.a. normal user processes).

Upvotes: 0

Zaid Amir
Zaid Amir

Reputation: 4775

First it is best for applications that use the ReadDirectoryChangesW API to run elevated make a manifest file for you app and set requireAdministrator as the requestedExecutionLevel level. Check here for reference.

Try removing FILE_SHARE_WRITE from the CreateFile call if you are using it.

Another option is to make your program run as a service, im not sure how applicable this is to your needs. You could post some code as to how you are getting the file handle and what are you passing to ReadDirectoryChangesW

Upvotes: 1

Related Questions