1313using System . Text ;
1414using System . Threading ;
1515using System . Threading . Tasks ;
16+ using System . Xml ;
1617using Windows . Storage ;
1718using Windows . Storage . FileProperties ;
1819using Windows . Storage . Streams ;
@@ -40,9 +41,146 @@ public static string FindOrigIcon(string icoFilePath) {
4041 }
4142
4243
44+ private static async Task < Bitmap > ExtractWindowsAppIconAsync ( string shortcutPath , string outputDirectory ) {
45+ try {
46+ // Get the shortcut target using Shell COM objects
47+ Type shellType = Type . GetTypeFromProgID ( "Shell.Application" ) ;
48+ if ( shellType == null ) return null ;
49+
50+ dynamic shell = Activator . CreateInstance ( shellType ) ;
51+ dynamic folder = shell . Namespace ( Path . GetDirectoryName ( shortcutPath ) ) ;
52+ dynamic shortcutItem = folder . ParseName ( Path . GetFileName ( shortcutPath ) ) ;
53+
54+ // Find the "Link target" property
55+ string linkTarget = null ;
56+ for ( int i = 0 ; i < 500 ; i ++ ) {
57+ string propertyName = folder . GetDetailsOf ( null , i ) ;
58+ if ( propertyName == "Link target" ) {
59+ linkTarget = folder . GetDetailsOf ( shortcutItem , i ) ;
60+ break ;
61+ }
62+ }
63+
64+ if ( string . IsNullOrEmpty ( linkTarget ) ) return null ;
65+
66+ // Extract the app name from the link target (remove everything after the first "_")
67+ string appName = System . Text . RegularExpressions . Regex . Replace ( linkTarget , "_.*$" , "" ) ;
68+ if ( string . IsNullOrEmpty ( appName ) ) return null ;
69+
70+ // Use Windows Runtime API to find the package
71+ Windows . Management . Deployment . PackageManager packageManager = new Windows . Management . Deployment . PackageManager ( ) ;
72+ IEnumerable < Windows . ApplicationModel . Package > packages = packageManager . FindPackagesForUser ( "" ) ;
73+
74+ // Find the package that matches the app name
75+ Windows . ApplicationModel . Package appPackage = packages . FirstOrDefault ( p => p . Id . Name . StartsWith ( appName , StringComparison . OrdinalIgnoreCase ) ) ;
76+ if ( appPackage == null ) return null ;
77+
78+ string installPath = appPackage . InstalledLocation . Path ;
79+ string manifestPath = Path . Combine ( installPath , "AppxManifest.xml" ) ;
80+
81+ if ( ! File . Exists ( manifestPath ) ) return null ;
82+
83+ // Load and parse the manifest XML
84+ XmlDocument manifest = new XmlDocument ( ) ;
85+ manifest . Load ( manifestPath ) ;
86+
87+ // Create namespace manager
88+ XmlNamespaceManager nsManager = new XmlNamespaceManager ( manifest . NameTable ) ;
89+ nsManager . AddNamespace ( "ns" , "http://schemas.microsoft.com/appx/manifest/foundation/windows10" ) ;
90+
91+ // Get logo path from manifest
92+ XmlNode logoNode = manifest . SelectSingleNode ( "/ns:Package/ns:Properties/ns:Logo" , nsManager ) ;
93+ if ( logoNode == null ) return null ;
94+
95+ string logoPath = logoNode . InnerText ;
96+ string logoDir = Path . Combine ( installPath , Path . GetDirectoryName ( logoPath ) ) ;
97+
98+ if ( ! Directory . Exists ( logoDir ) ) return null ;
99+
100+ string [ ] logoPatterns = new [ ] {
101+
102+ "*StoreLogo*.png" ,
103+
104+ } ;
105+
106+ string highestResLogoPath = null ;
107+ long highestSize = 0 ;
43108
109+ foreach ( string pattern in logoPatterns ) {
110+ foreach ( string file in Directory . GetFiles ( logoDir , pattern , SearchOption . AllDirectories ) ) {
111+ FileInfo fileInfo = new FileInfo ( file ) ;
112+ if ( fileInfo . Length > highestSize ) {
113+ highestSize = fileInfo . Length ;
114+ highestResLogoPath = file ;
115+ }
116+ }
117+
118+ if ( highestResLogoPath != null ) break ;
119+ }
120+
121+ if ( string . IsNullOrEmpty ( highestResLogoPath ) || ! File . Exists ( highestResLogoPath ) ) return null ;
122+
123+ // Load the image and resize/crop it to 200x200
124+ using ( FileStream stream = new FileStream ( highestResLogoPath , FileMode . Open , FileAccess . Read ) ) {
125+ using ( var originalBitmap = new Bitmap ( stream ) ) {
126+ // Create a square bitmap of 200x200
127+ var resizedIcon = ResizeAndCropImageToSquare ( originalBitmap , 200 ) ;
128+ return resizedIcon ;
129+ }
130+ }
131+ }
132+ catch ( Exception ex ) {
133+ Debug . WriteLine ( $ "Error extracting Windows app icon: { ex . Message } ") ;
134+ return null ;
135+ }
136+ }
44137
138+ /// <summary>
139+ /// Resizes and crops an image to a square with the specified size
140+ /// </summary>
141+ private static Bitmap ResizeAndCropImageToSquare ( Bitmap originalImage , int size , float zoomFactor = 1.1f ) {
142+ try {
143+ // Create a new square bitmap
144+ Bitmap resizedImage = new Bitmap ( size , size ) ;
145+
146+ // Calculate dimensions for maintaining aspect ratio
147+ int sourceWidth = originalImage . Width ;
148+ int sourceHeight = originalImage . Height ;
149+
150+ // Find the smallest dimension and calculate the crop area
151+ int cropSize = Math . Min ( sourceWidth , sourceHeight ) ;
152+
153+ // Apply zoom factor (smaller crop size = more zoom)
154+ cropSize = ( int ) ( cropSize / zoomFactor ) ;
155+
156+ // Center the cropping rectangle
157+ int cropX = ( sourceWidth - cropSize ) / 2 ;
158+ int cropY = ( sourceHeight - cropSize ) / 2 ;
159+
160+ // Create a graphics object to perform the resize
161+ using ( Graphics g = Graphics . FromImage ( resizedImage ) ) {
162+ // Set high quality mode for better results
163+ g . InterpolationMode = System . Drawing . Drawing2D . InterpolationMode . HighQualityBicubic ;
164+ g . SmoothingMode = System . Drawing . Drawing2D . SmoothingMode . HighQuality ;
165+ g . PixelOffsetMode = System . Drawing . Drawing2D . PixelOffsetMode . HighQuality ;
166+ g . CompositingQuality = System . Drawing . Drawing2D . CompositingQuality . HighQuality ;
167+
168+ // Draw the centered and cropped image to maintain aspect ratio
169+ g . DrawImage ( originalImage ,
170+ new Rectangle ( 0 , 0 , size , size ) ,
171+ new Rectangle ( cropX , cropY , cropSize , cropSize ) ,
172+ GraphicsUnit . Pixel ) ;
173+ }
174+
175+ return resizedImage ;
176+ }
177+ catch ( Exception ex ) {
178+ Debug . WriteLine ( $ "Error resizing image: { ex . Message } ") ;
179+ return null ;
180+ }
181+ }
45182
183+ // Also modify the main ExtractIconAndSaveAsync method to use this resize function for all icons
46184 public static async Task < string > ExtractIconAndSaveAsync ( string filePath , string outputDirectory , TimeSpan ? timeout = null ) {
47185 timeout ??= TimeSpan . FromSeconds ( 3 ) ;
48186 if ( string . IsNullOrEmpty ( filePath ) || ! File . Exists ( filePath ) ) {
@@ -54,45 +192,63 @@ public static async Task<string> ExtractIconAndSaveAsync(string filePath, string
54192 return await Task . Run ( async ( ) => {
55193 try {
56194 Bitmap iconBitmap = null ;
195+ string appName = string . Empty ;
57196 if ( Path . GetExtension ( filePath ) . ToLower ( ) == ".lnk" ) {
58- dynamic shell = Microsoft . VisualBasic . Interaction . CreateObject ( "WScript.Shell" ) ;
59- dynamic shortcut = shell . CreateShortcut ( filePath ) ;
60- string iconPath = shortcut . IconLocation ;
61- string targetPath = shortcut . TargetPath ;
62- if ( ! string . IsNullOrEmpty ( iconPath ) && iconPath != "," ) {
63- string [ ] iconInfo = iconPath . Split ( ',' ) ;
64- string actualIconPath = iconInfo [ 0 ] . Trim ( ) ;
65- int iconIndex = iconInfo . Length > 1 ? int . Parse ( iconInfo [ 1 ] . Trim ( ) ) : 0 ;
66- if ( File . Exists ( actualIconPath ) ) {
67- iconBitmap = ExtractSpecificIcon ( actualIconPath , iconIndex ) ;
197+ // Try to extract Windows app shortcut icon first
198+ iconBitmap = await ExtractWindowsAppIconAsync ( filePath , outputDirectory ) ;
199+
200+
201+ // If Windows app icon extraction failed, fall back to the existing method
202+ if ( iconBitmap == null ) {
203+ dynamic shell = Microsoft . VisualBasic . Interaction . CreateObject ( "WScript.Shell" ) ;
204+ dynamic shortcut = shell . CreateShortcut ( filePath ) ;
205+ string iconPath = shortcut . IconLocation ;
206+ string targetPath = shortcut . TargetPath ;
207+ if ( ! string . IsNullOrEmpty ( iconPath ) && iconPath != "," ) {
208+ string [ ] iconInfo = iconPath . Split ( ',' ) ;
209+ string actualIconPath = iconInfo [ 0 ] . Trim ( ) ;
210+ int iconIndex = iconInfo . Length > 1 ? int . Parse ( iconInfo [ 1 ] . Trim ( ) ) : 0 ;
211+ if ( File . Exists ( actualIconPath ) ) {
212+ iconBitmap = ExtractSpecificIcon ( actualIconPath , iconIndex ) ;
213+ }
214+ }
215+ if ( iconBitmap == null && ! string . IsNullOrEmpty ( targetPath ) && File . Exists ( targetPath ) ) {
216+ iconBitmap = ExtractIconWithoutArrow ( targetPath ) ;
217+ }
218+
219+ //Use the Icon with arrow as fallback
220+ if ( iconBitmap == null ) {
221+ Icon icon = Icon . ExtractAssociatedIcon ( filePath ) ;
222+ iconBitmap = icon . ToBitmap ( ) ;
68223 }
69- }
70- if ( iconBitmap == null && ! string . IsNullOrEmpty ( targetPath ) && File . Exists ( targetPath ) ) {
71- iconBitmap = ExtractIconWithoutArrow ( targetPath ) ;
72224 }
73225 }
74226 else {
75227 iconBitmap = ExtractIconWithoutArrow ( filePath ) ;
228+
76229 }
77230 if ( iconBitmap == null ) {
78231 Debug . WriteLine ( $ "No icon found for file: { filePath } ") ;
79232 return null ;
80233 }
81- Directory . CreateDirectory ( outputDirectory ) ;
82234
83- string iconFileName = GenerateUniqueIconFileName ( filePath , iconBitmap ) ;
84- string iconFilePath = Path . Combine ( outputDirectory , iconFileName ) ;
235+ using ( Bitmap resizedIcon = ResizeAndCropImageToSquare ( iconBitmap , 200 ) ) {
236+ Directory . CreateDirectory ( outputDirectory ) ;
237+ string iconFileName = GenerateUniqueIconFileName ( filePath , resizedIcon ) ;
238+ string iconFilePath = Path . Combine ( outputDirectory , iconFileName ) ;
85239
86- if ( File . Exists ( iconFilePath ) ) {
87- return iconFilePath ;
88- }
240+ if ( File . Exists ( iconFilePath ) ) {
241+ return iconFilePath ;
242+ }
243+
244+ using ( var stream = new FileStream ( iconFilePath , FileMode . Create ) ) {
245+ cancellationTokenSource . Token . ThrowIfCancellationRequested ( ) ;
246+ resizedIcon . Save ( stream , ImageFormat . Png ) ;
247+ }
89248
90- using ( var stream = new FileStream ( iconFilePath , FileMode . Create ) ) {
91- cancellationTokenSource . Token . ThrowIfCancellationRequested ( ) ;
92- iconBitmap . Save ( stream , ImageFormat . Png ) ;
249+ Debug . WriteLine ( $ "Icon saved to: { iconFilePath } ") ;
250+ return iconFilePath ;
93251 }
94- Debug . WriteLine ( $ "Icon saved to: { iconFilePath } ") ;
95- return iconFilePath ;
96252 }
97253 catch ( OperationCanceledException ) {
98254 Debug . WriteLine ( $ "Icon extraction timed out for: { filePath } ") ;
@@ -106,6 +262,10 @@ public static async Task<string> ExtractIconAndSaveAsync(string filePath, string
106262 }
107263 }
108264
265+
266+
267+
268+
109269 private static string GenerateUniqueIconFileName ( string filePath , Bitmap iconBitmap ) {
110270 using ( var md5 = System . Security . Cryptography . MD5 . Create ( ) ) {
111271 byte [ ] filePathBytes = System . Text . Encoding . UTF8 . GetBytes ( filePath ) ;
@@ -349,7 +509,7 @@ public static async Task<BitmapImage> ExtractIconFromFileAsync(string filePath,
349509 }
350510
351511
352- public static bool ConvertToIco ( string sourcePath , string icoFilePath ) {
512+ public static bool ConvertToIco ( string sourcePath , string icoFilePath ) {
353513 if ( string . IsNullOrEmpty ( sourcePath ) || string . IsNullOrEmpty ( icoFilePath ) ) {
354514 Debug . WriteLine ( "Invalid source or destination path." ) ;
355515 return false ;
@@ -476,6 +636,10 @@ public async Task<string> CreateGridIconAsync(
476636 System . Drawing . Bitmap iconBitmap = null ;
477637
478638 if ( Path . GetExtension ( filePath ) . ToLower ( ) == ".lnk" ) {
639+ iconBitmap = await ExtractWindowsAppIconAsync ( filePath , tempFolder ) ;
640+
641+
642+
479643 dynamic shell = Microsoft . VisualBasic . Interaction . CreateObject ( "WScript.Shell" ) ;
480644 dynamic shortcut = shell . CreateShortcut ( filePath ) ;
481645
@@ -495,6 +659,11 @@ public async Task<string> CreateGridIconAsync(
495659 if ( iconBitmap == null && ! string . IsNullOrEmpty ( targetPath ) && File . Exists ( targetPath ) ) {
496660 iconBitmap = ExtractIconWithoutArrow ( targetPath ) ;
497661 }
662+ //Use the Icon with arrow as fallback
663+ if ( iconBitmap == null ) {
664+ Icon icon = Icon . ExtractAssociatedIcon ( filePath ) ;
665+ iconBitmap = icon . ToBitmap ( ) ;
666+ }
498667 }
499668 else {
500669 iconBitmap = ExtractIconWithoutArrow ( filePath ) ;
0 commit comments