Audit

You might also like

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 7

# Version 1.

2 :: This script reads offline event logs, oldest to newest, bottom to


top.
# See http://www.trimideas.com/2015/04/auditing-changed-deleted-files.html for
instructions.

$LogPath = "C:\Windows\System32\Winevt\Logs\"
$ReportPath = "C:\Audit\File-Audit-Reports\"
$Formatted_Date = (Get-Date -UFormat %A-%B-%d-at-%I-%M-%S%p)
$ZipName = "Security-Events-for-" + (Get-Date -UFormat %A-%B-%d) + ".zip"
$Report_in_CSV = $ReportPath + "Audit of changed files on " + $Formatted_Date +
".csv"
$Truncated_Log_Path = $LogPath + "Archive-Security_on_" + $Formatted_Date + ".evtx"
$Today_Midnight = (Get-Date -Hour 0 -Minute 0 -Second 0)
$Total_Events = 0
$Start_Time = Get-Date
$Exclude_Windows_Directory = $TRUE

# Hashtable
# http://blogs.msdn.com/b/mediaandmicrocode/archive/2008/11/27/microcode-
powershell-scripting-tricks-the-joy-of-using-hashtables-with-windows-
powershell.aspx
$Pending_Delete=@{}

# Dynamically expanding array


# www.jonathanmedd.net/2014/01/adding-and-removing-items-from-a-powershell-
array.html
[System.Collections.ArrayList]
$Audit_Report=@("User,Action,Source,Destination,Time,DebugNotes")

# Files to purge from the Pending_Delete hashtable


[System.Collections.ArrayList]$MyGarbage=@()

# This allows the Try...Catch error handling to work.


# http://blogs.technet.com/b/heyscriptingguy/archive/2014/07/05/weekend-scripter-
using-try-catch-finally-blocks-for-powershell-error-handling.aspx
$ErrorActionPreference = "Stop"

# This function decides if the object is a file or a folder


# http://blogs.technet.com/b/heyscriptingguy/archive/2014/08/31/powertip-using-
powershell-to-determine-if-path-is-to-file-or-folder.aspx
Function Is_a_File
{
# If the path still exists, we can know for certain if it's a file or folder
Try {If ((Get-Item $Event.ObjectName) -is [System.IO.FileInfo]) {Return $True}}

# If the path is gone, we'll assume that it's a file if it contains a period.
Catch {If ($Event.ObjectName -like "*.*") {Return $True}}
}

# This function returns just the filename from a full path.


Function Get-FileName ($fullpath)

