Monitoring Event Logs in Windows with PowerShell: A Script for Recent Events

When managing a Windows environment, analyzing event logs is an essential part of troubleshooting. Windows logs a wide range of system, security, and application events, but sometimes specific problems can arise without easily accessible evidence. For example, when tracking down recent events, it can be cumbersome to manually check each log for relevant entries.

In this post, I’ll share a simple PowerShell script that extracts recent log entries across all event logs—not just the default System, Application, and Security logs. This script is particularly useful when you need to quickly review what’s been happening on your machine over the last 5 minutes.

The Challenge

By default, most users only check System, Application, and Security logs. However, Windows maintains many other event logs that can provide critical information about drivers, devices, or even specific services. Accessing these logs manually via Event Viewer can be tedious and time-consuming. The goal here is to automate this process with PowerShell and make it easier to analyze logs from a specific timeframe—such as the last 5 minutes.

The Solution: PowerShell Script

Below is the PowerShell script that retrieves recent events from all event logs and displays them in an easy-to-read format.

# Determine the time from 5 minutes ago
$TimeSpan = (Get-Date).AddMinutes(-5)

# Fetch all event log names
$logNames = Get-WinEvent -ListLog *

# Loop through each log and look for recent events
foreach ($logName in $logNames.LogName) {
# Fetch the last 100 events, suppress errors if no events exist
$events = Get-WinEvent -LogName $logName -MaxEvents 100 -ErrorAction SilentlyContinue

# If there are events, filter by time
if ($events) {
$recentEvents = $events | Where-Object { $_.TimeCreated -gt $TimeSpan }

# If recent events exist, display them
if ($recentEvents) {
Write-Host "Log: $logName" -ForegroundColor Cyan
$recentEvents | Select-Object TimeCreated, Id, LevelDisplayName, Message | Format-Table -AutoSize
}
}
}

