@@ -131,112 +131,83 @@ function Get-ApplicationName {
131131 return $appName
132132}
133133
134- function Get-PrettifyName {
135- param (
136- [string ]$Name
137- )
138-
139- if ($Name -match ' \.([^\.]+)$' ) {
140- $ProductName = $Matches [1 ]
141- } else {
142- # If no period is found, use the whole name.
143- $ProductName = $Name
144- }
145-
146- $PrettyName = ($ProductName -creplace ' ([A-Z\W_]|\d+)(?<![a-z])' , ' $&' ).trim()
147-
148- return $PrettyName
149- }
150-
151134# Gets the display name for a UWP app.
152135function Get-UWPApplicationName {
153136 param (
137+ [string ]$exePath , # The resolved executable path
154138 $app # The AppxPackage object
155139 )
140+
156141 # UWP properties are usually the best source
157- if ($app.DisplayName ) {
158- return $app.DisplayName.Trim ()
159- # Write-Host($app)
142+ if ($app.DisplayName ) { return $app.DisplayName.Trim () }
143+ if ($app.Name ) { return $app.Name.Trim () } # Often the package name, less ideal but a fallback
144+
145+ # If UWP properties fail, try standard name extraction on the EXE
146+ if (Test-Path $exePath - PathType Leaf) {
147+ return Get-ApplicationName - targetPath $exePath
160148 }
161- if ($app.Name ) {
162- return Get-PrettifyName - Name $app.Name
163- } # Often the package name, less ideal but a fallback
164149
165150 return $null # Failed to get a name
166151}
167152
168- function Get-ParseUWP {
153+ # Parses the AppxManifest.xml to find the primary executable path for a UWP app.
154+ function Get-UWPExecutablePath {
169155 param ([string ]$instLoc ) # InstallLocation from Get-AppxPackage
170156
171157 $manifestPath = Join-Path - Path $instLoc - ChildPath " AppxManifest.xml"
172158 if (-not (Test-Path $manifestPath - PathType Leaf)) {
173159 return $null # Manifest doesn't exist or isn't a file
174160 }
175161
176- $xmlContent = Get-Content $manifestPath - Raw - Encoding Default - ErrorAction Stop
177-
178- $appId = " App" # Default, as it works for most packages.
179- if ($xmlContent -match ' <Application[^>]*Id\s*=\s*"([^"]+)"' ) {
180- $appId = $Matches [1 ]
181- }
182-
183- $logoMatch = [regex ]::Match($xmlContent , ' <Properties.*?>.*?<Logo>(.*?)</Logo>.*?</Properties>' , [System.Text.RegularExpressions.RegexOptions ]::Singleline)
184- if ($logoMatch.Success ) {
185- $logo = $logoMatch.Groups [1 ].Value
186- }
162+ try {
163+ # Read manifest content, default encoding often works
164+ $xmlContent = Get-Content $manifestPath - Raw - Encoding Default - ErrorAction Stop
165+
166+ # Remove known namespace prefixes and xmlns attributes to simplify XML parsing
167+ $prefixesToRemove = ' uap10' , ' uap' , ' desktop' , ' rescap' , ' com' # Common prefixes
168+ $cleanedXml = $xmlContent
169+ foreach ($prefix in $prefixesToRemove ) {
170+ # Regex to remove 'prefix:' from start/end tags and self-closing tags
171+ $cleanedXml = $cleanedXml -replace " (</?)$prefix `:" , ' $1' `
172+ -replace " <$prefix `:([^>\s]+?)\s*/>" , " <$1 />"
173+ }
174+ # Remove the xmlns declarations themselves
175+ $cleanedXml = $cleanedXml -replace ' xmlns(:\w+)?="[^"]+"' , ' '
187176
188- return $logo , $appId
189- }
177+ # Attempt to parse the cleaned XML
178+ [ xml ] $manifest = $cleanedXml
190179
191- function Get-UWPBase64Logo {
192- param ([string ]$logo , [string ]$instLoc )
180+ # Find the first <Application> node
181+ $appNode = $manifest.Package.Applications.Application | Select-Object - First 1
182+ if (-not $appNode ) { return $null } # No application defined
193183
194- $logoPath = Join-Path - Path $instLoc - ChildPath $logo
184+ # Get the 'Executable' attribute value
185+ $exeRelPath = $appNode.Executable
186+ if (-not $exeRelPath ) { return $null } # No executable attribute
195187
196- if (-not (Test-Path $logoPath )) {
197- # if base file not exist, attempt to find scaled version.
198- $scaledVersions = @ (" scale-100" , " scale-200" , " scale-400" )
199- foreach ($scale in $scaledVersions ) {
200- $scaledLogoPath = $logoPath -replace ' \.png$' , " .$scale .png"
201- if (Test-Path $scaledLogoPath ) {
202- $logoPath = $scaledLogoPath
203- break
188+ # Handle special $targetnametoken$.exe case by finding the first EXE in the root
189+ if ($exeRelPath -like ' *$targetnametoken$.exe*' ) {
190+ $candidateExe = Get-ChildItem - Path $instLoc - Filter * .exe - File - ErrorAction SilentlyContinue | Select-Object - First 1
191+ if ($candidateExe -and (Test-Path $candidateExe.FullName - PathType Leaf)) {
192+ return $candidateExe.FullName
193+ }
194+ } else {
195+ # Handle regular relative path
196+ $fullPath = Join-Path - Path $instLoc - ChildPath $exeRelPath
197+ if (Test-Path $fullPath - PathType Leaf) {
198+ return $fullPath
204199 }
205200 }
206-
207- # null if no valid file found.
208- if (-not (Test-Path $logoPath )) {
209- return $null
210- }
211- }
212-
213- try {
214- $image = [System.Drawing.Image ]::FromFile($logoPath )
215-
216- # resize to 32x32
217- $resizedBmp = New-Object System.Drawing.Bitmap(32 , 32 )
218- $graphics = [System.Drawing.Graphics ]::FromImage($resizedBmp )
219- $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode ]::HighQualityBicubic
220- $graphics.DrawImage ($image , 0 , 0 , 32 , 32 )
221-
222- # Save as PNG to memory stream
223- $stream = New-Object System.IO.MemoryStream
224- $resizedBmp.Save ($stream , [System.Drawing.Imaging.ImageFormat ]::Png)
225-
226- $base64 = [Convert ]::ToBase64String($stream.ToArray ())
227-
228- # Clean up
229- $stream.Dispose ()
230- $graphics.Dispose ()
231- $resizedBmp.Dispose ()
232- $image.Dispose ()
233-
234- return $base64
235201 } catch {
202+ # Ignore any parsing errors and return null
236203 return $null
237204 }
205+
206+ # Default return if no valid path found
207+ return $null
238208}
239209
210+
240211# --- Main Application Logic ---
241212
242213# Use efficient List and HashSet for collection and deduplication
@@ -249,45 +220,14 @@ function Add-AppToListIfValid {
249220 param (
250221 [string ]$Name ,
251222 [string ]$InputPath , # The path discovered (can be relative or contain variables)
252- [string ]$Source , # Source type (e.g., 'system', 'winreg', 'startmenu')
253-
254- [string ]$logoBase64 , # Optional base64 logo, mainly for UWP logo discover system
255- [string ]$launchArg # optional arg
223+ [string ]$Source # Source type (e.g., 'system', 'winreg', 'startmenu')
256224 )
257225
258226 $resolved = $null
259227 $fullPath = $null
260228 $normalizedPathKey = $null
261229
262- # Step 1, handle UWP app.
263- if ($Source -eq " uwp" ) {
264- if (-not $launchArg ) {
265- return # launching UWP app requires Arg
266- }
267-
268- $normalizedPathKey = $InputPath.ToLowerInvariant () + $launchArg.ToLowerInvariant ()
269- if ($addedPaths.Contains ($normalizedPathKey )) {
270- return # Already added this app
271- }
272-
273- $icon = $null
274- if ($logoBase64 ) {
275- $icon = $logoBase64
276- }
277-
278- # UWP object
279- $apps.Add ([PSCustomObject ]@ {
280- Name = $Name
281- Path = $InputPath
282- Args = $launchArg
283- Icon = $icon
284- Source = ' uwp'
285- })
286- $addedPaths.Add ($normalizedPathKey ) | Out-Null
287- return
288- }
289-
290- # 2. Resolve and Validate Path Other
230+ # 1. Resolve and Validate Path
291231 try {
292232 $resolved = Resolve-Path - Path $InputPath - ErrorAction SilentlyContinue
293233 } catch { return } # Ignore if path resolution throws error
@@ -301,29 +241,28 @@ function Add-AppToListIfValid {
301241 return # Skip if path doesn't resolve to a valid file
302242 }
303243
304- # 3 . Validate Name (Basic Check)
244+ # 2 . Validate Name (Basic Check)
305245 if (-not $Name -or $Name.Trim ().Length -eq 0 -or $Name -like ' Microsoft? Windows? Operating System*' ) {
306246 return # Skip if name is empty, invalid, or generic OS name
307247 }
308248
309- # 4 . Check for Duplicates using normalized path
249+ # 3 . Check for Duplicates using normalized path
310250 if ($addedPaths.Contains ($normalizedPathKey )) {
311251 return # Skip if this exact executable path has already been added
312252 }
313253
314- # 5 . Get Icon
254+ # 4 . Get Icon
315255 $icon = Get-ApplicationIcon - targetPath $fullPath
316256
317- # 6 . Add the Application Object (matching WinApp type)
257+ # 5 . Add the Application Object (matching WinApp type)
318258 $apps.Add ([PSCustomObject ]@ {
319259 Name = $Name
320260 Path = $fullPath # Use the resolved, non-normalized path for output
321- Args = " "
322261 Icon = $icon
323262 Source = $Source
324263 })
325264
326- # 7 . Mark Path as Added
265+ # 6 . Mark Path as Added
327266 $addedPaths.Add ($normalizedPathKey ) | Out-Null
328267}
329268
@@ -513,9 +452,9 @@ if ($lnkFiles) {
513452
514453
515454# 4. UWP Apps
516- if (Get-Command Get-AppxPackage - AllUsers - ErrorAction SilentlyContinue) {
455+ if (Get-Command Get-AppxPackage - ErrorAction SilentlyContinue) {
517456 try {
518- Get-AppxPackage - AllUsers - ErrorAction SilentlyContinue |
457+ Get-AppxPackage - ErrorAction SilentlyContinue |
519458 Where-Object {
520459 $_.IsFramework -eq $false -and
521460 $_.IsResourcePackage -eq $false -and
@@ -524,18 +463,14 @@ if (Get-Command Get-AppxPackage -AllUsers -ErrorAction SilentlyContinue) {
524463 } |
525464 ForEach-Object {
526465 $app = $_
527- $pathValue = " explorer.exe"
528-
529- # Attempt to find logo and executable using Appxmanifest.xml
530- $logo , $appId = Get-ParseUWP - instLoc $app.InstallLocation
531- $launchArgs = " shell:AppsFolder\" + $app.PackageFamilyName + ' !' + $appId
466+ # Attempt to find the executable path using the manifest
467+ $exePath = Get-UWPExecutablePath - instLoc $app.InstallLocation
532468
533- if ($appId ) { # check if we have appId
469+ if ($exePath ) { # Function already validates path is a file
534470 # Get the best display name (UWP properties preferred)
535471 $name = Get-UWPApplicationName - exePath $exePath - app $app
536472 if ($name ) {
537- $base64 = Get-UWPBase64Logo - logo $logo - instLoc $app.InstallLocation
538- Add-AppToListIfValid - Name $name - InputPath $pathValue - Source " uwp" - logoBase64 $base64 - launchArg $launchArgs
473+ Add-AppToListIfValid - Name $name - InputPath $exePath - Source " uwp"
539474 }
540475 }
541476 }
@@ -632,7 +567,4 @@ if ($scoopDir) {
632567
633568# Convert the final list of application objects to a compressed JSON string.
634569# This is the only output sent to the standard output stream.
635-
636- $apps | ConvertTo-Json - Depth 5 - Compress
637-
638-
570+ $apps | ConvertTo-Json - Depth 5 - Compress
0 commit comments