Skip to content

Instantly share code, notes, and snippets.

@mavaddat
Last active August 21, 2023 18:11
Show Gist options
  • Save mavaddat/2174b546cab2be61c4936985a63f529a to your computer and use it in GitHub Desktop.
Save mavaddat/2174b546cab2be61c4936985a63f529a to your computer and use it in GitHub Desktop.
PowerShell Core function to search Winget (aka Windows Package Manager) logs
#Requires -Version 6.0
<#
.SYNOPSIS
Retrieves recent winget logs and searches them for a pattern, if specified.
.DESCRIPTION
This script parses the Windows Package Manager winget command-line tool log files and allows searching by specifying a pattern or date boundaries.
.PARAMETER Search
The pattern to search for in the log files. If not specified, all log entries are returned.
.PARAMETER AfterDate
The date after which to search for log entries. If not specified, all log entries are returned.
.PARAMETER BeforeDate
The date before which to search for log entries. If not specified, all log entries are returned.
.PARAMETER ErrorsOnly
If specified, only log entries with a source of FAIL are returned.
.PARAMETER HighlightPaths
If specified, paths in the log entries are highlighted.
.EXAMPLE
PS C:\> Get-WingetLogs -Search 'winget install' -AfterDate (Get-Date).AddDays(-1)
# This example retrieves all winget log entries containing the text 'winget install' from the last 24 hours.
.EXAMPLE
PS C:\> Get-WingetLogs -Search 'winget install' -AfterDate (Get-Date).AddDays(-1) -HighlightPaths
# This example retrieves all winget log entries containing the text 'winget install' from the last 24 hours and highlights the paths in the log entries.
.EXAMPLE
PS C:\> Get-WingetLogs -Search 'winget install' -AfterDate (Get-Date).AddDays(-1) -ErrorsOnly
# This example retrieves all winget log entries containing the text 'winget install' from the last 24 hours and only returns log entries with a source of FAIL.
.EXAMPLE
PS C:\> Get-WingetLogs -Search 'winget install' -AfterDate (Get-Date).AddDays(-1) -BeforeDate (Get-Date).AddHours(-1)
# This example retrieves all winget log entries containing the text 'winget install' from the last 24 hours, but only returns log entries from the last 23 hours.
.EXAMPLE
PS C:\> Get-WingetLogs -Search 'winget install' -AfterDate (Get-Date).AddDays(-1) -BeforeDate (Get-Date).AddHours(-1) -HighlightPaths
# This example retrieves all winget log entries containing the text 'winget install' from the last 24 hours, but only returns log entries from the last 23 hours and highlights the paths in the log entries.
.EXAMPLE
PS C:\> Get-WingetLogs -Search 'winget install' -AfterDate (Get-Date).AddDays(-1) -BeforeDate (Get-Date).AddHours(-1) -ErrorsOnly
# This example retrieves all winget log entries containing the text 'winget install' from the last 24 hours, but only returns log entries from the last 23 hours and only returns log entries with a source of FAIL.
.EXAMPLE
PS C:\> Get-WingetLogs -Search 'winget install' -AfterDate (Get-Date).AddDays(-1) -BeforeDate (Get-Date).AddHours(-1) -ErrorsOnly -HighlightPaths
# This example retrieves all winget log entries containing the text 'winget install' from the last 24 hours, but only returns log entries from the last 23 hours, only returns log entries with a source of FAIL, and highlights the paths in the log entries.
.NOTES
Author: Mavaddat Javid
Date: 2023-06-03
Version: 1.0
#>
function Get-WingetLogs {
[CmdletBinding()]
param (
# Parameter help description
[Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 0)]
[Alias('Filter', 'Pattern')]
[string]
$Search,
[Alias('After')]
[Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 1)]
[datetime]
$AfterDate = ([datetime]::MinValue),
[Alias('Before')]
[Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 2)]
[datetime]
$BeforeDate = ([datetime]::MaxValue),
[switch]
$ErrorsOnly,
[switch]
$HighlightPaths,
[Alias('Symbols','Icons')]
[switch]
$Emojis
)
begin {
class WingetLogs {
[datetime]$TimeStamp
[string]$Source
[string]$Message
WingetLogs([psobject]$IncomingObj) {
$this.TimeStamp = $IncomingObj.TimeStamp
$this.Source = $IncomingObj.Source
$this.Message = $IncomingObj.Message
}
}
[array]$WingetLogFiles = Get-ChildItem -Path ("$env:LOCALAPPDATA\Packages\Microsoft.DesktopAppInstaller*\LocalState\DiagOutputDir" | Resolve-Path ) -File
Write-Verbose -Message "Found $($WingetLogFiles.Count) Winget log files"
}
process {
$WingetLogs = $WingetLogFiles | ForEach-Object -Begin {
$numRecords = 0
Write-Verbose -Message "Searching for Winget log files in $($WingetLogFiles[0].DirectoryName)"
} -Process {
Write-Verbose -Message "Processing Winget log file $($_.FullName)"
Write-Progress -Activity "Parsing $($_.Name)" -Status "File $($WingetLogFiles.IndexOf($_)) out of $($WingetLogFiles.Count)" -PercentComplete (($WingetLogFiles.IndexOf($_) / $WingetLogFiles.Count) * 100)
$_ | Get-Content | Select-String -Pattern '^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[([^\]]+)\] (.+)$' | ForEach-Object {
$numRecords++
$source = $_.Matches.Groups[2].Value.Trim()
$message = ($source -eq 'FAIL') ? ($_.Matches.Groups[3].Value -replace '^(.+)$', "`e[31m`$1`e[0m") : ($_.Matches.Groups[3].Value)
if ($Emojis) {
$source = switch ($source) {
CLI { "🖥️"; break }
CORE { "🧰"; break }
FAIL { "❌"; break }
REPO { "📦"; break }
SQL { "🗄️"; break }
default { $source }
}
}
else {
$source = switch ($source) {
CLI { "`e[32m$source`e[0m"; break }
CORE { "`e[33m$source`e[0m"; break }
FAIL { "`e[31m$source`e[0m"; break }
REPO { "`e[34m$source`e[0m"; break }
SQL { "`e[35m$source`e[0m"; break }
default { $source }
}
}
if ($HighlightPaths) {
$pathsInMsg = $message | Select-String -Pattern '(?:([''"])[A-Z]:\\[^:!''"]+\1)|(?:[A-Z]:\\[^\s:!]+)' -AllMatches | ForEach-Object { $_.Matches.Value }
# Surround the paths with ANSI escape codes to highlight them
$pathsInMsg | ForEach-Object {
# Replace the $Search with the ANSI escape codes
$pathHighlighted = $_ -replace "($Search)", "`e[7m`$1`e[0m`e[36m" # Highlight the search term in the path
$message = $message -replace "$Search(.*)(?=$([regex]::Escape($_)))", "`e[7m$Search`e[0m`$1" # Move the path highlight start before the search highlight start
$message = $message -replace "(?<=$([regex]::Escape($_)))(.*)$Search", "`$1`e[7m$Search`e[0m" # Move the path highlight end after the search highlight end
$message = $message -replace "($([regex]::Escape($_)))", "`e[36m$pathHighlighted`e[0m" # Replace the path with the highlighted path
}
}
elseif (-not [string]::IsNullOrWhiteSpace($Search)) {
$message = $message -replace "($Search)", "`e[7m`$1`e[0m"
}
# add background color to the filter text
$message = $message -replace "((`e\[\d+m).*)?($Search)", "`$1`e[7m`$3`e[0m`$2"
[WingetLogs]::new(@{
TimeStamp = [datetime]::Parse(($_.Matches.Groups[1].Value))
Source = $source
Message = $message
})
} | Where-Object -FilterScript { $_.TimeStamp -ge $AfterDate -and $_.TimeStamp -le $BeforeDate -and $_.Message -match $Search }
} -End {
Write-Verbose -Message "Found $numRecords records in $($WingetLogFiles[0].DirectoryName)"
}
}
end {
if ($ErrorsOnly) {
return $WingetLogs | Where-Object -FilterScript { $_.Source -eq 'FAIL' }
}
else {
return $WingetLogs
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment