I'm always on the lookout for new ways to do things. Often I'm trying to find a way to create something that is easy to use without requiring a lot of PowerShell scripting. I also like using the final result as teaching aids so even if you don't need the end product, I hope you'll pick up a trick or two that you can use in your own scripting projects. The task I had in mind today is a better way to get event log information. Not the events themselves, but rather the event log file. How many entries are in it? How big is it? How much of the configured log is being used? Here's what I came up with.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
We have always had the Get-Eventlog cmdlet which will provide some of this information using the -List parameter.
There is a bit more to each object and you could use a PowerShell expression with Select-Object to get the desired result. You could also write a function to simplify the process. But if you have to go that route, I say find a way to use CIM or PowerShell remoting. So much of remote access in PowerShell is done over legacy protocols and I believe we should strive to do more over WsMan. Because the event log files are exposed via WMI through the Win32_NTEventLogFile, we can use Get-CimInstance to retrieve them.
Yes, the output is different. But by looking at the properties I can create a function to make it easier to query and write a custom object to the pipeline with more relevant information.
In my testing this is also much faster than using Get-Eventlog. Want to know how I did this and how it works? Grab a copy of the script file from GitHub.
The function includes complete help and examples and I've attempted to document with internal comments throughout. But let me touch on a few highlights.
First, because the function is essentially a wrapper for Get-CimInstance, I wanted to be able to use both computer names and CimSessions. You'll notice in the Parameters section that I'm using 2 parameter sets. You'll see that both sets are configured to accept value from pipeline. This works because when PowerShell processes incoming, it detects the object type and selects the appropriate parameter set.
My function also includes a few other parameters used by Get-CimInstance like -Filter. Anything that is specified when the command is executed becomes a part of the built-in variable $PSBoundParameters, which is a hashtable. I display it with Write-Verbose which is handy when troubleshooting. The great thing about $PSBoundParameters is that I can splat. The main part of my function splats to Get-CimInstance.
Get-CimInstance @PSBoundParameters | Select $ListProperties
However, this means for other parameters like -Name, I need to adjust PSBoundParameters because -Name isn't part of Get-CimInstance.
if ($Name) { #remove from PSBoundparameters $PSBoundParameters.Remove("Name") | Out-Null $filter = "logfilename = '$Name'" Write-Verbose "[BEGIN ] Adding filter: $filter" $PSBoundParameters.Add("Filter",$filter) }
I can remove the bound parameter, but this doesn't delete the value of $Name. It merely removes it from the hashtable. I can still use the parameter. In this case I'm defining a WMI filter and and adding Filter to $PSBoundParameters because that is a part of Get-CimInstance.
The other thing I've done which I think makes the script easier to read, is to define a set of properties in the Begin block. This keeps the Process block a bit simpler. In my function, I knew I wanted to get certain properties and use certain names. I also wanted to create new properties with some calculated values. For example, I wanted to show a percentage used value: How much of the configured maximum size is being used from the actual file size.
#define a set of Properties to return $Properties = @{Name="Computername";Expression={$_.CSName}}, @{Name="LogName";Expression={$_.LogFileName}}, "NumberOfRecords", @{Name="Path";Expression={$_.Name}}, @{Name="SizeMB";Expression = {[math]::Round($_.FileSize/1MB,2)}}, @{Name="MaxSizeMB";Expression = {$_.MaxFileSize/1MB -as [int]}}, @{Name="PctUsed";Expression= {[math]::Round(($_.FileSize/$_.maxFileSize)*100,2)}}, "LastModified", @{Name="ModifiedAge";Expression={(Get-Date) - $_.LastModified}}
I also combined these techniques. I wanted an option to display a quick list of event logs showing only the log name and number of entries.
if ($ListOnly) { #update PSBoundparameters #limit Get-CimInstance to only retrieving the required #properties which should speed up the query. $PSBoundParameters.Add("Property", @("Logfilename","NumberofRecords","CSName")) $PSBoundParameters.Remove("ListOnly") | Out-Null #define a list properties $ListProperties = @{Name="Computername";Expression={$_.CSName}}, @{Name="LogName";Expression={$_.LogFileName}}, "NumberOfRecords" }
Get-CimInstance supports retrieving only selected properties which can improve performance so I add that to $PSBoundParameters, get rid of the function's parameter and define a collection of properties when using -ListOnly.
Whenever I have used Get-Eventlog in the past I've usually had to add a step to filter out logs with no entries. So I included that ability in my function. Although I wanted the option to combine this with other parameters.
if ($SkipEmptyLog -And $Name) { #update existing filter #remove from PSBoundparameters $PSBoundParameters.Remove("SkipEmptyLog") | Out-Null $filter+= " AND NumberofRecords<>0" Write-Verbose "[BEGIN ] Updating filter: $filter" $PSBoundParameters.Filter = $filter } elseif ($SkipEmptyLog) { #remove from PSBoundparameters $PSBoundParameters.Remove("SkipEmptyLog") | Out-Null #create filter to only filter out logs with no records $filter+= "NumberofRecords<>0" Write-Verbose "[BEGIN ] Adding filter: $filter" $PSBoundParameters.Add("Filter",$filter) }
Remember, I'm always thinking about managing at scale and querying multiple servers at once. It might be possible that I want to query for a specific event log on 100 servers and skip any that have 0 entries.
Here are some examples of the function in action.
get-cimsession | get-eventlogfile -Name System | Out-Gridview -Title "System"
I can pipe existing CIM sessions to the command.
Or find logs that are full
get-cimsession | get-eventlogfile | Where {$_.PctUsed -ge 100 } | sort PctUsed,Computername -Descending | select Computername,LogName,*Size*,PctUsed | format-table
Clearly I need to address the Security log on my domain controller!
There are many ways I can think of to use this function. Of course I'd love to hear from you. Does this solve any problems? Did you pick up anything useful? How might you use it? If you encounter any bugs or have upgrade requests, please post them in comments on the GitHub page.