Aid Troubleshooting by Finding All Changes Within a Timeframe

Adam Bertram

Adam Bertram

Read more posts by this author.

Here’s a PowerShell script that reads Windows event logs across a specific timeframe to allow you to troubleshoot issues faster.

This script attempts to find all activity that happened on a computer within a certain timeframe. There are times where we’ve all discovered some crazy problem on a computer where we’re not quite sure where to look first. The only thing we know is that X problem happened between the times of Y and Z. This script was created as the first attempt you make at finding the cause of the problem.

Simply tell it when is the earliest time the problem may have happened and the latest time. From this, the script will go out and find all events generated in all event logs during that time. It will also attempt to find all files on the file system that either were last written to within the timeframe or contain a date/time string in them that is within the timeframe.

The goal of this script is to make you aware of all the changes that may have happened during that timeframe in an effort to give you as much information as possible to troubleshoot the problem.

Download this script on the Technet Script Repository

#requires -version 3
    This script searches a Windows computer for all event log or text log entries
    between a specified start and end time.
    This script enumerates all event logs within a specified timeframe and all text logs 
	that have a last write time in the timeframe or contains a line that has a date/time
	reference within the timeframe.  It records this information to a set of files and copies
	any interesting log file off for further analysis.
    PS> .\Get-EventsFromTimeframe.ps1 -StartTimestamp '01-29-2014 13:25:00' -EndTimeStamp '01-29-2014 13:28:00' -ComputerName COMPUTERNAME
    This example finds all event log entries between StartTimestamp and EndTimestamp and any file
    with an extension inside the $FileExtension param that either was last written within the timeframe
    or contains a line with a date/time reference within the timeframe.
.PARAMETER StartTimestamp
	The earliest date/time you'd like to begin searching for events.
.PARAMETER EndTimestamp
	The latest date/time you'd like to begin searching for events.
.PARAMETER Computername
	The name of the remote (or local) computer you'd like to search on.
.PARAMETER OutputFolderPath
	The path of the folder that will contain the text files that will contain the events.
.PARAMETER LogAuditFilPath
	The path to the text file that will document the log file, line number and match type
	to the logs that were matched.
.PARAMETER EventLogsOnly
	Use this switch parameter if you only want to search in the event logs.
	Use this parameter if you only want to search for logs on the file system.
.PARAMETER ExcludeDirectory
	If searching on the file system, specify any folder paths you'd like to skip.
.PARAMETER FileExtension
	Specify one or more comma-delimited set of file extensions you'd like to search for on the file system.
	This defaults to 'log,txt,wer' extensions.
[CmdletBinding(DefaultParameterSetName = 'Neither')]
param (
    [string]$ComputerName = 'localhost',
	[string]$OutputFolderPath = ".\$Computername",
	[Parameter(ParameterSetName = 'LogFiles')]
	[string]$LogAuditFilePath = "$OutputFolderPath\LogActivity.csv",
	[Parameter(ParameterSetName = 'EventLogs')]
    [Parameter(ParameterSetName = 'LogFiles')]
    [Parameter(ParameterSetName = 'LogFiles')]
	[Parameter(ParameterSetName = 'LogFiles')]
	[string[]]$FileExtension = @('log', 'txt', 'wer')
begin {
	if (!$EventLogsOnly.IsPresent) {
		## Create the local directory where to store any log files that matched the criteria
		$LogsFolderPath = "$OutputFolderPath\logs"
		if (!(Test-Path $LogsFolderPath)) {
			mkdir $LogsFolderPath | Out-Null
	function Add-ToLog($FilePath,$LineText,$LineNumber,$MatchType) {
		$Audit = @{
			'FilePath' = $FilePath;
			'LineText' = $LineText
			'LineNumber' = $LineNumber
			'MatchType' = $MatchType
		[pscustomobject]$Audit | Export-Csv -Path $LogAuditFilePath -Append -NoTypeInformation
process {
    ## Run only if the user wants to find event log entries
    if (!$LogFilesOnly.IsPresent) {
		## Find all of the event log names that contain at least 1 event
		$Logs = (Get-WinEvent -ListLog * -ComputerName $ComputerName | where { $_.RecordCount }).LogName
		$FilterTable = @{
			'StartTime' = $StartTimestamp
			'EndTime' = $EndTimestamp
			'LogName' = $Logs
		## Find all of the events in all of the event logs that are between the start and ending timestamps
		$Events = Get-WinEvent -ComputerName $ComputerName -FilterHashtable $FilterTable -ea 'SilentlyContinue'
		Write-Verbose "Found $($Events.Count) total events"
		## Convert the properties to something friendlier and append each event into the event log text file
		$LogProps = @{ }
		[System.Collections.ArrayList]$MyEvents = @()
		foreach ($Event in $Events) {
			$LogProps.Time = $Event.TimeCreated
			$LogProps.Source = $Event.ProviderName
			$LogProps.EventId = $Event.Id
			if ($Event.Message) {
				$LogProps.Message = $Event.Message.Replace("<code>n", '|').Replace("</code>r", '|')
			$LogProps.EventLog = $Event.LogName
			$MyEvents.Add([pscustomobject]$LogProps) | Out-Null
		$MyEvents | sort Time | Export-Csv -Path "$OutputFolderPath\eventlogs.txt" -Append -NoTypeInformation
	## Run only if the user wants to find log files
	if (!$EventLogsOnly.IsPresent) {
        ## Enumerate all remote admin shares on the remote computer.  I do this instead of enumerating all phyiscal drives because
        ## the drive may be there and the share may not which means I can't get to the drive anyway.
		$Shares = Get-WmiObject -ComputerName $ComputerName -Class Win32_Share | where { $_.Path -match '^\w{1}:\\$' }
		[System.Collections.ArrayList]$AccessibleShares = @()
		## Ensure I can get to all of the remote admin shares
		foreach ($Share in $Shares) {
			$Share = "\\$ComputerName\$($Share.Name)"
			if (!(Test-Path $Share)) {
				Write-Warning "Unable to access the '$Share' share on '$Computername'"
			} else {
				$AccessibleShares.Add($Share) | Out-Null	
		$AllFilesQueryParams = @{
			Path = $AccessibleShares
			Recurse = $true
			Force = $true
			ErrorAction = 'SilentlyContinue'
			File = $true
		## Add any directories specified in $ExcludeDirectory param to not search for log files in
		if ($ExcludeDirectory) {
			$AllFilesQueryParams.ExcludeDirectory = $ExcludeDirectory	
		## Create the crazy regex string that I use to search for a number of different date/time formats.
		## This is used in an attempt to search for date/time strings in each text file found
		##TODO: Add capability to match on Jan,Feb,Mar,etc
		$DateTimeRegex = "($($StartTimestamp.Month)[\\.\-/]?$($StartTimestamp.Day)[\\.\-/]?[\\.\-/]$($StartTimestamp.Year))|($($StartTimestamp.Year)[\\.\-/]?$($StartTimestamp.Month)[\\.\-/]?[\\.\-/]?$($StartTimestamp.Day))"
		## Enumerate all files matching the query params that have content
		Get-ChildItem @AllFilesQueryParams | where { $_.Length -ne 0 } | foreach {
			try {
				Write-Verbose "Processing file '$($_.Name)'"
				## Record the file if the last write time is within the timeframe.  This finds log files that may not record a 
				## date/time timestamp but may still be involved in whatever event the user is trying to find.
				if (($_.LastWriteTime -ge $StartTimestamp) -and ($_.LastWriteTime -le $EndTimestamp)) {
					Write-Verbose "Last write time within timeframe for file '$($_.Name)'"
					Add-ToLog -FilePath $_.FullName -MatchType 'LastWriteTime'
				## If the file found matches the set of extensions I'm trying to find and it's actually a plain text file.
				## I use the Get-Content to just double-check it's plain text before parsing through it.
				if ($FileExtension -contains $_.Extension.Replace('.','') -and !((Get-Content $_.FullName -Encoding Byte -TotalCount 1024) -contains 0)) {
					## Check the contents of text file to references to dates in the timeframe
					Write-Verbose "Checking log file '$($_.Name)' for date/time match in contents"
					$LineMatches = Select-String -Path $_.FullName -Pattern $DateTimeRegex
					if ($LineMatches) {
						Write-Verbose "Date/time match found in file '$($_.FullName)'"
						## Record all of the matching lines to the audit file.
						foreach ($Match in $LineMatches) {
							Add-ToLog -FilePath $_.FullName -LineNumber $Match.LineNumber -LineText $Match.Line -MatchType 'Contents'
						## Create the same path to the log file on the remote computer inside the output log directory and 
						## copy the log file with an event inside the timeframe to that path.
						## This will not work if an file path above 255 characters is met.
						$Trim = $_.FullName.Replace("\\$Computername\", '')
						$Destination = "$OutputFolderPath\$Trim"
						if (!(Test-Path $Destination)) {
							##TODO: Remove the error action when long path support is implemented
							mkdir $Destination -ErrorAction SilentlyContinue | Out-Null
						Copy-Item -Path $_.FullName -Destination $Destination -ErrorAction SilentlyContinue -Recurse
			} catch {
				Write-Warning $_.Exception.Message	

Subscribe to Adam the Automator

Get the latest posts delivered right to your inbox

Looks like you're offline!