Improves Winget search performance and user feedback

Refactors the Winget search functionality to enhance performance and provide a better user experience.

- Updates the UI to show a wait cursor, disable the search button, and display a status message during the search operation.
- Improves the post-search status message to differentiate between new and total applications found.
- Utilizes `ForEach-Object -Parallel` to speed up the processing of search results on multi-core systems.
This commit is contained in:
rbalsleyMSFT
2025-07-24 15:24:01 -07:00
parent 138fd1184c
commit 93f6eeac87
@@ -11,10 +11,15 @@ function Search-WingetApps {
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[psobject]$State [psobject]$State
) )
try {
$searchQuery = $State.Controls.txtWingetSearch.Text
if ([string]::IsNullOrWhiteSpace($searchQuery)) { return }
$searchQuery = $State.Controls.txtWingetSearch.Text
if ([string]::IsNullOrWhiteSpace($searchQuery)) { return }
$State.Controls.txtStatus.Text = "Searching Winget for apps matching query '$searchQuery'..."
$State.Window.Cursor = [System.Windows.Input.Cursors]::Wait
$State.Controls.btnWingetSearch.IsEnabled = $false
try {
# Get current items from the ListView # Get current items from the ListView
$currentItemsInListView = @() $currentItemsInListView = @()
if ($null -ne $State.Controls.lstWingetResults.ItemsSource) { if ($null -ne $State.Controls.lstWingetResults.ItemsSource) {
@@ -33,8 +38,6 @@ function Search-WingetApps {
# Search for new apps, which are streamed directly as PSCustomObjects # Search for new apps, which are streamed directly as PSCustomObjects
# with the required properties for performance. # with the required properties for performance.
$searchedAppResults = Search-WingetPackagesPublic -Query $searchQuery -DefaultArchitecture $defaultArch $searchedAppResults = Search-WingetPackagesPublic -Query $searchQuery -DefaultArchitecture $defaultArch
WriteLog "Found $($searchedAppResults.Count) apps matching query '$searchQuery'."
$finalAppList = [System.Collections.Generic.List[object]]::new() $finalAppList = [System.Collections.Generic.List[object]]::new()
$addedAppIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $addedAppIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
@@ -45,18 +48,37 @@ function Search-WingetApps {
} }
# Add new search results, avoiding duplicates of already added (selected) apps # Add new search results, avoiding duplicates of already added (selected) apps
$newAppsAddedCount = 0
foreach ($result in $searchedAppResults) { foreach ($result in $searchedAppResults) {
if (-not $addedAppIds.Contains($result.Id)) { # HashSet.Add returns $true if the item was added, $false if it already existed.
if ($addedAppIds.Add($result.Id)) {
$finalAppList.Add($result) $finalAppList.Add($result)
$addedAppIds.Add($result.Id) | Out-Null # Track added IDs to prevent duplicates from search results themselves $newAppsAddedCount++
} }
} }
# Update the ListView's ItemsSource using the passed-in State object # Update the ListView's ItemsSource using the passed-in State object
$State.Controls.lstWingetResults.ItemsSource = $finalAppList.ToArray() $State.Controls.lstWingetResults.ItemsSource = $finalAppList.ToArray()
# Update status text
$statusText = ""
if ($newAppsAddedCount -gt 0) {
$statusText = "Found $newAppsAddedCount new applications. "
}
else {
$statusText = "No new applications found. "
}
$statusText += "Displaying $($finalAppList.Count) total applications."
$State.Controls.txtStatus.Text = $statusText
} }
catch { catch {
[System.Windows.MessageBox]::Show("Error searching for apps: $_", "Error", "OK", "Error") $errorMessage = "Error searching for apps: $($_.Exception.Message)"
$State.Controls.txtStatus.Text = $errorMessage
[System.Windows.MessageBox]::Show($errorMessage, "Error", "OK", "Error")
}
finally {
$State.Window.Cursor = $null
$State.Controls.btnWingetSearch.IsEnabled = $true
} }
} }
@@ -160,18 +182,24 @@ function Search-WingetPackagesPublic {
WriteLog "Searching Winget packages with query: '$Query'" WriteLog "Searching Winget packages with query: '$Query'"
try { try {
# Stream results directly from Find-WinGetPackage and convert them to simple PSCustomObjects # Using ForEach-Object -Parallel can speed up object creation on multi-core systems
# on the fly using Select-Object with calculated properties. This is significantly faster # by distributing the work across multiple threads.
# for large datasets as it avoids holding complex objects in memory and bypasses the $results = Find-WinGetPackage -Query $Query -ErrorAction Stop
# expensive formatting system for the raw results. WriteLog "Found $($results.Count) packages matching query '$Query'."
Find-WinGetPackage -Query $Query -ErrorAction Stop | WriteLog "Creating output objects for Winget search results, please wait..."
Select-Object -Property @{Name = 'IsSelected'; Expression = { $false } }, $output = $results | ForEach-Object -Parallel {
Name, [PSCustomObject]@{
Id, IsSelected = [bool]$false
Version, Name = [string]$_.Name
Source, Id = [string]$_.Id
@{Name = 'Architecture'; Expression = { $DefaultArchitecture } }, Version = [string]$_.Version
@{Name = 'DownloadStatus'; Expression = { '' } } Source = [string]$_.Source
Architecture = [string]$using:DefaultArchitecture
DownloadStatus = [string]::Empty
}
} -ThrottleLimit 20
WriteLog "Winget search completed. Created $($output.Count) output objects."
return $output
} }
catch { catch {
WriteLog "Error during Winget search: $($_.Exception.Message)" WriteLog "Error during Winget search: $($_.Exception.Message)"