Skip to content

Commit 5f9d116

Browse files
authored
Merge pull request #4490 from Flow-Launcher/fix-shell-link-buffer-issue
Program Plugin: Refactor ShellLinkReader and fix argument/description retrieval
2 parents 02c8dfd + 1de6d75 commit 5f9d116

5 files changed

Lines changed: 150 additions & 94 deletions

File tree

Plugins/Flow.Launcher.Plugin.Program/NativeMethods.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,13 @@ SLR_FLAGS
1010
IShellItem
1111
SHCreateItemFromParsingName
1212
IShellLinkW
13-
CoTaskMemFree
13+
ShellLink
14+
CoTaskMemFree
15+
IPropertyStore
16+
PROPERTYKEY
17+
PROPVARIANT
18+
PKEY_Link_Arguments
19+
PropVariantClear
20+
VARENUM
21+
INFOTIPSIZE
22+
MAX_PATH

Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs

Lines changed: 0 additions & 85 deletions
This file was deleted.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
namespace Flow.Launcher.Plugin.Program.Programs
2+
{
3+
public readonly record struct ShellLinkReadResult(string TargetPath, string Description, string Arguments);
4+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using System.Runtime.InteropServices.ComTypes;
4+
using Flow.Launcher.Plugin.Program.Logger;
5+
using Windows.Win32;
6+
using Windows.Win32.Foundation;
7+
using Windows.Win32.System.Com;
8+
using Windows.Win32.System.Com.StructuredStorage;
9+
using Windows.Win32.System.Variant;
10+
using Windows.Win32.UI.Shell;
11+
using Windows.Win32.UI.Shell.PropertiesSystem;
12+
using Windows.Win32.Storage.FileSystem;
13+
14+
namespace Flow.Launcher.Plugin.Program.Programs
15+
{
16+
public static class ShellLinkReader
17+
{
18+
// Retrieves the target path, arguments, and description from a shell link
19+
public static ShellLinkReadResult Read(string path)
20+
{
21+
var link = new ShellLink();
22+
try
23+
{
24+
try
25+
{
26+
((IPersistFile)link).Load(path, (int)STGM.STGM_READ);
27+
var hwnd = new HWND(IntPtr.Zero);
28+
// Use SLR_NO_UI to avoid showing any UI during resolution, like Problem with Shortcut dialogs
29+
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishelllinka-resolve
30+
((IShellLinkW)link).Resolve(hwnd, (uint)SLR_FLAGS.SLR_NO_UI);
31+
}
32+
catch (COMException e)
33+
{
34+
ProgramLogger.LogException(
35+
$"|ShellLinkReader|Read|{path}|Error occurred while loading or resolving shell link",
36+
e
37+
);
38+
return new ShellLinkReadResult(string.Empty, string.Empty, string.Empty);
39+
}
40+
41+
var target = retrieveTargetPath((IShellLinkW)link, path);
42+
var description = retrieveDescription((IShellLinkW)link, path);
43+
var arguments = retrieveArguments((IPropertyStore)link, path);
44+
return new ShellLinkReadResult(target, description, arguments);
45+
}
46+
finally
47+
{
48+
// release unmanaged memory
49+
Marshal.ReleaseComObject(link);
50+
}
51+
}
52+
53+
private static unsafe string retrieveTargetPath(IShellLinkW shellLink, string path)
54+
{
55+
var data = new WIN32_FIND_DATAW();
56+
try
57+
{
58+
Span<char> targetBuffer = stackalloc char[(int)PInvoke.MAX_PATH];
59+
targetBuffer.Clear();
60+
fixed (char* targetBufferPtr = targetBuffer)
61+
{
62+
shellLink.GetPath((PWSTR)targetBufferPtr, (int)PInvoke.MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH);
63+
return MemoryMarshal.CreateReadOnlySpanFromNullTerminated(targetBufferPtr).ToString();
64+
}
65+
}
66+
catch (COMException e)
67+
{
68+
ProgramLogger.LogException($"|ShellLinkReader|retrieveTargetPath|{path}" +
69+
"|Error occurred while getting program target path from shell link", e);
70+
return string.Empty;
71+
}
72+
}
73+
74+
private static unsafe string retrieveDescription(IShellLinkW shellLink, string path)
75+
{
76+
try
77+
{
78+
Span<char> descriptionBuffer = stackalloc char[(int)PInvoke.INFOTIPSIZE];
79+
descriptionBuffer.Clear();
80+
fixed (char* descriptionBufferPtr = descriptionBuffer)
81+
{
82+
shellLink.GetDescription(descriptionBufferPtr, (int)PInvoke.INFOTIPSIZE);
83+
return MemoryMarshal.CreateReadOnlySpanFromNullTerminated(descriptionBufferPtr).ToString();
84+
}
85+
}
86+
catch (COMException e)
87+
{
88+
// C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception
89+
ProgramLogger.LogException(
90+
$"|ShellLinkReader|retrieveDescription|{path}" +
91+
"|Error caused likely due to trying to get the description of the program",
92+
e
93+
);
94+
return string.Empty;
95+
}
96+
}
97+
98+
private static string retrieveArguments(IPropertyStore shellLinkPropertyStore, string path)
99+
{
100+
PROPVARIANT argumentsProperty = new PROPVARIANT();
101+
102+
try
103+
{
104+
var argumentsKey = PInvoke.PKEY_Link_Arguments;
105+
shellLinkPropertyStore.GetValue(in argumentsKey, out argumentsProperty);
106+
107+
// CsWin32 preserves native C unions, so nested union fields are generated as "Anonymous".
108+
// see structure at https://learn.microsoft.com/en-ie/windows/win32/api/propidlbase/ns-propidlbase-propvariant#syntax
109+
var propVariantHeader = argumentsProperty.Anonymous.Anonymous;
110+
var propVariantValueUnion = propVariantHeader.Anonymous;
111+
var propVariantType = propVariantHeader.vt;
112+
113+
return propVariantType switch
114+
{
115+
VARENUM.VT_EMPTY => string.Empty,
116+
VARENUM.VT_LPWSTR => propVariantValueUnion.pwszVal.ToString(),
117+
_ => string.Empty
118+
};
119+
}
120+
catch (COMException e)
121+
{
122+
ProgramLogger.LogException($"|ShellLinkReader|retrieveArguments|{path}|Error occurred while getting program arguments", e);
123+
return string.Empty;
124+
}
125+
finally
126+
{
127+
PInvoke.PropVariantClear(ref argumentsProperty);
128+
}
129+
}
130+
}
131+
}

Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.IO;
44
using System.Linq;
55
using System.Security;
6-
using System.Text;
76
using System.Threading.Tasks;
87
using Microsoft.Win32;
98
using Flow.Launcher.Plugin.Program.Logger;
@@ -336,24 +335,22 @@ private static Win32 LnkProgram(string path)
336335
{
337336
var program = Win32Program(path);
338337
try
339-
{
340-
const int MAX_PATH = 260;
341-
StringBuilder buffer = new StringBuilder(MAX_PATH);
342-
ShellLinkHelper _helper = new ShellLinkHelper();
343-
string target = _helper.retrieveTargetPath(path);
338+
{
339+
var shellLink = ShellLinkReader.Read(path);
340+
string target = shellLink.TargetPath;
344341

345342
if (!string.IsNullOrEmpty(target) && File.Exists(target))
346343
{
347344
program.LnkResolvedPath = Path.GetFullPath(target);
348345
program.ExecutableName = Path.GetFileNameWithoutExtension(target);
349346

350-
var args = _helper.arguments;
347+
var args = shellLink.Arguments;
351348
if (!string.IsNullOrEmpty(args))
352349
{
353350
program.Args = args;
354351
}
355352

356-
var description = _helper.description;
353+
var description = shellLink.Description;
357354
if (!string.IsNullOrEmpty(description))
358355
{
359356
program.Description = description;

0 commit comments

Comments
 (0)