Summary of the new feature / enhancement
For decades, Windows admins have been using Get-EventLog to query classic event logs. In PowerShell 3, Get-EventLog was superseeded by the more powerful and versatile Get-WinEvent which technically has convincing features: it is faster, accepts sophisticated xml queries, can access evtx based event logs, and more.
However, Get-WinEvent was never really adopted by the community, and still Get-EventLog is used most often. In PowerShell Core, Get-Eventlog was actually removed from the package so users hooked to Get-EventLog are faced with code changes.
The primary reason just why users did not adopt the technically superior Get-WinEvent is simple: its user interface is awkward and non-discoverable. To perform an average query, at minimum a FilterHashtable is needed, and its predefined supported keys are not discoverable. So a user would have to google and investigate which keys are needed when in contrast, Get-EventLog would simply suggest intuitive parameters.
Since the new Get-WinEvent can do what Get-EventLog did (and more and faster), this cmdlet should simply support the old parametersets from Get-EventLog and translate them into the awkward filter hashtable keys.
Current Situation
Here is an average event log query using Get-EventLog returning the installed updates within the past 30 days:
$30Days = (Get-Date).AddDays(-30)
Get-Eventlog -LogName System -EntryType Information -InstanceId 19 -After $30Days -Source 'Microsoft-Windows-WindowsUpdateClient'
With Get-WinEvent, a user would have to compose at minimum a filter hashtable like so:
$30Days = (Get-Date).AddDays(-30)
$filter = @{
LogName = 'System'
Level = 4,5
Id = 19
StartTime = $30Days
ProviderName = 'Microsoft-Windows-WindowsUpdateClient'
}
Get-WinEvent -FilterHashtable $filter
None of the hashtable keys needed here is discoverable. They are not intuitive, there is no intellisense or completion, thus a user would have to go google.
That said, once the correct filter hashtable has been composed, Get-WinEvent returns the very same information (albeit property names may differ a little, this would be something admins can easily work around).
Suggestion
By adding the parameters supported by Get-EventLog to Get-WinEvent, the cmdlet could automate the process of composing the filter hashtable. In essence, Get-WinEvent would be usable just as easily as Get-EventLog has been for years. This would ease transition to PowerShell Core and also increase the performance of event log queries altogether.
With such a concept, the user would run Get-WinEvent like this:
$30Days = (Get-Date).AddDays(-30)
Get-WinEvent -LogName System -EntryType Information -InstanceId 19 -After $30Days -Source 'Microsoft-Windows-WindowsUpdateClient'
Proposed technical implementation details (optional)
The implementation is primarily diligence work and not technically challenging. Basically, the parameters have to be translated to the appropriate hashtable keys.
Below is a proof of concept which is obviously neither complete nor production ready. I added the most commonly used parameters used by Get-EventLog. I did not i.e. implement the list parameterset (which should be trivial enough).
<#
Suggestion to improve Get-WinEvent in order to make it compatible to the commonly used Get-EventLog calls
Below is a prototype using a proxy function. Run it to enhance Get-WinEvent.
To get rid of the enhancement, either restart PowerShell or run:
Remove-Item -Path function:Get-WinEvent
Note that the prototype emits the composed hashtable to the console (green)
#>
function Get-WinEvent
{
[CmdletBinding(DefaultParameterSetName='GetLogSet', HelpUri='https://go.microsoft.com/fwlink/?LinkID=138336')]
param(
[Parameter(ParameterSetName='ListLogSet', Mandatory=$true, Position=0)]
[AllowEmptyCollection()]
[string[]]
${ListLog},
[Parameter(ParameterSetName='LogNameGetEventlog', Mandatory=$true, Position=0)] <#NEW#>
[Parameter(ParameterSetName='GetLogSet', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${LogName},
[Parameter(ParameterSetName='ListProviderSet', Mandatory=$true, Position=0)]
[AllowEmptyCollection()]
[string[]]
${ListProvider},
<# Get-EventLog supports wildcards, Get-WinEvent does not. Needs to be corrected. #>
[Parameter(ParameterSetName='GetProviderSet', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)]
[string[]]
${ProviderName},
[Parameter(ParameterSetName='FileSet', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${Path},
[Parameter(ParameterSetName='FileSet')]
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[Parameter(ParameterSetName='XmlQuerySet')]
[ValidateRange(1, 9223372036854775807)]
[long]
${MaxEvents},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateRange(0, 2147483647)]
[int]
${Newest},
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='ListProviderSet')]
[Parameter(ParameterSetName='ListLogSet')]
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[Parameter(ParameterSetName='XmlQuerySet')]
[Parameter(ParameterSetName='LogNameGetEventlog')] <#NEW#>
[Alias('Cn')]
[ValidateNotNullOrEmpty()] <#CORRECTED#>
[string] <# used to be [String[]], Get-WinEvent accepts [string] only, should be changed to accept string arrays #>
${ComputerName},
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='ListProviderSet')]
[Parameter(ParameterSetName='ListLogSet')]
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[Parameter(ParameterSetName='XmlQuerySet')]
[Parameter(ParameterSetName='FileSet')]
[pscredential]
[System.Management.Automation.CredentialAttribute()]
${Credential},
[Parameter(ParameterSetName='FileSet')]
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='GetLogSet')]
[ValidateNotNull()]
[string]
${FilterXPath},
[Parameter(ParameterSetName='XmlQuerySet', Mandatory=$true, Position=0)]
[xml]
${FilterXml},
[Parameter(ParameterSetName='HashQuerySet', Mandatory=$true, Position=0)]
[hashtable[]]
${FilterHashtable},
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='ListLogSet')]
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[switch]
${Force},
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='FileSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[Parameter(ParameterSetName='XmlQuerySet')]
[switch]
${Oldest},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateNotNullOrEmpty()]
[datetime]
${After},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateNotNullOrEmpty()]
[datetime]
${Before},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateNotNullOrEmpty()]
[string[]]
${UserName},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog', Position=1)]
[ValidateRange(0, 9223372036854775807)]
[ValidateNotNullOrEmpty()]
[long[]]
${InstanceId},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateNotNullOrEmpty()]
[ValidateRange(1, 2147483647)]
[int[]]
${Index},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[Alias('ET')]
[ValidateNotNullOrEmpty()]
[ValidateSet('Error','Information','FailureAudit','SuccessAudit','Warning')]
[string[]]
${EntryType},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[Alias('ABO')]
[ValidateNotNullOrEmpty()]
[string[]]
${Source},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[Alias('MSG')]
[ValidateNotNullOrEmpty()]
[string]
${Message},
<# NEW #>
[Parameter(ParameterSetName='LogNameGetEventlog')]
[switch]
${AsBaseObject},
[Parameter(ParameterSetName='ListGetEventlog')]
[switch]
${List},
[Parameter(ParameterSetName='ListGetEventlog')]
[switch]
${AsString}
)
begin
{
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Diagnostics\Get-WinEvent', [System.Management.Automation.CommandTypes]::Cmdlet)
# if the user chose the Get-EventLog compatible parameters,
# compose the appropriate filterhashtable:
$scriptCmd = if ($PSCmdlet.ParameterSetName -eq 'LogNameGetEventlog')
{
# mandatory parameter:
$filter = @{
LogName = $PSBoundParameters['Logname']
}
$null = $PSBoundParameters.Remove('LogName')
if ($PSBoundParameters.ContainsKey('Before'))
{
$filter['EndTime'] = $PSBoundParameters['Before']
$null = $PSBoundParameters.Remove('Before')
}
if ($PSBoundParameters.ContainsKey('After'))
{
$filter['StartTime'] = $PSBoundParameters['After']
$null = $PSBoundParameters.Remove('After')
}
if ($PSBoundParameters.ContainsKey('EntryType'))
{
# severity is translated to an integer array:
$levelFlags = [System.Collections.Generic.List[int]]@()
# string input converted to integer array:
if ($PSBoundParameters['EntryType'] -contains 'Error')
{
$levelFlags.Add(1) # critical
$levelFlags.Add(2) # error
}
if ($PSBoundParameters['EntryType'] -contains 'Warning')
{
$levelFlags.Add(3) # warning
}
if ($PSBoundParameters['EntryType'] -contains 'Information')
{
$levelFlags.Add(4) # informational
$levelFlags.Add(5) # verbose
}
# default to 0:
if ($levelFlags.Count -gt 0)
{
$filter['Level'] = [int[]]$levelFlags
}
# audit settings stored in Keywords key:
if ($PSBoundParameters['EntryType'] -contains 'FailureAudit')
{
$filter['Keywords'] += 0x10000000000000
}
if ($PSBoundParameters['EntryType'] -contains 'SuccessAudit')
{
$filter['Keywords'] += 0x20000000000000
}
$null = $PSBoundParameters.Remove('EntryType')
}
if ($PSBoundParameters.ContainsKey('InstanceId'))
{
$filter['ID'] = $PSBoundParameters['InstanceId']
$null = $PSBoundParameters.Remove('InstanceId')
}
if ($PSBoundParameters.ContainsKey('Source'))
{
$filter['ProviderName'] = $PSBoundParameters['Source']
$null = $PSBoundParameters.Remove('Source')
}
$PSBoundParameters['FilterHashtable'] = $filter
Write-Host ($filter | Out-String) -ForegroundColor Green
if ($PSBoundParameters.ContainsKey('Newest'))
{
$PSBoundParameters['MaxEvents'] = $PSBoundParameters['Newest']
$null = $PSBoundParameters.Remove('Newest')
}
}
$scriptCmd =
{
& $wrappedCmd @PSBoundParameters
}
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Microsoft.PowerShell.Diagnostics\Get-WinEvent
.ForwardHelpCategory Cmdlet
#>
}
Once you run this code, you can then continue to use Get-WinEvent like before, but you now can also start using the parameters that you know from Get-EventLog. The enhanced Get-WinEvent automatically converts these parameters into a filterhashtable, and there is no need anymore for the user to know its keys or predefined awkward codes for event types.
Notes
There are a few technical limitations, i.e. Get-EventLog supports wildcards for providernames whereas Get-WinEvent does not. Get-WinEvent should be enhanced in these areas to be on the same level.
Obviously the new parameters should get rich intellisense to suggest available lognames and provider names.
Summary of the new feature / enhancement
For decades, Windows admins have been using
Get-EventLogto query classic event logs. In PowerShell 3,Get-EventLogwas superseeded by the more powerful and versatileGet-WinEventwhich technically has convincing features: it is faster, accepts sophisticated xml queries, can access evtx based event logs, and more.However,
Get-WinEventwas never really adopted by the community, and stillGet-EventLogis used most often. In PowerShell Core,Get-Eventlogwas actually removed from the package so users hooked toGet-EventLogare faced with code changes.The primary reason just why users did not adopt the technically superior
Get-WinEventis simple: its user interface is awkward and non-discoverable. To perform an average query, at minimum a FilterHashtable is needed, and its predefined supported keys are not discoverable. So a user would have to google and investigate which keys are needed when in contrast,Get-EventLogwould simply suggest intuitive parameters.Since the new
Get-WinEventcan do whatGet-EventLogdid (and more and faster), this cmdlet should simply support the old parametersets fromGet-EventLogand translate them into the awkward filter hashtable keys.Current Situation
Here is an average event log query using
Get-EventLogreturning the installed updates within the past 30 days:With
Get-WinEvent, a user would have to compose at minimum a filter hashtable like so:None of the hashtable keys needed here is discoverable. They are not intuitive, there is no intellisense or completion, thus a user would have to go google.
That said, once the correct filter hashtable has been composed,
Get-WinEventreturns the very same information (albeit property names may differ a little, this would be something admins can easily work around).Suggestion
By adding the parameters supported by
Get-EventLogtoGet-WinEvent, the cmdlet could automate the process of composing the filter hashtable. In essence,Get-WinEventwould be usable just as easily asGet-EventLoghas been for years. This would ease transition to PowerShell Core and also increase the performance of event log queries altogether.With such a concept, the user would run
Get-WinEventlike this:Proposed technical implementation details (optional)
The implementation is primarily diligence work and not technically challenging. Basically, the parameters have to be translated to the appropriate hashtable keys.
Below is a proof of concept which is obviously neither complete nor production ready. I added the most commonly used parameters used by
Get-EventLog. I did not i.e. implement the list parameterset (which should be trivial enough).Once you run this code, you can then continue to use
Get-WinEventlike before, but you now can also start using the parameters that you know fromGet-EventLog. The enhancedGet-WinEventautomatically converts these parameters into a filterhashtable, and there is no need anymore for the user to know its keys or predefined awkward codes for event types.Notes
There are a few technical limitations, i.e.
Get-EventLogsupports wildcards for providernames whereasGet-WinEventdoes not.Get-WinEventshould be enhanced in these areas to be on the same level.Obviously the new parameters should get rich intellisense to suggest available lognames and provider names.