Auditing changed / deleted files on Windows 2008 R2, 2012, or 2012 R2


This is the story of using Powershell via Scheduled Task to audit files that are remotely modified, deleted, renamed, or moved on a file server running Microsoft Windows Server 2008 R2, 2012, or 2012 R2.  It's been tested via Windows 7 and 2012 R2.


Auditing (metaphorically) positions your finger closer to the pulse of a file server, helps assist users who've misplaced files, and serves up the answer when a manager asks "Who deleted it?"

There are commercial auditing solutions (Lepide, NetWrix).  That said, this project helped me:
  • Learn about Powershell
  • Build awareness of security auditing on Windows servers
  • Fill my evenings for 4 months after putting the baby to bed

Further reading

Lessons learned

  • Windows Event Logs are memory-mapped, meaning the files live in RAM for quick access.  The SysInternals' RAMmap tool (download) (introduction) lets you see memory-mapped files.

  • Get-EventLog is much faster than Get-WinEvent, but doesn't offer an easy way to convert events to XML, nor does it read offline event log files.

  • Puzzled that my script only uses 25% of my quad-core CPU, I learned that it runs in a single-thread - it's using 100% of a single core.

  • The script chewed up massive amounts of RAM until I learned to use the Powershell pipeline.

  • To count and sort the most common event IDs in a security event log:
    Get-EventLog "Security" | Group EventID | Sort Count

How to deploy

  • First, enable auditing via group policy.  Don't be distracted by the 9 legacy categories that you first see - these are legacy categories from Server 2003; using them will generate more events than you need.  Instead, use the Advanced Audit Policy Configuration.  Be aware that activating the advanced audit policy will disable any of those legacy auditing settings that you may have enabled in the past.
  • On Server 2012 R2, enable Audit File System - Success.

  • On Server 2012 and 2008 R2, you also need Audit Handle Manipulation - Success in order to get event 4656 "Handle requested".

  • Target the policy to just the selected server(s) via the GPO's Security Filtering.  In this example, a single domain controller also serves as a file server.

  • Second, enable auditing on the folder(s) of interest:

    Audit Success by Domain Users.

    Read attributes.................for renamed files/folders (to identify the new name).
    Create files / write data....for modified files.
    Delete...............................deleted files/folders.

    If you don't care about auditing renamed folders, you can dramatically reduce the quantity of logged events by creating two auditing ACLs - one which audits all three items show above for files only, and a second which audits only the second two and applies to folders only.

    You'll probably want to turn off some default auditing for high-traffic system folders.  For example, the c:\windows\system32\dhcp folder has auditing enabled by default and it'll nearly drown you in events all by itself.

  • Third, set a max size for the security event log (I use 128MB), set it to archive itself when full, and observe (or change) the folder where saved logs reside...set the script's $LogPath variable accordingly.

  • Create a folder called C:\Audit
  • Create a folder called C:\Audit\File-Audit-Reports
  • Save the script as C:\Audit\Monitor-File-Server-Activity.ps1

  • Create a scheduled task to run it every day at 11:45pm.
schtasks /create /ru SYSTEM /tn "Monitor file server activity" /sc daily /tr "Powershell.exe -nologo -noprofile -noninteractive -ExecutionPolicy Bypass -File C:\Audit\Monitor-File-Server-Activity.ps1" /ST 23:45

  • Place the command line version of 7-Zip in the same directory as the saved event logs.

Pseudo code

Backup and clear the Windows Security Event Log.
For Each (security event log that was modified today)
  Import selected events
  For Each (imported event from the log file)
    Convert the event data to XML
    If Event ID = 4656 (handle requested):
      The object still exists (not deleted).
    If Event ID = 4663 and AccessMask = "Delete":
      The object was deleted, overwritten, moved, or renamed.
    If Event ID = 4663 and AccessMask = "Modified":
      The object was modified.
    If Event ID = 4663 and AccessMask = "Read Attributes":
      An extremely common event.
      Decide if it indicates an object was moved or renamed.
    If Event ID = 4659:
      The object was deleted.
    If Event ID = 4660:
      The object was deleted.
    Review a revolving list of "maybe" deleted objects -
    decide if they were actually deleted, or just overwritten.
Create a report in CSV and HTML.
Compress the security event logs to save disk space by 95%.
Delete compressed logs older than a specified age.

Observations that the script is based on

- Double 4663 event w/ access mask "Delete" indicates a file created.
- Single 4663 event w/ access mask "Delete" indicates a file modified.
- Single 4663 event w/ access mask "0x2" indicates a file was modified.

- Single 4663 event w/ access mask "Delete", followed by event 4660 w/ the same handle ID.
- Single 4659 event.

- Single 4663 event w/ access mask "Delete" followed by another 4663 event w/ "Read Attributes" and the same handle ID.


You may download the script from the TechNet gallery: