Generate a Windows File Server Audit Using PowerShell

Managing Windows file shares should be a routine task, but when users encounter issues with their files, it becomes stressful to quickly find out what is wrong.

There is a lot of maintenance required to manage and protect file shares, from checking data with anti-malware tools to locking down the access control list. But an often overlooked administrative task is how to audit activity on file sharing, which can be helpful in troubleshooting issues. You can use PowerShell to automate the process of auditing file sharing activity, and then generate a report from the event logs.

How to Configure Windows File Server Auditing

To get a full set of data on interactions on the Windows file server, you need to audit two specific resources: file shares and objects in file shares.

To get started, configure the security policy with the appropriate type of audit. You can do this either on the domain controller if the file server is joined to the domain, or directly on the file server if you are in a workgroup. Open the Group Policy Editor and navigate to Computer Configuration> Security Settings> Advanced Audit Policy Configuration> System Audit Policies – Local GPO> Object Access.

Make sure to change Local Computer Policy, rather than Push Group Policy from a domain controller. The process is the same if you are in an Active Directory environment, except that you change a Group Policy assigned to the file server.

In the Object Access node, enable Audit File Sharing and Audit File System and select both Success and Failure.

Run the gpupdate command to apply the updated GPO.

Next, configure auditing on the folder with PowerShell by installing the NTFSSecurity PowerShell module:

Install-Module NtfsSecurity

Then set the path you want to audit on your $ path variable and run the following command to enable auditing for everyone. The following code enables auditing on the D: Share Test folder:

$path="D:ShareTest"
Add-NTFSAudit -Path $path -AccessRights FullControl -Account Everyone -AuditFlags Success -InheritanceFlags ContainerInherit,ObjectInherit -PropagationFlags None

Then validate the settings through PowerShell.

Get-NTFSAudit -Path $path

You can also check it through the GUI by right clicking on the folder and navigating to Properties> Security> Advanced> Auditing.

When users access this folder through a share, the Security Event Log records this event with an ID 5140. An Event ID of 4663 will be displayed in the log when accessing a file or folder.

Other relevant event identifiers:

  • 5142 – when a user adds a network share
  • 5143 – when a user changes a network share
  • 5144 – when a user deletes a network share

Creating the report object

To produce a useful report, PowerShell will collect relevant events and data from the event log. The following example uses the Get-WinEvent cmdlet to return events with an ID of 4663 or 5140 from last week:

$events = Get-WinEvent -FilterHashtable @{
    LogName="Security"
    Id = 4663,5140
    StartTime = (Get-Date).AddDays(-7)
}

If you just turned on auditing, there might not be many events. Use the following command to get the number:

$events.Count

If you look at the message for an event 5140, you will see that it captures a lot of detail.

PowerShell breaks down the details of an event 5140 in the security log.

The login ID is generated because it is a 5140 event. On subsequent accesses to files and folders, the login ID will match the 4663 events.

4663 Windows Event ID
The connection ID in event 4663 matches that of event 5140.

This screenshot shows that event 4663 is related to event 5140 due to the same login ID.

When a user accesses a file share, they generate an event 5140. Whenever a user interacts with objects in the audited folder, the system generates an event 4663 with a login ID that matches event 5140 .

It can be difficult to parse useful information, but the event object has a ToXML method that gives properties with names. When you convert a 5140 event to XML and examine the data, you get a better presentation of the event data.

Windows Event 5140 to XML
Convert a 5140 event to XML for a more organized overview of its associated data.

The following screenshot shows the data for an event 4663 while converting to XML.

Windows Event XML Data
Details of event 4663 after conversion to XML.

You can convert this data to a hash table for easy access.

$dataHt = @{}
$ex.Event.EventData.Data | %{$dataHt[$_.Name] = $_.'#text'}

You can now access a property by name.

$dataHt['ObjectName']

The command will return the name of the monitored shared folder.

To create the Windows file server audit report, you will want to focus on the following properties in the XML event:

  • SubjectLogonId
  • Object name (path)
  • SubjectUserName
  • SubjectUserDomainName
  • IP address (if a 5140)
  • Access List

Most of them are self-explanatory, but AccessList is more complicated. Access is stored by a diagram value in the format %% 4416. This value refers to the permissions common to objects in the file system, in particular the right to read data from the file, the right to read data from the directory, and the right to list the contents of the directory.

You need to parse to get the four digits and then create an enumeration for converting to string value.

enum AccessType {
    ReadData_ListDirectory = 4416
    WriteData_AddFile = 4417
    AppendData_AddSubdirectory_CreatePipeInstance = 4418
    ReadEA = 4419
    WriteEA = 4420
    Execute_Traverse = 4421
    DeleteChild = 4422
    ReadAttributes = 4423
    WriteAttributes = 4424
    DELETE = 1537
    READ_CONTROL = 1538
    WRITE_DAC = 1539
    WRITE_OWNER = 1540
    SYNCHRONIZE = 1541
    ACCESS_SYS_SEC = 1542
}

Take the type of access for each event and use Selection string to match the pattern %% 4416, then, for each of these groups, convert it with the enumeration:

$ats = foreach ($stringMatch in ($dataHt['AccessList'] | Select-String -Pattern '%%(?d{4})' -AllMatches)) {
    foreach ($group in $stringMatch.Matches.Groups | ?{$_.Name -eq 'id'}) {
        [AccessType]$group.Value
    }
}

