<#PSScriptInfo .VERSION 1.0 .GUID 03a6a60d-01b5-49a8-adb9-ca890ea6f2eb .AUTHOR Juan Granados .COPYRIGHT 2021 Juan Granados .TAGS Audit Report HTML csv mail File Access .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/File%20Server%20Access%20Audit%20Report%20with%20PowerShell .RELEASENOTES Initial release #> <# .DESCRIPTION This PowerShell script allows to audit several file servers and send a report in CSV and HTML by mail. CSV file can be import on Excel to generate a File Audit Report. HTML report can filter and sorting rows by server, time, user, file or operation (read, delete or write). Requirements: Enable Audit in Windows and target folders, instructions: https://github.com/juangranados/powershell-scripts/tree/main/File%20Server%20Access%20Audit%20Report%20with%20PowerShell Author: Juan Granados #> ######################################################################################################################################### #Variables to modify ######################################################################################################################################### # List of servers to check audit events $server = "SRVJ-FS01","SVMEN01" # List of file extensions to ignore $ignoredExtensions = "tmp","rgt","mta","tlg","nd","ps","log","ldb","crdownload","DS_Store","cdf-ms","ini" # List of users to ignore. If you do not want to ignore users, leave it empty. # Example: $skippedUsers = "administrator","audit-test" $skippedUsers = "" # List of files to audit if you are interested only in a few files. If empty, all files (except those with ignored extensions) will be included in report. # For example: $filesToAudit = "MontlyReport.xlsx","Internal Database.mdb" -> Only this two files will be included in report. $filesToAudit = "" # Number of hours back in time to check audit events $hoursBackToCheck = "24" # Reports path $timestamp = Get-Date -format yyyy-MM-dd_HH-mm-ss # Timestamp to add to report name $htmlReportPath = "$PSScriptRoot\" + "$timestamp" + "_AuditReport.html" # default: html report path in script path. $csvReportPath = "$PSScriptRoot\" + "$timestamp" + "_AuditReport.csv" # default: csv report path in script path. $transcriptPath = "$PSScriptRoot\" + "$timestamp" + "_AuditReport.log" # default: PowerShell transcript in script path. # Mail settings [string]$SMTPServer="mail.contoso.com" # If "None", no mail is sending. Example: [string]$SMTPServer="mail.contoso.com" [string[]]$Recipient="jgranados@contoso.com" # List of recipients. Example: [string[]]$Recipient="jdoe@contoso.com","fsmith@contoso.com" [string]$Sender = "audit-reports@contoso.com" # Sender. Example: [string]$Sender="reports@contoso.com" [string]$Username="audit-reports@contoso.com" # User name to authenticate with mail server. If "None", no auth is performed. Example: [string]$Username="jdoe@gmail.com" [string]$Password="P@ssw0rd" # Password to to authenticate with mail server. If "None", no auth is performed. Example: [string]$Password="P@ssw0rd" [string]$SSL="True" # Using TLS/SSL to authenticate. Example: [string]$SSL="True" (Is required for Gmail or Office365) [int]$Port=25 # Port of mail server. Example: [int]$Port=587 ######################################################################################################################################### #Internal variables. Do not modify. ######################################################################################################################################### $ErrorActionPreference = "Stop" $startDate = (get-date).AddHours(-$hoursBackToCheck) $ns = @{e = "http://schemas.microsoft.com/win/2004/08/events/event"} $htmlEvents = [System.Collections.ArrayList]@() $csvContents = @() $accessMasks = [ordered]@{ '0x80' = 'Read' '0x2' = 'Write' '0x10000' = 'Delete' } $previousTimeCreated = "" $previousSubjectUserName = "" $previousObjectName = "" $previousAccessMask = "" $lastEventTimeCreated = "" $evts = $null ######################################################################################################################################### #Functions. Do not modify. ######################################################################################################################################### Function getFileExtension($path) { $file = (Split-Path -Path $path -Leaf).Split(".") return $file[$file.Length-1] } # This function checks to see if the file should be ignored. Function isTempFile($path) { $fileName = (Split-Path -Path $path -Leaf) $fileExtension = getFileExtension $path If ($fileName.substring(0,1) -eq "~" -or $fileName -eq "thumbs.db" -or $fileName.Substring(0,1) -eq "$") { return $true } ForEach($extension in $ignoredExtensions) { if ($fileExtension -eq $extension) { return $true } } return $false } Function isUserToSkip($user) { foreach ($svr in $server) { $serverUser = $svr + '$' if ($user -eq $serverUser) { return $true } } foreach ($skippedUser in $skippedUsers) { if ($user -eq $skippedUser) { return $true } } return $false } Function isFileToAudit($path) { if ($filesToAudit -eq "") { return $true } else { $fileName = (Split-Path -Path $path -Leaf) foreach ($file in $filesToAudit) { if ($file -eq $fileName) { return $true } } return $false } } Function isFile($path) { try { if ((Get-Item $path) -is [System.IO.FileInfo]) { return $true } } catch { if ((Split-Path -Path $path -Leaf) -like "*.*") { return $true } } } function checkIfAddEvent($serverName, $timeCreated, $userName, $objectName, [string]$accessMask) { if (-not [string]::IsNullOrEmpty($accessMask)){ if ($script:previousTimeCreated) { if ($timeCreated -gt $script:previousTimeCreated.AddSeconds(1) -or $userName -ne $script:previousSubjectUserName -or $objectName -ne $script:previousObjectName) { Write-Host "Adding event: $serverName | $script:previousTimeCreated | $script:previousSubjectUserName | $script:previousObjectName | $script:previousAccessMask" -ForegroundColor Green # Add event to html $htmlEvents.add("`n") | Out-Null $htmlEvents.add(" $($serverName)`n") | Out-Null # Time of access $htmlEvents.add(" $($script:previousTimeCreated.ToString("yyyy-MM-ddTHH:mm:ss"))`n") | Out-Null # Time of access $htmlEvents.add(" $($script:previousSubjectUserName)`n") | Out-Null # User $htmlEvents.add(" $($script:previousObjectName)`n") | Out-Null # File $htmlEvents.add(" $($script:previousAccessMask)`n") | Out-Null # Action $htmlEvents.add("`n") | Out-Null # Add event to csv $row = New-Object System.Object $row | Add-Member -MemberType NoteProperty -Name "Server" -Value $serverName $row | Add-Member -MemberType NoteProperty -Name "Time" -Value $script:previousTimeCreated.ToString("yyyy-MM-ddTHH:mm:ss") $row | Add-Member -MemberType NoteProperty -Name "User" -Value $script:previousSubjectUserName $row | Add-Member -MemberType NoteProperty -Name "File" -Value $script:previousObjectName $row | Add-Member -MemberType NoteProperty -Name "Action" -Value $script:previousAccessMask $script:csvContents += $row Write-Host "Analizing event: $timeCreated | $userName | $objectName | $accessMask" -ForegroundColor Cyan $script:lastEventTimeCreated = $script:previousTimeCreated $script:previousTimeCreated = $timeCreated $script:previousSubjectUserName = $userName $script:previousObjectName = $objectName $script:previousAccessMask = $accessMask } else { Write-Host "Analizing event: $timeCreated | $userName | $objectName | $accessMask" -ForegroundColor Cyan if ($script:previousAccessMask -ne "Write") { $script:previousAccessMask = $accessMask } } } else { Write-Host "Analizing event: $timeCreated | $userName | $objectName | $accessMask" -ForegroundColor Cyan $script:previousTimeCreated = $timeCreated $script:previousSubjectUserName = $userName $script:previousObjectName = $objectName $script:previousAccessMask = $accessMask } } else { Write-Host "Audit event $accessMask not included in 'Read', 'Modified' or 'Deleted'" -ForegroundColor DarkRed } } ######################################################################################################################################### # Main. Do not modify. ######################################################################################################################################### cls Start-Transcript $transcriptPath foreach ($svr in $server) { Write-Output "Getting events with ID 4663 of $svr since $startDate. This may take several minutes depending of log size..." $evts = Get-WinEvent -computer $svr -FilterHashtable @{LogName="security";ProviderName="Microsoft-Windows-Security-Auditing";ID="4663";StartTime=$startDate} -oldest foreach($evt in $evts) { $xml = [xml]$evt.ToXML() $SubjectUserName = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[@Name='SubjectUserName']/text()" | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value $ObjectName = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[@Name='ObjectName']/text()" | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value $AccessMask = Select-Xml -Xml $xml -Namespace $ns -XPath "//e:Data[@Name='AccessMask']/text()" | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value if ($evt.TimeCreated -ge $startDate){ if (isFile $ObjectName) { if (-not (isTempFile $ObjectName) -and -not (isUserToSkip $SubjectUserName) -and (isFileToAudit $ObjectName)) { checkIfAddEvent $svr $evt.TimeCreated $SubjectUserName $ObjectName $accessMasks[$AccessMask.ToString()] } } } } # Last event in case of has not been added if ($lastEventTimeCreated -ne $previousTimeCreated -and $previousTimeCreated -ne $null -and !([string]::IsNullOrWhiteSpace($previousTimeCreated))) { Write-Host "Adding event : $previousTimeCreated | $previousSubjectUserName | $previousObjectName | $previousAccessMask" -ForegroundColor Green $htmlEvents.add("`n") | Out-Null $htmlEvents.add(" $($svr)`n") | Out-Null $htmlEvents.add(" $($previousTimeCreated.ToString("yyyy-MM-ddTHH:mm:ss"))`n") | Out-Null # Time of access $htmlEvents.add(" $($previousSubjectUserName)`n") | Out-Null # User $htmlEvents.add(" $($previousObjectName)`n") | Out-Null # File or folder $htmlEvents.add(" $($previousAccessMask)`n") | Out-Null # Action $htmlEvents.add("`n") | Out-Null # Add event to csv $row = New-Object System.Object $row | Add-Member -MemberType NoteProperty -Name "Server" -Value $svr $row | Add-Member -MemberType NoteProperty -Name "Time" -Value $previousTimeCreated.ToString("yyyy-MM-ddTHH:mm:ss") $row | Add-Member -MemberType NoteProperty -Name "User" -Value $previousSubjectUserName $row | Add-Member -MemberType NoteProperty -Name "File" -Value $previousObjectName $row | Add-Member -MemberType NoteProperty -Name "Action" -Value $previousAccessMask $csvContents += $row } $previousTimeCreated = "" $previousSubjectUserName = "" $previousObjectName = "" $previousAccessMask = "" } $head = @" "@ $body = @"