How It Works

  1. Fetching Log Names: The script starts by retrieving the names of all available event logs on your system using the following command:powershellCopy code$logNames = Get-WinEvent -ListLog * This command lists every log, including ones that are not typically visible in the default Event Viewer console.
  2. Setting a Time Filter: The script calculates the timestamp from 5 minutes ago by using Get-Date combined with the AddMinutes() method:powershellCopy code$TimeSpan = (Get-Date).AddMinutes(-5) This value will be used to filter out any events that occurred before this time.
  3. Looping Through Logs: The script then loops through each log fetched earlier:powershellCopy codeforeach ($logName in $logNames.LogName) { For each log, it pulls the last 100 events using Get-WinEvent. The -ErrorAction SilentlyContinue parameter is important here—it suppresses any errors if no events are found in a particular log, ensuring the script doesn’t stop unexpectedly.
  4. Filtering by Time: Once the events are retrieved, they are filtered by the timestamp:powershellCopy code$recentEvents = $events | Where-Object { $_.TimeCreated -gt $TimeSpan } This ensures that only events created in the last 5 minutes are shown.
  5. Displaying the Results: If any events match the filter, they are displayed in a neat table using Format-Table:powershellCopy code$recentEvents | Select-Object TimeCreated, Id, LevelDisplayName, Message | Format-Table -AutoSize The table includes the time the event was created, its event ID, the level (such as Error, Warning, or Information), and the message content.

Why This Matters

By gathering events from all logs, this script ensures that no critical logs are overlooked. Whether you’re troubleshooting a hardware issue, investigating network activity, or monitoring driver problems, reviewing all logs within a specific timeframe can give you a broader perspective on system behavior.

Example Output

Here’s an example of what the output looks like when recent events are found:

Log: Application
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
9/10/2024 11:45 AM 1001 Error Application error occurred...
9/10/2024 11:47 AM 2002 Warning Warning: High CPU usage...

This provides a quick snapshot of recent system activity across various logs.

Happy scripting!

Use PowerShell Invoke-WebRequest to login on a website

To automate some tasks with PowerShell on a website you sometimes need to log in. Today I tried some curl and postman tricks but it isn’t hard if you know what to script with PowerShell and bypass all other tools.

First, find a website to log in to, then check the page source and what the submit button does. In this example, it is “Inloggen” (dutch for login).

Then start the developer tools (F12), select the network tab, enter the credentials and login.

Now check the POST request in the developer tools.

The important things on this login form are:

  • Gebruikersnaam (username)
  • Wachtwoord (password)
  • __RequestVerificationToken

Now we can write a simple script

$LoginUri = "https://example.website.nl/versie6-0-0/mijnscore/Login"
$BackendUri = "https://example.website.nl/mijnzwemscore/inhaalles.asp"
# ==========================================
$LoginResponse = Invoke-WebRequest -Uri $LoginUri -SessionVariable "Session" 
$LoginBody = @{
    __RequestVerificationToken = $LoginResponse.InputFields[1].value
    Gebruikersnaam             = "EnterUsername"
    Wachtwoord                 = "EnterPassword"
    Submit                     = "Inloggen"
}
$LoginResponse = Invoke-WebRequest -Uri $LoginUri -WebSession $Session -Body $LoginBody -Method "POST"

Invoke-WebRequest -Uri $BackendUri -WebSession $Session 

Explanation of the script:

  1. Open the website and find out what the __RequestVerificationToken must be
  2. Create a body with the credentials, the login form submit and the __RequestVerificationToken
  3. Do the actual login and save the web session (cookies and stuff).
  4. Now we can use the previous web session to check all the backend stuff

Edit: 10-5-2024

In the comments I get 2 questions from Laerte Junior:

  1. How do I get the $LoginUri
  2. How do I get the $BackendUri

Let start with the first one. In this example I use Firefox and the zwemscore example

Press F12 (Developers Options)

Now Login to the website and check the POST URL (right click copy)

and you get the $LoginUri

When you do an successful login you can see all the backend URI’s. In my case was “Les Inhalen” the one I want to scrape the data from. So I click “Les Inhalen”

And I get the $BackendUri

Now run the first 3 lines from the script in PowerShell

$LoginUri = "https://thelocalgym.zwemscore.nl/versie6-0-0/mijnscore/Login"
$BackendUri = "https://thelocalgym.zwemscore.nl/mijnzwemscore/inhaalles.asp"
$LoginResponse = Invoke-WebRequest -Uri $LoginUri -SessionVariable "Session"

And check the $LoginResponse.Inputfields

In our case we need:

  1. __RequestVerificationToken
  2. Gebruikersnaam (username)
  3. Wachtwoord (Password)

Because we know the username and password the only dynamic thing is __RequestVerificationToken

So we can use 2 different ways to get the data.

The Dynamic way: ($LoginResponse.InputFields | Where-Object {$_.name -like "__RequestVerificationToken"}).value

-OR-

The Array way (quick ‘n dirty): $LoginResponse.InputFields[6].value

Quick ‘n dirty

$LoginUri = "https://thelocalgym.zwemscore.nl/versie6-0-0/mijnscore/Login"
$BackendUri = "https://thelocalgym.zwemscore.nl/mijnzwemscore/inhaalles.asp"
# ==========================================
$LoginResponse = Invoke-WebRequest -Uri $LoginUri -SessionVariable "Session" 
$LoginBody = @{
    __RequestVerificationToken = $LoginResponse.InputFields[6].value
    Gebruikersnaam             = "ExampleUser"
    Wachtwoord                 = "ExamplePass"
    Submit                     = "Inloggen"
}
$LoginResponse = Invoke-WebRequest -Uri $LoginUri -WebSession $Session -Body $LoginBody -Method "POST"

$response = Invoke-WebRequest -Uri $BackendUri -WebSession $Session 

$response.RawContent

Dynamic way (better)

$LoginUri = "https://thelocalgym.zwemscore.nl/versie6-0-0/mijnscore/Login"
$BackendUri = "https://thelocalgym.zwemscore.nl/mijnzwemscore/inhaalles.asp"
# ==========================================
$LoginResponse = Invoke-WebRequest -Uri $LoginUri -SessionVariable "Session" 
$RequestToken = ($LoginResponse.InputFields | Where-Object {$_.name -like "__RequestVerificationToken"}).value
$LoginBody = @{
    __RequestVerificationToken = $RequestToken
    Gebruikersnaam             = "ExampleUser"
    Wachtwoord                 = "ExamplePass"
    Submit                     = "Inloggen"
}
$LoginResponse = Invoke-WebRequest -Uri $LoginUri -WebSession $Session -Body $LoginBody -Method "POST"

$response = Invoke-WebRequest -Uri $BackendUri -WebSession $Session 

$response.RawContent

Happy login 🙂

Update Windows Server with powershell

This is for 2016 server. But it will work for other versions of Windows

[Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted
Install-Module PSWindowsUpdate -Confirm:$false
Set-PSRepository -Name "PSGallery" -InstallationPolicy Untrusted
Get-WindowsUpdate -AcceptAll -Install -AutoReboot

Happy Patching 🙂

Configure WSUS client with Powershell and Regedit

My use case for a customer was to configure WSUS for a couple of DMZ servers. The DMZ servers are not domain-joined. So I create a PowerShell script to configure the registry so I can easily deploy the settings to the servers.

You can use this script also for non domain-joined servers

# Script for WSUS configuration on non-domain joined servers

# First stop the Windows service
Get-Service -name wuauserv | stop-service

# Variables
$registryPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
$name1 = "TargetGroup"
$value1 = "Production"
$name2 = "WUServer"
$value2 = "http://wsus.domain.local:8530"
$name3 = "WUStatusServer"
$value3 = "http://wsus.domain.local:8530"
$name4 = "DoNotConnectToWindowsUpdateInternetLocations"
$value4 = "1"
$name5 = "TargetGroupEnabled"
$value5 = "1"
$registryPathAU = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
$nameAU1 = "AUOptions"
$valueAU1 = "3"
$nameAU2 = "UseWUServer"
$valueAU2 = "1"


# Inject registry
IF(!(Test-Path $registryPath))
{
    New-Item -Path $registryPath -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name1 -Value $value1 -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name2 -Value $value2 -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name3 -Value $value3 -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name4 -Value $value4 -PropertyType DWORD -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name5 -Value $value5 -PropertyType DWORD -Force | Out-Null
}
ELSE
{
    New-ItemProperty -Path $registryPath -Name $name1 -Value $value1 -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name2 -Value $value2 -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name3 -Value $value3 -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name4 -Value $value4 -PropertyType DWORD -Force | Out-Null
    New-ItemProperty -Path $registryPath -Name $name5 -Value $value5 -PropertyType DWORD -Force | Out-Null
}
IF(!(Test-Path $registryPathAU))
{
    New-Item -Path $registryPathAU -Force | Out-Null
    New-ItemProperty -Path $registryPathAU -Name $nameAU1 -Value $valueAU1 -PropertyType DWORD -Force | Out-Null
    New-ItemProperty -Path $registryPathAU -Name $nameAU2 -Value $valueAU2 -PropertyType DWORD -Force | Out-Null
    }
ELSE
{
    New-ItemProperty -Path $registryPathAU -Name $nameAU1 -Value $valueAU1 -PropertyType DWORD -Force | Out-Null
    New-ItemProperty -Path $registryPathAU -Name $nameAU2 -Value $valueAU2 -PropertyType DWORD -Force | Out-Null
}

# Start the Windows service
Get-Service -name wuauserv | start-service

# Find updates and report to the WSUS server
$updateSession = new-object -com "Microsoft.Update.Session"; $updates=$updateSession.CreateupdateSearcher().Search($criteria).Updates
Start-sleep -seconds 10
wuauclt /detectnow
wuauclt /reportnow
c:\windows\system32\UsoClient.exe startscan

I save this file on the WSUS server itself at http://wsus.domain.local:8530/script.txt so I can download and run it from the DMZ environment.

invoke-webrequest -uri http://wsus.domain.local:8530/script.txt -outfile script.ps1
.\script.ps1

Happy updating 🙂

Powershell better tab autocomplete

With Powershell ISE you can use CTRL + SPACE for auto completion.

Powershell ISE Completion

But when you work in a native Powershell window you don’t have this option. And many often you cycle with tab completion trough the powershell and because you press the TAB button too soon you have to remove characters and cycle again trough all the commands.

Unit now 😉

There is an easy fix you can implement so the Powershell have the same auto completion like bash.

  • Type notepad $profile for Windows or gedit $profile for Linux
  • Add the line Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete
Add line in $profile
  • Save the file and restart Powershell
  • And now you have a much better working tab completion
Linux Example
Windows Example

Happy coding 🙂

Add Powershell 7 to your Powershell ISE

I find this code snippet somewhere on the internet and I like it 🙂

Swtich Powershell version in ISE

Just open ISE and paste these code and you are ready to rock!

$ErrorActionPreference= 'silentlycontinue'
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Clear()
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Switch to PowerShell 7", { 
        function New-OutOfProcRunspace {
            param($ProcessId)
  
            $ConnInfo = New-Object -TypeName System.Management.Automation.Runspaces.NamedPipeConnectionInfo -ArgumentList @($ProcessId)
            $DefaultType = [System.Management.Automation.Runspaces.TypeTable]::LoadDefaultTypeFiles()
  
            $Runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($ConnInfo, $Host, $Default)
  
            $Runspace.Open()
            $Runspace
        }
  
        $PowerShell = Start-Process PWSH -ArgumentList @("-NoExit") -PassThru -WindowStyle Hidden
        $Runspace = New-OutOfProcRunspace -ProcessId $PowerShell.Id
        $Host.PushRunspace($Runspace)
}, "ALT+F5") | Out-Null
  
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Switch to PowerShell 5", { 
    $Host.PopRunspace()
  
    $ChildProc = Get-CimInstance -ClassName win32_process | where {$_.ParentProcessId -eq $Pid}
    $ChildProc | ForEach-Object { Stop-Process -Id $_.ProcessId }
  
}, "ALT+F6") | Out-Null