{If ($fullpath -ne $null) {Return $fullpath.Split('\')[-1]}}

# This function returns just the foldername from a full path.


Function Get-FolderName ($fullpath)

{If ($fullpath -ne $null) {Return


$fullpath.Substring(0,$fullpath.LastIndexOf("\"))}}
# This function checks to see if the file should be ignored.
Function Is_Temporary
{
$fullpath = $Event.ObjectName

# A list of file extensions to ignore


$Temp_Files =
"tmp","rgt","mta","tlg",".nd",".ps","log",":Zone.Identifier","crdownload",".DS_Stor
e",":AFP_AfpInfo",":AFP_Resource"

# Check that the full path is long enough to be applicable, then ask if the
file's last few characters match a temporary extension.
$Temp_Files | ForEach {If ($fullpath.length -ge $_.length -AND
$fullpath.substring($fullpath.length - $_.length, $_.length) -eq $_) {Return
$True}}

If ((Get-FileName $fullpath).substring(0,1) -eq "~") {Return $True}


If ((Get-FileName $fullpath) -eq "thumbs.db") {Return $True}

Return $False
}

# This function empties out the Pending_Delete array, deciding whether the file was
created/modified or deleted.
Function CleanUp ($Seconds2Wait)
{
ForEach ($Key in $Pending_Delete.Keys)
{
# Calculate the number of seconds
$TimeSpan = -((New-TimeSpan -Start $Event.TimeCreated -End
$Pending_Delete[$Key].TimeCreated).TotalSeconds)

If ($Pending_Delete[$Key].Alive)
{
Audit_Report_Add $Pending_Delete[$Key].User "created/modified" $Key ""
$Pending_Delete[$Key].TimeCreated "Single 4663 event"
# Mark the item for removal from the list of deleted items
$MyGarbage.Add($Key)
}
# Conclude that the object was deleted if it hasn't been referenced in the
past $Seconds2Wait
# I was using the .Confirmed tag for this, but PDF printers were triggering
false positives.
ElseIf ($TimeSpan -ge $Seconds2Wait)
{
Audit_Report_Add $Pending_Delete[$Key].User "deleted" $Key ""
$Pending_Delete[$Key].TimeCreated "Aged out"
# Mark the item for removal from the list of deleted items
$MyGarbage.Add($Key)
}
}
# You can't remove items from a hashtable and then continue enumerating it, so
this is a work-around.
ForEach ($Item in $MyGarbage) {$Pending_Delete.Remove($Item)}
# Clear this temporary array now that I'm done with it.
$MyGarbage.Clear()
# This is not expected to happen, but if it does - I'll know about it!
If ($Seconds2Wait -eq 0 -AND $Pending_Delete.Count -ge 1)

{$Audit_Report.Add("Orphaned objects")
$Pending_Delete.Keys | ForEach-Object {$Audit_Report.Add($_)}}
}

# This function checks for duplicate lines when writing audit lines to the report.
# A default value of " " is assigned to each parameter in case no value was
specified when the function was called.
Function Audit_Report_Add ($User1 = " ",$Action1 = " ",$Source1 = " ",$Destination1
= " ",$Time1 = " ",$DebugNotes1 = " ")
{
# Attempt to show the person's full name instead of just their username
$User1 = Try{[string](Get-ADUser $User1).Name} Catch {$User1}

# The current line that we'd like to write into the report.
$NewLine = $User1 + "," + $Action1 + ",`"" + $Source1 + "`""
# The last line that was written into the report.
# On Server 2008 R2 w/ .NET 3.5, I wasn't able to reference the last item in a
System.Collections.ArrayList via [-1], so used this way instead.
$LastLine = Try{$Audit_Report[$Audit_Report.Count-
1].Substring(0,$NewLine.Length)} Catch {" "}

# If the new line isn't a duplicate, then put it in the report.


If ($LastLine -ne $NewLine) {$Audit_Report.Add($User1 + "," + $Action1 + ",`""
+ $Source1 + "`",`"" + $Destination1 + "`"," + $Time1 + "," + $DebugNotes1)}
}

# This function increases the number of logs you can save by compressing them to
save disk space
Function Compress_Logs
{
Try
{
# You'll need the command-line version of 7-zip (www.7-zip.org) in the
$LogPath directory
Set-Location $LogPath
Start-Process -FilePath "7za.exe" -ArgumentList "a $ZipName Archive-
Security*" -Wait

# Manually trigger .NET garbage collection to close open file handles so


security logs can be deleted after compression.
[System.GC]::Collect()
Sleep 60

# Delete the originals now that they're zipped.


Get-ChildItem ($LogPath + "Archive-Security*") | Remove-Item
}
Catch
{
"$Start_Time Unable to zip/delete the logs. Maybe you need to put
`"7za.exe`" in $LogPath" | Out-File ($LogPath + "A WARNING.txt") -Append
}
}

# This optional function deletes old logs so they don't accumulate forever
Function Prune_Logs ($Days2Retain)
{
# Gather all the zip files older than the specified number of days and delete
them.
Get-ChildItem *.zip | ? {$_.LastWriteTime -lt (Get-Date).AddDays(-
$Days2Retain)} | Remove-Item
}

# Backup and clear the event log


# https://4sysops.com/archives/managing-the-event-log-with-powershell-part-2-backup
$Security_log = get-wmiobject win32_nteventlogfile -filter "logfilename =
'Security'"
$Security_log.BackupEventlog($Truncated_Log_Path)
Clear-Eventlog "Security"

# Collect all the event logs from today and order them oldest to newest.
$Event_Logs = Get-ChildItem ($LogPath + "*.evtx") | ?{$_.LastWriteTime -ge
$Today_Midnight} | Sort LastWriteTime

ForEach ($Log in $Event_Logs)


{
# Define what we want to pull out of the event log
$MyFilter = @{Path=($Log).FullName;ID=4656,4659,4660,4663}

# Retrieve events; if none are found, immediately go to the next log file.
Try {$Events = Get-WinEvent -FilterHashTable $MyFilter -Oldest} Catch {"No
events were found in $Log"; Continue}

ForEach ($Raw_Event in $Events)


{
# Count how many events are processed and display it in the HTML report
$Total_Events++

# Convert the event into an XML object


# Thanks to
http://blogs.technet.com/b/ashleymcglone/archive/2013/08/28/powershell-get-
winevent-xml-madness-getting-details-from-event-logs.aspx
Try{$EventXML = [xml]$Raw_Event.ToXML()} Catch {Audit_Report_Add "Unable to
convert an event to XML"}

# Loop through the XML values and turn them into a hashtable for easy access.
# http://learn-powershell.net/2010/09/19/custom-powershell-objects-and-
performance/
# http://stackoverflow.com/questions/10847573/changing-powershell-pipeline-
type-to-hashtable-or-any-other-enumerable-type
$Event = @{}
ForEach ($object in $EventXML.Event.EventData.Data) {$Event.Add($object.name,
$object.'#text')}
$Event.Add("ID",$Raw_Event.ID)
$Event.Add("TimeCreated",$Raw_Event.TimeCreated)

# Check if we're supposed to ignore changes in the C:\Windows directory (e.g.


Windows Updates).
If ($Exclude_Windows_Directory)
{If ($Event.ObjectName.Length -ge 10 -and
$Event.ObjectName.Substring(0,10) -eq "C:\Windows") {Continue}}

If ($Event.ID -eq "4656" -AND # Event 4656 = a


handle was requested.
-NOT (Is_Temporary) -AND # Exclude temporary
files.
$Pending_Delete.ContainsKey($Event.ObjectName)) # It's common for a
"Delete" event to be logged right before a file is created/saved.

# The file was not deleted, so mark it as created/modified.


{If ($Pending_Delete.ContainsKey($Event.ObjectName))
{$Pending_Delete[$Event.ObjectName].Alive = $TRUE}}

ElseIf ($Event.ID -eq "4663" -AND # Event 4663 = object


access.
$Event.AccessMask -eq "0x10000" -AND # 0x10000 = Delete,
but this can mean different things - delete, overwrite, rename, move.
-NOT (Is_Temporary)) # Exclude temporary
files

{
# If I've already logged a "Delete" for this file...and am seeing it
again...
# ...the file wasn't actually deleted, so mark it as
created/modified.
If ($Pending_Delete.ContainsKey($Event.ObjectName))
{
# Exclude folders
If (Is_a_File) {Audit_Report_Add $Event.SubjectUsername
"created/modified" $Event.ObjectName "" $Event.TimeCreated "Twin 4663 events"}

# The file/folder was not actually deleted, so remove it from


this array.
$Pending_Delete.Remove($Event.ObjectName)
}
# Now record the filename, username, handle ID, and time.
$Pending_Delete.Add($Event.ObjectName,@{User =
$Event.SubjectUsername; HandleID = $Event.HandleId; TimeCreated =
$Event.TimeCreated; Alive = $FALSE; Confirmed = $FALSE})
}

ElseIf ($Event.ID -eq "4663" -AND # Event 4663 = object


access.
$Event.AccessMask -eq "0x2" -AND # 0x2 = is a classic
"object was modified" signal.
-NOT (Is_Temporary) -AND # Exclude temporary
files.
(Is_a_File)) # Exclude folders

{
Audit_Report_Add $Event.SubjectUsername "created/modified"
$Event.ObjectName "" $Event.TimeCreated "0x2 AccessMask"
# The file was not actually deleted, so remove it from this array.
$Pending_Delete.Remove($Event.ObjectName)
}

ElseIf ($Event.ID -eq "4663" -AND


$Event.AccessMask -eq "0x80") # A 4663 event with
0x80 (Read Attributes) is logged
{ # with the same
handle ID when files/folders are moved or renamed.
ForEach ($Key in $Pending_Delete.Keys)
{
# If the Handle & User match...and the object wasn't
deleted...figure out whether it was moved or renamed.
If ($Pending_Delete[$Key].HandleID -eq $Event.HandleID -AND
$Pending_Delete[$Key].User -eq $Event.SubjectUsername -AND
-NOT $Pending_Delete[$Key].Confirmed -AND
-NOT (Is_Temporary))
{
If ((Get-FileName $Event.ObjectName) -ceq (Get-FileName $Key)
-AND -NOT $Pending_Delete[$Key].InvalidHandleID)

{Audit_Report_Add $Event.SubjectUsername "moved" $Key


$Event.ObjectName $Event.TimeCreated
$Pending_Delete.Remove($Key)}

ElseIf ((Get-FolderName $Event.ObjectName) -ceq (Get-


FolderName $Key))

{
If ((Get-Filename $Key) -eq "New Folder")
{Audit_Report_Add $Event.SubjectUsername "created"
$Event.ObjectName "" $Event.TimeCreated}
Else
{Audit_Report_Add $Event.SubjectUsername "renamed"
$Key $Event.ObjectName $Event.TimeCreated}
$Pending_Delete.Remove($Key)
}
Break
}
}
# If none of those conditions match, at least note that the file
still exists (if applicable).
If ($Pending_Delete.ContainsKey($Event.ObjectName))
{$Pending_Delete[$Event.ObjectName].Alive = $TRUE}
}

ElseIf ($Event.ID -eq "4659" -AND # Event 4659 = a handle


was requested with intent to delete
-NOT (Is_Temporary)) # Exclude temporary
files
{
# If you use Windows File Explorer on Server 2012 R2 to delete a file:
event 4659 is logged on the destination file server.
# If you use a command prompt on Server 2012 R2 to delete a file: event
4663 is logged on the destination file server.
Audit_Report_Add $Event.SubjectUsername "deleted" $Event.ObjectName ""
$Event.TimeCreated "Event 4659"
}

# This delete confirmation doesn't happen when objects are moved/renamed; it


does when files are created/deleted.
ElseIf ($Event.ID -eq "4660")
{
ForEach ($Key in $Pending_Delete.Keys)
{
If ($Event.HandleID -eq $Pending_Delete[$Key].HandleID -AND
$Event.SubjectUserName -eq $Pending_Delete[$Key].User)
{$Pending_Delete[$Key].Confirmed = $TRUE}
}
}
CleanUp 120
}
}

# Flush out the list of deleted items (improbable in production at 11:45pm, but was
an issue when testing the script).
CleanUp 0

# Output the audit results to a CSV file for manipulation in Excel


$Audit_Report | Out-File $Report_in_CSV

# Convert to html
$html_file = $ReportPath + "html_report.html"
Get-Content $Report_in_CSV | ConvertFrom-Csv | ConvertTo-Html | Out-File
"$html_file"

# Zip the event logs to reduce file size


Compress_Logs

# Delete logs older than 90 days


Prune_Logs 90

You might also like