File Audit Report

Click on a row to short by. Click again to reverse shorting.

Start typing on search box to filter by server, time, user, file or operation. You can filter by multiple fields.

$htmlEvents
Server Time User File Operation
"@ if ($htmlEvents.Count -eq 0) { Write-Host "There is not any audit events to report. Check audit configuration." -ForegroundColor Red Stop-Transcript Exit(1) } $title = "File Audit Report" $timestamp = Get-Date -format yyyy-MM-dd_HH-mm-ss $htmlReportPath = "$PSScriptRoot\" + "$timestamp" + "_AuditReport.html" $csvReportPath = "$PSScriptRoot\" + "$timestamp" + "_AuditReport.csv" Write-Host "Creating CSV file: $csvReportPath" -ForegroundColor Yellow $csvContents | Export-CSV -Path $csvReportPath -Encoding UTF8 -NoTypeInformation try{ Write-Host "Creating HTML file: $htmlReportPath" -ForegroundColor Yellow ConvertTo-HTML -Title $title -Head $Head -Body $Body | Out-File $htmlReportPath }catch { Write-Host "Error storing htlm report" -ForegroundColor Red write-host "Caught an exception:" -ForegroundColor Red write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red } # Mail sending if($SMTPServer -ne "None"){ #Creating a Mail object $msg = new-object Net.Mail.MailMessage #Creating SMTP server object $smtp = new-object Net.Mail.SmtpClient($SMTPServer,$Port) #Email structure $msg.From = $Sender $msg.ReplyTo = $Sender ForEach($mail in $Recipient) { $msg.To.Add($mail) } if ($Username -ne "None" -and $Password -ne "None") { $smtp.Credentials = new-object System.Net.NetworkCredential($Username, $Password) } if ($SSL -ne "False") { $smtp.EnableSsl = $true } #Email subject $msg.subject = "Audit Report" #Email body $msg.body = "File Audit Reports Attached." $msg.IsBodyHtml = $true $msg.Attachments.Add($csvReportPath) $msg.Attachments.Add($htmlReportPath) #Sending email try{ Write-Host "Sending email" -ForegroundColor Yellow $smtp.Send($msg) Write-Host "Mail sending ok. End of script" -ForegroundColor Green }catch { Write-Host "Error sending email" -ForegroundColor Red write-host "Caught an exception:" -ForegroundColor Red write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red } } Stop-Transcript