Skip to content

Make Get-WinEvent compatible to Get-EventLog #15844

@TobiasPSP

Description

@TobiasPSP

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Enhancementthe issue is more of a feature request than a bugWG-Cmdletsgeneral cmdlet issues

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions