# PowerShell – Everything you wanted to know about Event Logs and then some

If you feel this title is very familiar to you it’s because I actually have stolen the title from Kevin Marquette. I’m in awe of his posts that take you thru topic from beginning till the end. No splitting, no hiding anything, everything on a plate, in a single post. That’s why I’ve decided to write a post that will take you on a trip on how to work with Event Logs, something that is an internal part of Windows Administration. If you’ve never worked with Events and you’re in IT you most likely should make an effort to find out what it is and how you can eat it.

blog

20 Feb 2019 fr

![PowerShell – Everything you wanted to know about Event Logs and then some](/wp-content/uploads/2019/01/img_5c43869ae7b9f.png)

[Voir les images (36)](#pf-content-image-1-0)

If you feel this title is very familiar to you it's because I **have stolen** the title from **Kevin Marquette**. I'm in awe of his posts that take you thru topic from beginning till the end. No splitting, no hiding anything, everything on a plate, in a single post. That's why I've decided to write a post that will take you on a trip on how to work with **Event Logs, **something that is an internal part of Windows Administration. If you've never worked with Events and you're in IT, you most likely should make an effort to find out what it is and how you can eat it. After you read this pretty lengthy blog post you may also be interested in **[The only PowerShell Command you will ever need to find out who did what in Active Directory](https://evotec.xyz/the-only-powershell-command-you-will-ever-need-to-find-out-who-did-what-in-active-directory/)**. It massively simplifies gathering events from **Active Directory**, but not only that. It will be worth your time – I promise!As any respected administrator should be aware (I hope) that anytime something happens in Windows, it's written to **Event Log** (with exceptions). The user is created, windows update is installed, time is synchronized, service is restarted. Anything and everything can be found in Event Logs. Of course, not everything is enabled by default, but we will talk about it a bit later. There are four default Event Log types. Those are:

- **Application** – shows events related to software installed on a machine.
- **Security** – contains events related to the security of a computer. It's very important Event Log and usually hardest to work with permission wise
- **Setup** – holds setup events related to KB installations, performance events, and events that occur during installation
- **System** – shows events that are related to the system. Restart of system, restart of services, system files, stuff like that

And a special kind of **Event Log**

- **Forwarded Events **– is a special place to store event logs that are forwarded from other computers in the network. You have to explicitly set it up but it comes as a very useful way to have a central place for logs you verify often

But that's just what you get by default. There are many, many more **Event Logs**. Every time you install some feature or enable some option in **Windows**, it usually comes with its own set of logs. Sometimes it writes events directly to one of those four default **Event Logs**, but often it creates very own custom ones for additional, extended information.

### 💡 Event Viewer – What's that?

Your first and usually last tool to help you work with Events is **Event Viewer**. It's a tool that Microsoft bundles as part of Windows since it's early beginnings and it hasn't changed much over the years. It's simple but at the same time a very powerful tool.

[!\[Event Viewer - Windows 10\](/wp-content/uploads/2019/01/img_5c43869ae7b9f.png)](/wp-content/uploads/2019/01/img_5c43869ae7b9f.png)

As you can see in the screenshot above, there are those **five event log types** I talked before, but also a bunch of other types created by **applications** and **services** that are installed in the system. Now, some of those event logs are empty, some contain useless information that you will never have to care about, but you should be aware of their existence.

### 💡 Event Log Schema

Each event consists of multiple standard fields. Those fields are as follows:

- **Log Name** – is the name of Event Log you want to view. Those are, among others, **Application**, **Security**, **System** and so on.
- **Source** – Is a name that allows you to distinguish the source of events. Usually, it will be an application name or service that created an event.
- **Event ID** – as the name suggests it's an **ID** of an **Event**. Each Event ID tells a different story. Combined with **Log Nam**e it's one of the most important information. Just by typing **Event ID **in google will give you lots of possible descriptions on the potential problem you're having and hopefully a solution to it.
- **Level** – There are 6 levels available **Verbose**, **Informational**, **Warning**, **Error**, **Critical** and **LogAlways**. This is event criticality. Often when diagnosing problems you can filter logs only to **Critical**, **Error** level to get instant information about issues on workstation/server.
- **User** – This field usually contains a user that generated an event. Usually, it will be **SYSTEM** or **N/A (Not available)**.
- **Logged** – Time the event was logged on
- **Task Category** – Each event source can have its category. Some events have it, some don't.
- **Keywords** – There are 9 keywords in use. Most important are: **AuditFailure**, AuditSuccess
- **Computer** – computer name that reported the event.

But they are not the only ones. Each event also consists of a message and a lot of other fields that contain data. It's up to developers to choose how they want to build their events and what properties they fill. This makes it a bit hard to automate because each event type can have an entirely different structure (to some degree).

### 💡 Working with Windows Events in PowerShell

Microsoft offers multiple commands that allow **Administrators** to work with **Event Logs**. You can read, write and create event logs. For this article, I'm going to focus only on reading part of it. For this article, I will focus on the two most important commands from my perspective. The two commands that are provided with the system are: **Get-EventLog** and **Get-WinEvent**. **Get-EventLog** has been around for ages and is still available on modern systems. **Get-WinEvent** is technically replacement of it. While some people still use **Get-EventLog** it's slowly being phased out. It already is unable to report proper log sizes for event logs that are bigger than **4GB**. You can read about this problem on my other blog post at **[Get-EventLog shows wrong maximum size of event logs](https://evotec.xyz/get-eventlog-shows-wrong-maximum-size-of-event-logs/)**. It can't work with most of the modern **Event Logs** created by applications either. You should make an effort to learn using **Get-WinEvent** or as I will try to show you start using my version (wrapper) called **Get-Events** which is available after you install **PSEventViewer** module. Let's not rush it thou and have a small tour over both offerings.

```powershell
Get-Command *Event* -CommandType Cmdlet | Where-Object { $_.Source -ne 'AWSPowerShell' -and $_.Source -ne 'Hyper-V' }

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Clear-EventLog                                     3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          Get-Event                                          3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Get-EventLog                                       3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          Get-EventSubscriber                                3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Get-WinEvent                                       3.0.0.0    Microsoft.PowerShell.Diagnostics
Cmdlet          Limit-EventLog                                     3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          New-Event                                          3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          New-EventLog                                       3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          New-WinEvent                                       3.0.0.0    Microsoft.PowerShell.Diagnostics
Cmdlet          Register-CimIndicationEvent                        1.0.0.0    CimCmdlets
Cmdlet          Register-EngineEvent                               3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Register-ObjectEvent                               3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Register-WmiEvent                                  3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          Remove-Event                                       3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Remove-EventLog                                    3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          Show-EventLog                                      3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          Unregister-Event                                   3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Wait-Event                                         3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Write-EventLog                                     3.1.0.0    Microsoft.PowerShell.Management
```

### 💡 Get a list of available Event Logs – Get-WinEvent

**Get-WinEvent** has many good features. One of which is an easy way to get all available **Event Logs**.

```powershell
Get-WinEvent -ListLog *
```

[!\[Get-WinEvent\](/wp-content/uploads/2019/01/img_5c43b3e8d443c.png)](/wp-content/uploads/2019/01/img_5c43b3e8d443c.png)

Of course, it also allows you to ask remote machines for the same type of data.

```powershell
Get-WinEvent -ListLog * -ComputerName AD1
```

We can also provide credentials if the computer requires different ones then the ones we are currently logged with. Using **Get-Credential** will ask us to provide name and password we want to connect with.

```powershell
$Credentials = Get-Credentials
Get-WinEvent -ListLog * -ComputerName AD1 -Credentials $Credentials
```

### 💡 Get a list of available Event Logs – Get-EventLog

**Get-EventLog **also provides a similar way to get Event Logs list.

```powershell
Get-EventLog -List
```

It also provides a way to do it with Credentials and on remote computers. But there's a big difference what they can show.

[!\[Get-EventLog\](/wp-content/uploads/2019/02/img_5c6c2afcb530a.png)](/wp-content/uploads/2019/02/img_5c6c2afcb530a.png)

Same computer, a different command to list **Event Logs** available. The difference is close to **480 Event Logs** that are missing from **Get-EventLog** command. It's essentially able to work only with **Classic Event Logs**.

### 💡 Get few events from Application Log using Get-WinEvent or Get-EventLog

Both commands can be used to list event logs from. Below I'm listing five events from **Application** log using both commands. They work largely the same in that matter.

[!\[Get-EventLog vs Get-WinEvent\](/wp-content/uploads/2019/02/img_5c6c2c6b44177.png)](/wp-content/uploads/2019/02/img_5c6c2c6b44177.png)

### 💡 Get-WinEvent vs Get-EventLog on Event Log Details

There's one more useful feature of **Get-WinEvent -ListLog** over **Get-EventLog -List**. **Get-EventLog** provides a list and nothing more above what you see in the screenshot above. **Get-WinEvent**, on the other hand, delivers a lot more information.

[!\[Get-EventLog -List\](/wp-content/uploads/2019/02/img_5c6c2ede06d7b.png)](/wp-content/uploads/2019/02/img_5c6c2ede06d7b.png)

In comparison, this is what is reported by **Get-WinEvent**. As you can notice below **ListLog** is string parameter that takes **wildcards** and allows you to limit output to **Event Logs** you need. **Get-EventLog** only uses switch parameter and outputs all logs, including much less information.

[!\[Get-WinEvent -ListLog *\](/wp-content/uploads/2019/02/img_5c6c300a65fff.png)](/wp-content/uploads/2019/02/img_5c6c300a65fff.png)

As you can see above **Get-WinEvent ListLog** is able to deliver full information about** log size**,** last access time**,** last write time**, the **oldest record**, **records in total** and many many more. This command can be used to effectively deliver information about all logs stored on the system. You would typically use it to **verify current and maximum log size**, **log mode** (**circular**, **backup**) and file location.

### 💡 Get event log with Get-CimInstance or Get-WmiObject

For full disclosure, there's also a way to work with Event Logs using WMI objects. But it doesn't offer all functionalities and has similar problems as **Get-EventLog **in some areas. If you would like to explore that route following is the output of WMI Class **Win32_NTEventLogFile**.

```powershell
$WMI = Get-CimInstance -ClassName 'Win32_NTEventlogfile'
$WMI |Format-Table -AutoSize
```

[!\[Get-CimInstance -ClassName 'Win32_NTEventlogfile'\](/wp-content/uploads/2019/02/img_5c6c784dc525c.png)](/wp-content/uploads/2019/02/img_5c6c784dc525c.png)

And if we check details there's a lot more information available for each event log.

[!\[Get-CimInstance\](/wp-content/uploads/2019/02/img_5c6c7909c2afc.png)](/wp-content/uploads/2019/02/img_5c6c7909c2afc.png)

**Get-CimInstance** is a new, modern way to query **WMI** classes. If you want to use an old approach, be my guest

```powershell
$WMI = Get-WMIObject -ClassName 'Win32_NTEventlogfile'
$WMI |Format-Table -AutoSize
```

[!\[Get-WMIObject -ClassName 'Win32_NTEventlogfile'\](/wp-content/uploads/2019/02/img_5c6c79aadb693.png)](/wp-content/uploads/2019/02/img_5c6c79aadb693.png)

As I've mentioned earlier, data returned by WMI shouldn't be trusted. My **[other blog post explains this](https://evotec.xyz/get-eventlog-shows-wrong-maximum-size-of-event-logs/)**.

### 💡 Get-WinEvent vs Get-EventLog on Event Log Details

If you've been reading everything above you may be thinking now that why do I mention **Get-EventLog** at all and why people use it still when **Get-WinEvent** is superior in comparison. To understand where they are coming from let's take a look at the syntax of both commands. As a side note, I'm using **Mathias Jessen** regex to clean up output for visibility reasons.

[!\[Get-WinEvent vs Get-EventLog Syntax\](/wp-content/uploads/2019/02/img_5c6c359a45adb.png)](/wp-content/uploads/2019/02/img_5c6c359a45adb.png)

Get-EventLog Synatx

```powershell
PS C:\Users\pklys> (Get-Command -Name 'Get-EventLog' -Syntax) -replace '\]? \[*(?=-|
 -InstanceId]
 -ComputerName
 -Newest
 -After
 -Before
 -UserName
 -Index
 -EntryType
 -Source
 -Message
 -AsBaseObject
 ]

Get-EventLog
 -ComputerName
 -List
 -AsString
 ]
```

Get-WinEvent Syntax

```powershell
PS C:\Users\pklys> (Get-Command -Name 'Get-WinEvent' -Syntax) -replace '\]? \[*(?=-|
 -MaxEvents
 -ComputerName
 -Credential
 -FilterXPath
 -Force
 -Oldest
 ]

Get-WinEvent
 -ListLog]
 -ComputerName
 -Credential
 -Force
 ]

Get-WinEvent
 -ListProvider]
 -ComputerName
 -Credential
 ]

Get-WinEvent
 -ProviderName]
 -MaxEvents
 -ComputerName
 -Credential
 -FilterXPath
 -Force
 -Oldest
 ]

Get-WinEvent
 -Path]
 -MaxEvents
 -Credential
 -FilterXPath
 -Oldest
 ]

Get-WinEvent
 -FilterHashtable]
 -MaxEvents
 -ComputerName
 -Credential
 -Force
 -Oldest
 ]

Get-WinEvent
 -FilterXml]
 -MaxEvents
 -ComputerName
 -Credential
 -Oldest
 ]
```

As you can see above **Get-EventLog** while offering fewer choices, it does provide more parameters for a single query. **Get-WinEvents** has more options to choose from, but they are a bit limited (at first sight).

[!\[Get-WinEvent vs Get-EventLog Syntax\](/wp-content/uploads/2019/02/img_5c6c5d3891684.png)](/wp-content/uploads/2019/02/img_5c6c5d3891684.png)

```powershell
$DateAfter = (Get-Date).AddDays(-1)
$DateBefore = (Get-Date)
$EventLogTest = Get-EventLog -LogName Security -InstanceId 4625 -Before $DateBefore -After $DateAfter -Newest 5
$WinEventTest = Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625; StartTime = $DateAfter; EndTime = $DateBefore } -MaxEvents 5
```

As you can see above what **Get-EventLog** offers as a natural, intuitive way to get events we want by providing named parameters for common scenarios. **Get-WinEvent** requires a bit more complicated syntax, but it gives far more options to it if you know what you're doing. It's more error prone but at the same time very powerful. At this point, I should mention that while on first look **InstanceID** looks like **EventID** and even acts like EventID it is NOT **EventID**. So while in the example above I've used it this way it may not give you expected results every time.

> The [InstanceId](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.instanceid?view=netframework-4.7.2) property uniquely identifies an event entry for a configured event source. The [InstanceId](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.instanceid?view=netframework-4.7.2) for an event log entry represents the full 32-bit resource identifier for the event in the message resource file for the event source. The [EventID](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.eventid?view=netframework-4.7.2) property equals the [InstanceId](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.instanceid?view=netframework-4.7.2) with the top two bits masked off. Two event log entries from the same source can have matching [EventID](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.eventid?view=netframework-4.7.2) values, but have different [InstanceId](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.instanceid?view=netframework-4.7.2) values due to differences in the top two bits of the resource identifier. If the application wrote the event entry using one of the [WriteEntry](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlog.writeentry?view=netframework-4.7.2) methods, the [InstanceId](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.instanceid?view=netframework-4.7.2) property matches the optional `eventId` parameter. If the application wrote the event using [WriteEvent](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlog.writeevent?view=netframework-4.7.2), the [InstanceId](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.instanceid?view=netframework-4.7.2) property matches the resource identifier specified in the [InstanceId](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventinstance.instanceid?view=netframework-4.7.2) of the `instance` parameter. If the application wrote the event using the Win32 API `ReportEvent`, the [InstanceId](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlogentry.instanceid?view=netframework-4.7.2) property matches the resource identifier specified in the `dwEventID` parameter.

[!\[Get-EventLog InstanceID vs EventID\](/wp-content/uploads/2019/02/img_5c6d3c7076910.png)](/wp-content/uploads/2019/02/img_5c6d3c7076910.png)

See the difference? Some **EventID** are the same as **InstanceID**, but some are not even close. That means you can't reliably trust in **Get-EventLog** return data if you use **InstanceID** (unless you are sure what you ask for).

### 💡 Three ways to get data from Get-WinEvent

Actually, **Get-WinEvent** offers three complicated ways to ask for data. Those are:

- **FilterHashtable** – Specifies a query in hash table format to select events from one or more event logs. The query contains a **hash table** with one or more key-value pairs.
- **FilterXPath** – Specifies an **XPath** query that this cmdlet select events from one or more logs.
- **FilterXML** – Specifies a structured **XML** query that this cmdlet selects events from one or more event logs.

All three methods offer some pros and cons. **FilterHashtable** is most commonly used because it's predictable and fairly easy to build.

### 💡 Get-WinEvent – FilterHashTable How To

Below you can see multiple keys that you can define in a **HashTable** for **Get-WinEvent**.

```powershell
LogName=
ProviderName=
Path=
Keywords=
ID=
Level=
StartTime=
EndTime=
UserID=
Data=
*=
```

As you can see **FilterHashTable** has multiple options to filter on. It has **LogName**, **ProviderName** (a name for **Source**), **Path** (**FilePath** for scanning offline **EVTX** files), **Keywords**, **ID**, **Level**, **UserID** and **Data** and **Dates**. While all that is available it's not so easy to master. As you can notice **Level** and **Keywords** hashtable keys require a number, not a value you usually see in **Event Viewer**. Same for **UserSID** which is not something you will have on hand easily.

```powershell
$FilterHashTable = @{
    LogName   = 'Security'
    ProviderName= 'Microsoft-Windows-Security-Auditing'
    #Path =
    #Keywords =
    ID        = 4625
    #Level =
    StartTime = (Get-Date).AddDays(-1)
    EndTime   = Get-Date
    #UserID =
    #Data =
}

Get-WinEvent -FilterHashtable $FilterHashTable -MaxEvents 5

Get-EventLog -LogName 'Security' -Source 'Microsoft-Windows-Security-Auditing' -Newest 5
```

Above you can see both **Get-WinEvent** and **Get-EventLog** in comparison. It doesn't look like an even fight to me. **Get-EventLog** is more comfortable providing the ability to ask for Events without checking any documentation, but at the same time, **Get-WinEvent** has more options to choose from. Get-EventLog has **EntryType** which is another name for **Keywords.** Get-WinEvent has **Levels** (as seen below), and Get-EventLog does not. This allows you to narrow results if needed. Again **Get-EventLog EntryTypes** comes in a **readable** form. You don't need to know long numbers that you have to provide to Get-WinEvent. Of course, you can define hashtables as I did below and use those data to simplify your input, but you need to remember about it.

```powershell
$Keywords = @{
    AuditFailure     = 4503599627370496
    AuditSuccess     = 9007199254740992
    CorrelationHint2 = 18014398509481984
    EventLogClassic  = 36028797018963968
    Sqm              = 2251799813685248
    WdiDiagnostic    = 1125899906842624
    WdiContext       = 562949953421312
    ResponseTime     = 281474976710656
    None             = 0
}
```

```powershell
$Levels = @{
    Verbose       = 5
    Informational = 4
    Warning       = 3
    Error         = 2
    Critical      = 1
    LogAlways     = 0
}
```

I can hear you now saying…Wait! Even if something is missing you can filter it out. Just use **Where-Object**. Indeed you could but if you check what is the output of **Get-EventLog** you can clearly see there is **EntryType** which is the same as **Keywords** for Get-WinEvent but **Level** is not given.

[!\[Get-EventLog Output\](/wp-content/uploads/2019/02/img_5c6c54badd009.png)](/wp-content/uploads/2019/02/img_5c6c54badd009.png)

Get-EventLog Output In comparison that's how **Get-WinEvent** output looks like.

[!\[Get-WinEvent Output\](/wp-content/uploads/2019/02/img_5c6c55312239a.png)](/wp-content/uploads/2019/02/img_5c6c55312239a.png)

Get-WinEvent Output There's some more data on **Get-WinEvent** to choose from. There's one fair warning thou. You shouldn't use **Where-Object** to filter out data from **Event Logs**. Why not? Because for Where-Object to do its job both **Get-WinEven**t and **Get-Eventlog **has to first get all the data and then pass it to **Where-Object** which drops elements that we don't' want. While it may seem innocent it's actually a very heavy process. Imagine asking Event Logs for 2000 events that match where you're actually interested in 5 of them. You would first get all 2000 events and use **Where-Object** locally to apply the filter and get that 5 objects you want. You just wasted seconds, minutes if not hours doing that. Think of Event Logs like a Database. The better the query you build, the better results you get and in a shorter amount of time.

```powershell
Write-Color 'Scanning Event Log with Get-EventLog' -Color Blue

$Time2 = Start-TimeLog
$Event2 = Get-EventLog -LogName 'Security' -Source 'Microsoft-Windows-Security-Auditing' -InstanceId 4625 -Before (Get-Date) -After ((Get-Date).AddDays(-1))| Where-Object { $_.Index -eq '4125545' }
Stop-TimeLog -Time $Time2

Write-Color 'Scanning Event Log with Get-WinEvent' -Color Green
$Time = Start-TimeLog
$FilterHashTable = @{
    LogName      = 'Security'
    ProviderName = 'Microsoft-Windows-Security-Auditing'
    #Path =
    #Keywords =
    ID           = 4625
    #Level =
    StartTime    = (Get-Date).AddDays(-1)
    EndTime      = Get-Date
    #UserID =
    #Data =
}

$Event1 = Get-WinEvent -FilterHashtable $FilterHashTable | Where-Object { $_.RecordID -eq '4125545'}
Stop-TimeLog -Time $Time
```

[!\[Get-WinEvent vs Get-EventLog speed comparision\](/wp-content/uploads/2019/02/img_5c6c5ff7c9568.png)](/wp-content/uploads/2019/02/img_5c6c5ff7c9568.png)

The results of this test surprised me if I am, to be honest. While I wanted to show you the difference you get when using **Where-Object** filtering and how it can affect speed I didn't expect a huge difference in what is essentially the same scan for the same data. The results are **10 seconds** for **Get-EventLog** and **3 minutes 42 seconds** for **Get-WinEvent**. Wow! Totally not what I expected but let's leave it for now, and I'll show you later what **Get-WinEvent** can do to mitigate that problem. **Get-EventLog was faster** because **InstanceID** is actually **Indexed** property, while **EventID** is not. And knowing that **InstanceID** is not exactly **EventID** (as stated earlier) it's possible that for different data requests Get-EventLog can give you wrong results. This is why to compare Oranges to Oranges we need to use **Where-Object** and explicitly ask for **EventID**. You need to remember thou that when you use **Where-Object** all logs that matched criteria are first downloaded to **PowerShell**, stored in memory and then filtered to get a single **Record**.

```powershell
Write-Color 'Scanning Event Log with Get-EventLog' -Color Blue

$Time2 = Start-TimeLog
$Event2 = Get-EventLog -LogName 'Security' -Source 'Microsoft-Windows-Security-Auditing' -Before (Get-Date) -After ((Get-Date).AddDays(-1)) | Where-Object { $_.EventID -eq 4625 -and $_.Index -eq '4096742' }
Stop-TimeLog -Time $Time2

Write-Color 'Scanning Event Log with Get-WinEvent' -Color Green
$Time = Start-TimeLog
$FilterHashTable = @{
    LogName      = 'Security'
    ProviderName = 'Microsoft-Windows-Security-Auditing'
    ID           = 4625
    StartTime    = (Get-Date).AddDays(-1)
    EndTime      = Get-Date
}

$Event1 = Get-WinEvent -FilterHashtable $FilterHashTable -Verbose | Where-Object { $_.RecordID -eq '4096742'}
Stop-TimeLog -Time $Time
```

Even with the modification of our query for **Get-EventLog**, it was still on top of **Get-WinEvent** with no real change to results. This changes a lot when we have to query a remote machine. As you can see below when we queried my Domain Controller Get-EventLog scanned all Security Logs, and after it got them, it dropped the ones that didn't match, losing all of them as there were 0 results. It took **26 minutes** to make that scan. **Get-WinEvent**, on the other hand, took only **8 seconds** to find out that there is actually no 4625 EventID in the database and terminated shortly after. This proves that using **Where-Object** to filter events is very time-consuming. It may be fast in some instances, but for others, it will take ages to get proper data.

[!\[Get-WinEvent Remote\](/wp-content/uploads/2019/02/img_5c6d18b41b3e9.png)](/wp-content/uploads/2019/02/img_5c6d18b41b3e9.png)

### 💡 Get-WinEvent – FilterXML

While **FilterHashTable** was useful it's just a helper for **FilterXML**. Let's have a quick look at what Verbose message shows for Get-WinEvent when you run it, using the same example with **FilterHashTable** as a parameter.

[!\[Get-WinEvent Verbose Output\](/wp-content/uploads/2019/02/img_5c6c61fd00779.png)](/wp-content/uploads/2019/02/img_5c6c61fd00779.png)

```xml
        *[(System/TimeCreated[@SystemTime>='2019-02-18T19:46:24.000Z' and @SystemTime

.
```

It actually creates **XML Query** behind the scenes and passing it to Get-WinEvent for us. As you can see in **XML** there are **Dates From / To**, and there is **Event ID**. If we use the same query as above we can simply pass it as a parameter to** Get-WinEvent**.

```powershell
$XML = @'


        *[(System/TimeCreated[@SystemTime>='2019-02-18T19:46:24.000Z' and @SystemTime


'@

Get-WinEvent -FilterXML $XML
```

You will get exactly the same results as we did when using **FilterHashTable**. So why would you go and create a bit complicated **XML** instead of using reasonably comfortable to use **HashTable**? If you would use the same query, we did before where I was so surprised with the** lousy performance** of Get-WinEvent you would get the same results. But if our goal was to get single **RecordID** you should be pleasantly surprised. Let's see, and build a simple XML query with RecordID, something that is not possible with **FilterHashTable**.

```xml
         *[System[EventRecordID=4096742]]
```

As you can see in **PowerShell** code below I'm simply using here-strings to wrap that **XML** in a nicely formatted way and I pass it as a single parameter to Get-WinEvent.

```powershell
$XML = @'


         *[System[EventRecordID=4096742]]


'@

$Time = Start-TimeLog
$Events = Get-WinEvent -FilterXML $XML
$Events
Stop-TimeLog -Time $Time
```

[!\[Results of FilterXML\](/wp-content/uploads/2019/02/img_5c6c65e823d2f.png)](/wp-content/uploads/2019/02/img_5c6c65e823d2f.png)

Results? 100ms to get 1 **RecordID** we wanted. Exactly that record and nothing else. Fast right? So while **Get-EventLog** seemed to win with Get-WinEvent on the larger query, that we had to filter anyways, we just asked **Get-WinEvent** to provide one event, and it delivered it in** 100ms**. This is the real deal and full Power of what Get-WinEvent can offer. Flexibility. It allows you to build complicated queries asking for precise data and deliver it in no time.

[!\[Event Viewer XML\](/wp-content/uploads/2019/02/img_5c6c68da93051.png)](/wp-content/uploads/2019/02/img_5c6c68da93051.png)

At this point, I should mention that each **Event** is actually available as **XML**. What you see in **Event Viewer** is also accessible via an XML schema. As you can see above each event has lots of data stored in it. What is prettified in **Event Viewer** in form of the long message showing as below, is actually written as each field separately in XML.

[!\[Event Viewer Main View\](/wp-content/uploads/2019/02/img_5c6c69da488ff.png)](/wp-content/uploads/2019/02/img_5c6c69da488ff.png)

See where I am getting at? You can build your XML query that includes that data you want to query for. Let's build an XML that looks for all failed logins on user called Test.

```xml
         *[EventData[(Data[@Name='SubjectUserName'] = 'Test') or (Data[@Name='TargetUserName'] = 'Test')]]
```

On a side note, when I first saw this I wasn't really happy I would need to create this, especially that it came as one line. It really hard to understand what is happening in that XML. But that's where VSCode helps with XML formatting and all.

```powershell
$XML = @'


         *[EventData[(Data[@Name='SubjectUserName'] = 'Test') or (Data[@Name='TargetUserName'] = 'Test')]]


'@

$Time = Start-TimeLog
$Events = Get-WinEvent -FilterXML $XML
$Events.Count
Stop-TimeLog -Time $Time
```

[!\[Get-WinEvent FilterXML query returning 131 matching events in just over five seconds.\](/wp-content/uploads/2019/02/img_5c6c6c16c4ea3.png)](/wp-content/uploads/2019/02/img_5c6c6c16c4ea3.png)

In just over 5 seconds I've found **131 events** that match my criteria. Something that wouldn't be possible even with reasonably fast **Get-EventLog**.** **The only problem with FilterXML path is you need to create that **XML**. With simpler ones, it's not that hard, but more complicated ones can get tricky, especially with dates and multiple ID's involved.

### 💡 Get-WinEvent – FilterXPath

Finally, we're at XPath. How it's different than 2 other options? It's just stripped down XML. I think it will be easiest if I will just show you how it looks?

```powershell
$XPath = @'
*[EventData[(Data[@Name='SubjectUserName'] = 'Test') or (Data[@Name='TargetUserName'] = 'Test')]]
'@

$Events = Get-WinEvent -FilterXPath $XPath -LogName 'Security'
```

As you can see above all I had to do is leave the essential bits of **XML**. I also had to add parameter **LogName** because in stripped down version of **XML** we've also removed a log that is supposed to have that scan. Not a huge difference right? It delivers the same results as two other options.

### 💡 Get-WinEvent filtering alternative in Get-EventLog

While Get-WinEvent doesn't have ability to define search in such degree it does allow you to filter things on **Message** using a wildcard.

```powershell
Get-EventLog -LogName 'Security' -Message '*Test*' -Newest 2000
```

Using the above code you should get similar results like the one above with few exceptions. It will be **slower** (I actually had to limit output to 2000 records to make sure it finishes quick enough) and it won't be as direct. After all **Test** word could be anywhere else in **Message**.

### 💡 Tips and Tricks of Get-WinEvent and Get-EventLog

We've established above there are few differences between the two commands but there are also other reasons why people prefer to use Get-EventLog. They are called **ReplacementStrings**. You see when you use either Get-WinEvent or Get-EventLog you get an event that has a lot of information stored but it stores it in **Message** property.

[!\[Tips and Tricks for Get-EventLog\](/wp-content/uploads/2019/02/img_5c6c70ab06d57.png)](/wp-content/uploads/2019/02/img_5c6c70ab06d57.png)

As you can see above there's a lot of data in Message property and each event type, each event can have different data stored in it. And it's stored as plain text. To parse it, every time you would want to do that it would take some heavy PowerShell skills (well maybe not really, but for the sake of this article lets pretend it is hard – I wouldn't do it myself). What Get-EventLog offers is **ReplacementStrings**.

[!\[Get-EventLog - One Event\](/wp-content/uploads/2019/02/img_5c6c71801e2df.png)](/wp-content/uploads/2019/02/img_5c6c71801e2df.png)

```powershell
$EventLogTest = Get-EventLog -LogName Security -InstanceId 4625 -Before $DateBefore -After $DateAfter -Newest 5
$EventLogTest[0].ReplacementStrings | Format-List *
```

As we can see above, all values that are stored in the message are now given to us on a silver platter. Of course, it's missing fork and knife because there are no property names next to it, but it's something right? Of course, Get-WinEvent also offers this functionality which is accessed in a bit different way

[!\[Tips and Tricks on Get-WinEvent\](/wp-content/uploads/2019/02/img_5c6c72c4e713c.png)](/wp-content/uploads/2019/02/img_5c6c72c4e713c.png)

```powershell
$WinEventTest = Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625; StartTime = $DateAfter; EndTime = $DateBefore } -MaxEvents 5
$WinEventTest[0] |Format-List *
$WinEventTest[0].Properties
```

Different tools, almost the same data. Still missing property names but it's something you can work with. For example, you could use the following solution to create **PSCustomObject **and have created your properties with the values you already have — just a bit of mix and match. Let's pick this random event from Application log. It has three properties that we care about.

[!\[Random event from Event Viewer\](/wp-content/uploads/2019/02/img_5c6cf8f3e5107.png)](/wp-content/uploads/2019/02/img_5c6cf8f3e5107.png)

```powershell
$FilterHashTable = @{
    LogName   = 'Application'
    ID        = 1534
    StartTime = (Get-Date).AddHours(-1)
    EndTime   = Get-Date
}


$Events = Get-WinEvent -FilterHashtable $FilterHashTable | ForEach-Object {
    $Values = $_.Properties | ForEach-Object { $_.Value }

    # return a new object with the required information
    [PSCustomObject]@{
        Time      = $_.TimeCreated
        # index 0 contains the name of the update
        Event     = $Values[0]
        Component = $Values[1]
        Error     = $Values[2]
        User      = $_.UserId.Value
    }
}

$Events | Format-Table -AutoSize
```

Exactly same query can be done with Get-EventLog.

```powershell
$EventLog = Get-EventLog -LogName 'Application' -After ((Get-Date).AddHours(-1)) -InstanceId 1534 | ForEach-Object {
    # return a new object with the required information
    [PSCustomObject]@{
        Time      = $_.TimeGenerated
        # index 0 contains the name of the update
        Event     = $_.ReplacementStrings[0]
        Component = $_.ReplacementStrings[1]
        Error     = $_.ReplacementStrings[2]
        User      = $_.UserName
    }
}

$EventLog | Format-Table -AutoSize
```

[!\[Comparison of results\](/wp-content/uploads/2019/02/img_5c6cfb99581c0.png)](/wp-content/uploads/2019/02/img_5c6cfb99581c0.png)

As you can see, results are largely similar with exception of User property which is translated to the proper name. **Get-WinEvent** left us with **SID**. While not a big deal it's something we have to be aware of. Interesting enough if we time both solutions **Get-WinEvent** wins this one with a margin of five seconds.

### 💡 Things to know when using Get-WinEvent or Get-EventLog

Both Get-EventLog and Get-WinEvent are still in use and they have their pros and cons specific to their use cases. But both suffer from a couple of issues. Those are:

- **Get-WinEvent** is not able to query more than 23 EventIDs on one scan

```powershell
$EventID = @(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23)
$Test = Get-WinEvent -FilterHashtable @{ ID = $EventID; LogName = 'Application' }
$EventID = @(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)
$Test = Get-WinEvent -FilterHashtable @{ ID = $EventID; LogName = 'Application' }
```

[!\[Get-WinEvent example showing that querying more than 23 event IDs can incorrectly return no matching events.\](/wp-content/uploads/2019/02/img_5c6d1216c94f4.png)](/wp-content/uploads/2019/02/img_5c6d1216c94f4.png)

As you can see above it returns right away saying that there were no events that matched your criteria. If you wouldn't know about this issue you could falsely assume there are indeed no events like that. You may wonder now why would you require to query more than 23 events at the same time? Well, it's much faster to ask once for 23 events than to ask 23 times for 1 event. From my own testing, it takes about the same time to query for one event id as it takes for 23 events with of course some added time to parse those events. Solution to this is to simply split into 2 chunks and ask for remaining EventID's another time.

- **Get-WinEvent** is not able to query multiple computers at the same time. You can't pass an **Array** of **ComputerNames** to it and get all results back.

```powershell
$FilterHashTable = @{
    LogName   = 'Application'
    ID        = 903
    StartTime = (Get-Date).AddDays(-1)
    EndTime   = Get-Date
}

$ComputerName = 'AD1', 'AD2'
$Event1 = foreach ($Computer in $ComputerName) {
    Get-WinEvent -FilterHashtable $FilterHashTable -ComputerName $Computer
}
```

Using the above PowerShell, we can ask multiple computers one by one. First AD1 will be scanned and then AD2. Add that to multiple IDs (over 23) and you would have to create a loop within a loop.

- **Get-WinEvent** and **Get-EventLog** don't provide property names for data in Message property. They contain values, but the naming part is not there. You either have to build it yourself as shown in one of the examples above, or you have to go deep into **XML** returned by **Get-WinEvent** to get them. And trust me there are at least four different types of XML events that you can find in Event Logs. You need to know XML to get to that data if you want to have it in an automated way.
- **Get-EventLog** is very slow to work with remote machines. Mainly because it has to rely on **Where-Object** to filter out complicated results since it doesn't allow you to ask for **EventID** directly in a query (instead of giving you **InstanceID** which is not the same). Most queries would take ages to process as everything that remotely looks like an Event will have to be downloaded first and then filtered right after. It means you may be getting 2000 events to get 5 of them.

### 💡 PSEventViewer – What's that?

As I've mentioned at the beginning of this article, there's a 3rd option called [**PSEventViewer**](https://evotec.xyz/hub/scripts/pseventviewer-powershell-module/). At the time of writing this article, it contains three commands. Those are **Get-Events**, **Get-EventsFilter** and **Get-EventsInformation**. No, I've not written another parser of **Event Logs**. It's a **wrapper over Get-WinEvent** that provides many useful features that you would otherwise need to do on your own. It consolidates some tips and overcomes issues I've mentioned above.

### 💡 Get-EventsFilter – How do I use it?

To be perfectly clear this function is not written by me but by **cduff **on **Spiceworks**. It was further extendd by **Justin Grote** and finally a bit by me. So credit for this goes to those guys! What Get-EventsFilter can do is create both **XPath** and **XML** filters for you. Let's go thru a few examples.

```powershell
Get-EventsFilter -XPathOnly -NamedDataFilter @{ TargetUserName = 'Test'; SubjectUserName = 'Test' } -LogName 'Security'


Get-EventsFilter -NamedDataFilter @{ TargetUserName = 'Test'; SubjectUserName = 'Test' } -LogName 'Security'


                *[EventData[(Data[@Name='SubjectUserName'] = 'Test') or (Data[@Name='TargetUserName'] = 'Test')]]


#>
```

Basically, you tell it what you need and it's able to generate XML and XPATH for you. Let's have a look at another one.

```powershell
Get-EventsFilter -NamedDataFilter @{ TargetUserName = 'Test'; SubjectUserName = 'Test' } -ProviderName 'Microsoft-Windows-Security-Auditing' -LogName 'Security'


                (*[System[Provider[@Name='Microsoft-Windows-Security-Auditing']]]) and (*[EventData[(Data[@Name='SubjectUserName'] = 'Test') or (Data[@Name='TargetUserName'] = 'Test')]])


#>
```

As you can see above you're able to generate XML without having to deal with potential errors of playing by hand with **XML**. And another one. This time query is a bit more complicated as we asked for **LogName**, **Providername**, **EventIDs**, and **NamedDataFilter**.

```powershell
Get-EventsFilter -NamedDataFilter @{ TargetUserName = 'Test'; SubjectUserName = 'Test' } `
    -ProviderName 'Microsoft-Windows-Security-Auditing' `
    -LogName 'Security' `
    -ID 1,2,3,4,5,6,7,8,9,10


                ((*[System[(((((((((EventID=1) or (EventID=2)) or (EventID=3)) or (EventID=4)) or (EventID=5)) or (EventID=6)) or (EventID=7)) or (EventID=8)) or (EventID=9)) or (EventID=10)]]) and (*[System[Provider[@Name='Microsoft-Windows-Security-Auditing']]])) and (*[EventData[(Data[@Name='SubjectUserName'] = 'Test') or (Data[@Name='TargetUserName'] = 'Test')]])


#>
```

Easy right? This gives you full control over Get-WinEvent and allows you to pick what you want when you want it. This should optimize the time it takes to get Windows Events for any query.

```powershell
Get-EventsFilter -NamedDataFilter @{ TargetUserName = 'Test'; SubjectUserName = 'Test' } `
    -ProviderName 'Microsoft-Windows-Security-Auditing' `
    -LogName 'Security' `
    -ID 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 `
    -StartTime ((Get-Date).AddDays(-1)) `
    -EndTime (Get-Date)


                ((((*[System[(((((((((EventID=1) or (EventID=2)) or (EventID=3)) or (EventID=4)) or (EventID=5)) or (EventID=6)) or (EventID=7)) or (EventID=8)) or (EventID=9)) or (EventID=10)]]) and (*[System[TimeCreated[timediff(@SystemTime) = 1]]])) and (*[System[Provider[@Name='Microsoft-Windows-Security-Auditing']]])) and (*[EventData[(Data[@Name='SubjectUserName'] = 'Test') or (Data[@Name='TargetUserName'] = 'Test')]])


#>
```

Isn't it cool? What's cooler is that this knowledge is also required for non-powershell tasks. You will need it if you want to create Subscriptions for **Event Forwarding** in Event Viewer. Sure it can generate some XML/XPATH automatically but if you want very specific information you will have to get it yourself. And that's where **Get-EventFilter** comes with help.

[!\[Event Viewer - Subscriptions XML/XPATH\](/wp-content/uploads/2019/02/img_5c6d20891c0ca.png)](/wp-content/uploads/2019/02/img_5c6d20891c0ca.png)

### 💡 Get-EventsInformation – How do I use it?

**Get-EventInformation** is a little helper to give you details about **Event Logs** on remote computers. You can ask multiple computers for multiple logs and it will scan them in parallel. It means that you will be able to scan 10-20 machines in no time without having to wait for each machine to finish. You can also ask it to scan file logs at the same time.

```powershell
Get-EventsInformation -Machine AD1, AD2 -LogName Security,Application | Format-Table -AutoSize
```

[!\[Get-EventsInformation\](/wp-content/uploads/2019/02/img_5c6d21e1701d9.png)](/wp-content/uploads/2019/02/img_5c6d21e1701d9.png)

It's possible that I will most likely work on this command a bit more so it will cover more information in the future. I'll make sure to update this section when it comes to that.

### 💡 Get-Events – What the fuss is all about?

I know what you're thinking. I shouldn't have used **Get-Events** name because it's close to **Get-Event** which is a command that's available in the system (but not working in same way Get-WinEvent and Get-EventLog does). I've chosen this name a while back, published it, used it in PSEventViewer and at some point, people told me **Get-Event** exists. I don't want to change it. It makes no sense now. You will have to get used to it or not use it at all. It took us a moment to get here and if you've read everything above this function solves it all. It's one in all tool that mostly works. But let's get thru its features, shall we?

- **Get-Events** – it **doesn't have EventID limit**. If you give it multiple EventID's it will divide them into groups of events making sure the limit of Get-WinEvent is not reached and then it queries for all of them separately. It does that in parallel. You don't have to wait for one query to finish to go to another.
- **Get-Events** – has built-in **Get-EventsFilter**. This means that you can create most filters in one command without ever using XML.

[!\[Get-Events\](/wp-content/uploads/2019/02/img_5c6d2802041cb.png)](/wp-content/uploads/2019/02/img_5c6d2802041cb.png)

- **Get-Events** – you can ask it for single **RecordID** and it will ask politely for that record without having to go thru **Where-Object**. As you can see below it generated proper XML query on the fly and delivered results in 200 ms. That was 100ms more because it actually tried to do it via RunSpaces in parallel. If there were more computers it would start all the computer scans at the same time.

[!\[Get-Events RecordID\](/wp-content/uploads/2019/02/img_5c6d265f412f1.png)](/wp-content/uploads/2019/02/img_5c6d265f412f1.png)

- **Get-Events **– finally it **recovers both Property Name and Value** for fields hidden in Message. It reads all XML types (that I am aware of) and extracts that information if possible (some XML events don't have names given). It provides those properties and their names as part of your standard object you would get from Get-WinEvent.

[!\[PSEventViewer\](/wp-content/uploads/2018/05/img_5b0b29d509b82.png)](/wp-content/uploads/2018/05/img_5b0b29d509b82.png)

- **Get-Events** – doesn't have Computer limits. You can pass an **Array** of computers to it, and it will scan all of them. It will do so in parallel so you won't have to wait. It will submit all your queries to all servers you want at the same time making sure the limit of 23 Events is not reached either. You can also match it with **NamedDataFilter** and **NamedDataExcludeFilter**. Which means you can ask for the certain property but only if other property doesn't have some data.

[!\[Get-Events Advanced Usage\](/wp-content/uploads/2019/02/img_5c6d33a36cb1c.png)](/wp-content/uploads/2019/02/img_5c6d33a36cb1c.png)

- **Get-Events** – provides easy to use **Keywords** and **Levels** as well. No more you need to remember numbers for **Keywords** or **Levels**. Those get translated for you and inserted directly into **XML** query.

[!\[Get-Events supports Keywords and Levels\](/wp-content/uploads/2019/02/img_5c6d399691f93.png)](/wp-content/uploads/2019/02/img_5c6d399691f93.png)

Feel free to experiment. I've made a lot of effort to iron out the bugs but if you find some cases where it doesn't work like you expect it to please let me know. There are a couple of other things you should know thou. **By default, Get-Events hide errors**. **All of them.** When Get-WinEvent doesn't find anything it throws an error. Get-Events doesn't. Same for other errors. It hides them. If it can't reach some server for any reason it won't tell you that unless you explicitly tell it to. You can either use **Verbose** switch for that and you will see errors as part of that, or you can use **ExtenededOutput** switch. In such a case, **Hashtable** is returned with two properties: **Output** and **Errors**.

[!\[Get-Events Errors\](/wp-content/uploads/2019/02/img_5c6d35adab039.png)](/wp-content/uploads/2019/02/img_5c6d35adab039.png)

There is also special parameter available **ExtendedInput** which was created for [**PSWinReporting**](https://evotec.xyz/hub/scripts/pswinreporting-powershell-module/)project. PSWinReporting is reporting platform built on top of [**PSEventViewer**](https://evotec.xyz/hub/scripts/pseventviewer-powershell-module/)that provides a way to create reports from events to Email, Microsoft Teams, Slack and Discord. The new version of [**PSWinReporting**](https://evotec.xyz/hub/scripts/pswinreporting-powershell-module/)is almost ready which has few tricks upon its sleeves.

```powershell
        Server                                                    LogName         EventID                     Type
        ------                                                    -------         -------                     ----
        AD1                                                       Security        {5136, 5137, 5141, 5136...} Computer
        AD2                                                       Security        {5136, 5137, 5141, 5136...} Computer
        EVO1                                                      ForwardedEvents {5136, 5137, 5141, 5136...} Computer
        AD1.ad.evotec.xyz                                         Security        {5136, 5137, 5141, 5136...} Computer
        AD2.ad.evotec.xyz                                         Security        {5136, 5137, 5141, 5136...} Computer
        C:\MyEvents\Archive-Security-2018-08-21-23-49-19-424.evtx Security        {5136, 5137, 5141, 5136...} File
        C:\MyEvents\Archive-Security-2018-09-08-02-53-53-711.evtx Security        {5136, 5137, 5141, 5136...} File
        C:\MyEvents\Archive-Security-2018-09-14-22-13-07-710.evtx Security        {5136, 5137, 5141, 5136...} File
        C:\MyEvents\Archive-Security-2018-09-15-09-27-52-679.evtx Security        {5136, 5137, 5141, 5136...} File
        AD1                                                       System          104                         Computer
        AD2                                                       System          104                         Computer
        EVO1                                                      ForwardedEvents 104                         Computer
        AD1.ad.evotec.xyz                                         System          104                         Computer
        AD2.ad.evotec.xyz                                         System          104                         Computer
        C:\MyEvents\Archive-Security-2018-08-21-23-49-19-424.evtx System          104                         File
        C:\MyEvents\Archive-Security-2018-09-08-02-53-53-711.evtx System          104                         File
        C:\MyEvents\Archive-Security-2018-09-14-22-13-07-710.evtx System          104                         File
        C:\MyEvents\Archive-Security-2018-09-15-09-27-52-679.evtx System          104                         File
```

[!\[ExtendedInput for Get-Events\](/wp-content/uploads/2019/02/img_5c6d3726884a8.png)](/wp-content/uploads/2019/02/img_5c6d3726884a8.png)

ExtenededInput parameter takes an **Array** of **PSCustomObjects** in a specific format allowing to pass everything in one go. **Get-Events** will take it up and process everything on one go. Whether it's a mix of servers, forwarders or file-based logs, it will scan it all. I don't expect it to be used because it was prepared solely for [**PSWinReporting**](https://evotec.xyz/hub/scripts/pswinreporting-powershell-module/), but I wanted you to know it's there. Best of all both **PSEventViewer** and **PSWinReporting** are **FREE** and **OpenSource**. I host them on **[GitHub](https://github.com/EvotecIT) **with all my other projects. Finally, I've made every effort for this article to be as transparent as possible. If you find any errors, typos, things that are not clear or you see any mistakes in my thinking, please don't hesitate to contact me. If you have some feature requests or bugs specifically for **PSEventViewer**, please report them on **GitHub**. If you know some other tips and tricks that you would like to share and provide them to readers of this blog post I'll be happy to update this article with them.

## Projets associés

Projets liés aux tags, catégories et au contenu de cet article.

[Tous les projets](/fr/projets/)

Recherche des projets associés...

[Retour au blog](/blog)

## Articles associés

- [Active Directory – How to track down why and where the user account was locked out](/fr/blog/active-directory-how-to-track-down-why-and-where-the-user-account-was-locked-out/)
- [Get-EventLog shows wrong maximum size of event logs](/fr/blog/get-eventlog-shows-wrong-maximum-size-of-event-logs/)
- [Windows Server 2019 – Desktop path for all users redirected to System32 \ SystemProfile \ Desktop](/fr/blog/windows-server-2019-desktop-path-for-all-users-cwindowssystem32configsystemprofiledesktop/)

<!-- PowerForge markdown artifact sha256:F671EBFBF913744E7724327710AFE7F8B4956E90BF7BC6EACA2EF982FB36C701 -->