Happy coding!

Create Azure Linux VM with worpress pre-installed

This is my first completed automated Linux Azure VM deployment. I like to share it with you.

There are 3 parts

  1. Create a keygen for ssh
  2. Powershell script
  3. Bash script

First start powershell and create a keypair with passphase

ssh-keygen -m PEM -t rsa -b 4096

Then place the bash script somewhere on your local computer

#! /bin/bash
apt-get update
apt-get install -y wordpress php libapache2-mod-php mysql-server php-mysql

echo "Alias /blog /usr/share/wordpress" >>/etc/apache2/sites-available/wordpress.conf
echo "<Directory /usr/share/wordpress>" >>/etc/apache2/sites-available/wordpress.conf
echo "    Options FollowSymLinks" >>/etc/apache2/sites-available/wordpress.conf
echo "    AllowOverride Limit Options FileInfo" >>/etc/apache2/sites-available/wordpress.conf
echo "    DirectoryIndex index.php" >>/etc/apache2/sites-available/wordpress.conf
echo "    Order allow,deny" >>/etc/apache2/sites-available/wordpress.conf
echo "    Allow from all" >>/etc/apache2/sites-available/wordpress.conf
echo "</Directory>" >>/etc/apache2/sites-available/wordpress.conf
echo "<Directory /usr/share/wordpress/wp-content>" >>/etc/apache2/sites-available/wordpress.conf
echo "    Options FollowSymLinks" >>/etc/apache2/sites-available/wordpress.conf
echo "    Order allow,deny" >>/etc/apache2/sites-available/wordpress.conf
echo "    Allow from all" >>/etc/apache2/sites-available/wordpress.conf
echo "</Directory>" >>/etc/apache2/sites-available/wordpress.conf

a2ensite wordpress
a2enmod rewrite 
reload apache2 
service apache2 reload
systemctl restart apache2

mysql -e "CREATE DATABASE wordpress;"
mysql -e "CREATE USER wordpress@localhost IDENTIFIED BY 'Secret@Pass1';"
mysql -e "GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER ON wordpress.* TO wordpress@localhost;"
mysql -e "FLUSH PRIVILEGES;"

echo "<?php" >>/etc/wordpress/config-localhost.php
echo "define('DB_NAME', 'wordpress');">>/etc/wordpress/config-localhost.php
echo "define('DB_USER', 'wordpress');">>/etc/wordpress/config-localhost.php
echo "define('DB_PASSWORD', 'Secret@Pass1');">>/etc/wordpress/config-localhost.php
echo "define('DB_HOST', 'localhost');">>/etc/wordpress/config-localhost.php
echo "define('DB_COLLATE', 'utf8_general_ci');">>/etc/wordpress/config-localhost.php
echo "define('WP_CONTENT_DIR', '/usr/share/wordpress/wp-content');">>/etc/wordpress/config-localhost.php
echo "?>">>/etc/wordpress/config-localhost.php

service mysql start


publicip=$(dig +short myip.opendns.com @resolver1.opendns.com) && mv /etc/wordpress/config-localhost.php /etc/wordpress/config-$publicip.php

Then put the code in the Powershell ISE, change some variables and kickoff the script.

The things you may need to change:

  • script.sh location

New-AzResourceGroup -Name lxautodeploy -Location westeurope

# Create a subnet configuration
$subnetConfig = New-AzVirtualNetworkSubnetConfig `
  -Name "mySubnet" `
  -AddressPrefix 192.168.1.0/24

# Create a virtual network
$vnet = New-AzVirtualNetwork `
  -ResourceGroupName "lxautodeploy" `
  -Location "westeurope" `
  -Name "myVNET" `
  -AddressPrefix 192.168.0.0/16 `
  -Subnet $subnetConfig

# Create a public IP address and specify a DNS name
$pip = New-AzPublicIpAddress `
  -ResourceGroupName "lxautodeploy" `
  -Location "westeurope" `
  -AllocationMethod Static `
  -IdleTimeoutInMinutes 4 `
  -Name "mypublicdns$(Get-Random)"


# Create an inbound network security group rule for port 22
$nsgRuleSSH = New-AzNetworkSecurityRuleConfig `
  -Name "myNetworkSecurityGroupRuleSSH"  `
  -Protocol "Tcp" `
  -Direction "Inbound" `
  -Priority 1000 `
  -SourceAddressPrefix * `
  -SourcePortRange * `
  -DestinationAddressPrefix * `
  -DestinationPortRange 22 `
  -Access "Allow"

# Create an inbound network security group rule for port 80
$nsgRuleWeb = New-AzNetworkSecurityRuleConfig `
  -Name "myNetworkSecurityGroupRuleWWW"  `
  -Protocol "Tcp" `
  -Direction "Inbound" `
  -Priority 1001 `
  -SourceAddressPrefix * `
  -SourcePortRange * `
  -DestinationAddressPrefix * `
  -DestinationPortRange 80 `
  -Access "Allow"

# Create a network security group
$nsg = New-AzNetworkSecurityGroup `
  -ResourceGroupName "lxautodeploy" `
  -Location "westeurope" `
  -Name "myNetworkSecurityGroup" `
  -SecurityRules $nsgRuleSSH,$nsgRuleWeb

  # Create a virtual network card and associate with public IP address and NSG
$nic = New-AzNetworkInterface `
  -Name "myNic" `
  -ResourceGroupName "lxautodeploy" `
  -Location "westeurope" `
  -SubnetId $vnet.Subnets[0].Id `
  -PublicIpAddressId $pip.Id `
  -NetworkSecurityGroupId $nsg.Id

  # Define a credential object
$securePassword = ConvertTo-SecureString ' ' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ("azureuser", $securePassword)

# Create a virtual machine configuration
$vmConfig = New-AzVMConfig `
  -VMName "myLXVM" `
  -VMSize "Standard_D2s_v3" | `
Set-AzVMOperatingSystem `
  -Linux `
  -ComputerName "myLXVM" `
  -Credential $cred `
  -DisablePasswordAuthentication | `
Set-AzVMSourceImage `
  -PublisherName "Canonical" `
  -Offer "UbuntuServer" `
  -Skus "18.04-LTS" `
  -Version "latest" | `
Add-AzVMNetworkInterface `
  -Id $nic.Id

# Configure the SSH key
$sshPublicKey = cat ~/.ssh/id_rsa.pub
Add-AzVMSshPublicKey `
  -VM $vmconfig `
  -KeyData $sshPublicKey `
  -Path "/home/azureuser/.ssh/authorized_keys"

New-AzVM `
  -ResourceGroupName "lxautodeploy" `
  -Location westeurope -VM $vmConfig

Get-AzPublicIpAddress -ResourceGroupName "lxautodeploy" | Select "IpAddress"



Invoke-AzVMRunCommand -ResourceGroupName "lxautodeploy" -Name 'myLXVM' -CommandId 'RunShellScript' -ScriptPath "script.sh" -Verbose

Now you can go to http://<publicip>/blog to access the new blog

You can access the server with ssh azureuser@<publicip>

Have fun with it!