For the example event, you can see that it returns the access level that was used in the event. In this case, it is a read operation:

$ats
ReadData_ListDirectory

Create the Windows File Server Report

With events stored in the $ events variable, you can iterate over them, create the hash table, determine access, and select properties:

$report = foreach ($event in $events | Sort TimeCreated) {
    [xml]$ex = $event.ToXML()
    $dataHt = @{}
    $ex.Event.EventData.Data | %{$dataHt[$_.Name] = $_.'#text'}
    $ats = foreach ($stringMatch in ($dataHt['AccessList'] | Select-String -Pattern '%%(?d{4})' -AllMatches)) {
    foreach ($group in $stringMatch.Matches.Groups | ?{$_.Name -eq 'id'}) {
            [AccessType]$group.Value
        }
    }
    [pscustomobject]@{
        Time = $event.TimeCreated
        EventId = $event.Id
        LogonID = $dataHt['SubjectLogonId']
        Path = "$($dataHt['ObjectName'])".trim('??')
        Share = $dataHt['ShareName']
        User = $dataHt['SubjectUserName']
        UserDomain = $dataHt['SubjectDomainName']
        IpAddress = $dataHt['IpAddress']
        AccessType = $ats -join ', '
    }
}

Now you can look at the events in a more complete way. The following screenshot shows an event 4663:

4663 event ID details
The script output refines the details associated with event ID 4663.

This screenshot shows the details of a 5140 event:

Information about Event ID 5140
The information for event ID 5140 differs slightly from what appears for event 4663.

Because of how Windows events work, the same data is not present in every type of event. However, because we know the IP address of the login ID displayed in event 5140, we also know that all subsequent 4663 events with the same login ID will have the same IP address. With that in mind, implement a caching hash table:

$ipCache = @{}
$report = foreach ($event in $events | Sort TimeCreated) {
    ...
    if ($event.Id -eq 5140) {
        $ipCache[$dataHt['SubjectLogonId']] = $dataHt['IpAddress']
    } else {
        $dataHt['IpAddress'] = $ipCache[$dataHt['SubjectLogonId']]
    }
    ...
}

Each event must obtain an IP address, unless the original 5140 event falls outside of the date range of the Get-WinEvent order.

The last step is to filter the IPC $ share and all local IP addresses on the file server:

$localIps = (Get-NetIPAddress).IPAddress
$report = $report | ?{$_.Share -ne '*IPC$'}
$report = $report | ?{$localIps -notcontains $_.IpAddress}

Output Windows File Server Audit as HTML

One of the easiest ways to generate a HTML report with PowerShell is to create an object and point it at Convert to HTML:

$report | ConvertTo-Html | Out-File C:tmpSimpleHtml.html

While this works, the presentation is not ideal; it is the equivalent of Format-Table, except in HTML. You’ll want the output to be more polite, especially if you plan to share this report with someone who isn’t quite so technical. Install the PSHTML module, which will allow us to produce better HTML reports:

Install-Module PSHTML

Then create a report by grouping the data. There are several options, such as user, login ID, and IP address, but for this tutorial, group by path and share.

The following code combines the two types because event 4663 does not contain a share and event 5140 does not contain a path:

$pathGroup = $report | Group-Object Path | Where-Object {$_.Name}
$shareGroup = $report | Group-Object Share | Where-Object {$_.Name}

Create a report that creates a header for each path or share and places all grouped events that match that path or share in a table. The following script will also build a table of contents by assigning a Username property at each share path or header:

$html = html {
    header {
        h1 {
            'Share Access Report'
        }
    }
    h2 {
        'Table of contents'
    }
    li {
        'Paths'
        foreach ($pg in $pathGroup) {
            ul {
                a -href "#$($pg.Name)" {
                    $pg.Name
                }
            }
        }
    }
    li {
        'Shares'
        foreach ($sg in $shareGroup) {
            ul {
                a -href "#$($sg.Name)" {
                    $sg.Name
                }
            }
        }
    }
    foreach ($pg in $pathGroup) {
        h2 -Id $pg.Name {
            $pg.Name
        }
        $pg.Group | ConvertTo-PSHTMLTable -Properties Time,EventId,LogonId,Path,User,UserDomain,IpAddress,AccessType
    }
    foreach ($sg in $shareGroup) {
        h2 -Id $sg.Name {
            $sg.Name
        }
        $sg.Group | ConvertTo-PSHTMLTable -Properties Time,EventId,LogonId,Share,User,UserDomain,IpAddress,AccessType
    }
   
}
$html | Out-File C:tmptest.html

The resulting report, while straightforward, is clear and easy to navigate.

HTML File Server Report
The PSHTML module produces a report in HTML format with the details associated with each path or share.

When you click on a path, the report displays the header that contains these events. For example, when you click on MyBitcoinPrivateKey.txt, you will see information about this file.

file access report
When you click on a particular file, the report shows which user accessed it and when.

The report shows when SneakyUser accessed the file.

The Benefits of a File Server Permissions Report

This report is a useful way for you to both examine all this data and share it with people who prefer to easily navigate through the details, thanks to the HTML format. One of the advantages of using the PSHTML PowerShell module is flexibility; with some adjustments, you can easily group by user or login ID and include this information in the report.