How to parse "special" .lnk files, aka. MSI shortcuts aka. Windows Installer advertised shortcuts using C#

November 19, 2007 9:21 pm

[Update 2012-02-04: Jonathan pointed out a few additional things in the comments.]

[Update 2010-01-20: It looks like the calls to MsiGetShortcutTarget() and MsiGetComponentPath() only work correctly on Windows 7 if you make them from a thread that has STA apartment state. So if you do a lengthy operation like iterating through the entire Start menu on a background thread, make sure to use the Thread class for that (remember to call SetApartmentState() before kicking it off) and not BackgroundWorker. BackgroundWorker threads are taken from a thread pool and are always MTA (they can't be forced to STA in any way). Thanks to the folks on stackoverflow for discussing and figuring this out, especially Arnout.]

This is a problem that’s been bugging me for about a year and I finally found time to dig in and work on how to solve it. A big thank you goes to Aaron Stebner for pointing me in the right direction. Thanks Aaron!

Anyway, for various reasons I’ve wanted to display a list of applications the user has installed on the computer. Getting at the All Programs folder in the Start Menu is not too big a problem. You can get parts of it via Environment.GetFolderPath(SpecialFolder.Programs) and other parts of it (for all users) via a bunch of P/Invoke incantations that I’m not going into here. Iterating through all subfolders and picking out the .lnk files is not a problem either.

What is a problem (at least it was for me) is what to do with those .lnk files that you can’t get parsed correctly via WshShell.CreateShortcutFile() after adding a reference to the Windows Script Host Object Model to your project. The .TargetPath will usually return something down in a C:\Windows\Installer\{GUID} directory.

I had noticed that the Windows Installer XML 3.0 project creates shortcuts like that, and since Aaron is involved in that project, I asked him about it. He graciously told me that those shortcuts are “advertised” shortcuts, a kind of Windows Installer shortcut that enables putting a link in the Start Menu (or elsewhere) while not necessarily installing the whole product that the link points to. So I started digging in with Google. A CodeProject article contained a comment about two functions that can be used in combination to find the real target of and advertised shortcut: MsiGetShortcutTarget() and MsiGetComponentPath(). After some more digging, I had P/Invoke declarations for both of these from this SourceForge project.

I ended up with this bit of code, which I use first on any shortcut file I need to interpret; then if it returns null, I use the WshShell way instead.

public static string ParseShortcut(string file) { 
 StringBuilder product = new StringBuilder(MaxGuidLength +1);
 StringBuilder feature = new StringBuilder(MaxFeatureLength +1);
 StringBuilder component = new StringBuilder(MaxGuidLength +1);
 MsiGetShortcutTarget(file, product, feature, component);
 int pathLength = MaxPathLength;
 StringBuilder path = new StringBuilder(pathLength);
 InstallState installState = MsiGetComponentPath( product.ToString(), component.ToString(), path, ref pathLength);
 if (installState == InstallState.Local) 
 { 
 return path.ToString();
 }
 else
  {
 return null;
 }
} 

If you’re interested, you can download the class with the rest of the needed declarations.

27 Responses to “How to parse "special" .lnk files, aka. MSI shortcuts aka. Windows Installer advertised shortcuts using C#”

[...] sure to drop by his site and grab the rest of the sample code just incase you need more info – even if it is done in [...]

Greg Zelesnik wrote a comment on May 9, 2008

This was incredibly helpful to me. Thank you for taking the time to publish this explanation and example on the web. I have been trying to figure out how to parse advertised shortcuts for many hours, and this was just the ticket. thanks!

GeekTieGuy wrote a comment on May 9, 2008

Glad to be of help. And thanks for taking the time to comment as well!

Greg Zelesnik wrote a comment on May 15, 2008

Have you ever gotten this code to work on Vista? The MsiGetShortcutTarget() method is returning nothing for advertised shortcuts on my Vista Business OS.

GeekTieGuy wrote a comment on May 15, 2008

I’ve got it to work on Vista Home Premium and Ultimate. Maybe in your case the shortcuts are for things that are truly advertised and not installed yet, i.e. there is no real target at the time you try to resolve the link? Also, I apply the WshShell method after MsiGetShortcutTarget() returns null (note I didn’t include that code in the downloadable sample). I assume you are doing that too?

Greg Zelesnik wrote a comment on May 16, 2008

Thanks for responding! I am, in fact, using the WshShell method after the MsiGetShorcutTarget() returns null from parsing a .lnk file, and it works like a champ. However, it’s weird. It’s the MS Office 2003 advertising shortcuts that are returning null in my case, and I know they’re installed as I use them daily. The weird part is that the return value fromMsiGetShortcutTarget() call is 1603, which is ERROR_INSTALL_FAILED. The WshShell method returns the icon file for these links. Not sure what’s going on here, but I’m going to try it on some other advertising links that I know I have. Thanks for responding and letting me know that it’s not a fundamental Vista issue!

GeekTieGuy wrote a comment on May 16, 2008

No problem. I haven’t seen problems with MS Office 2003 shortcuts myself, but I have seen Office 2007 shortcuts work. Maybe it depends on the install source (admin share, SMS, etc.)?

Ben wrote a comment on November 5, 2008

This post was a tremendous help. Thank you.

I am seeing similar behavior on Vista Home Premium that Greg reported. In my case, the MsiGetShortcutTarget fails on Office 2007 shortcuts that are in fact installed.

Question, does anyone have further insight on resolving advertised shortcuts on Vista?

Dirk wrote a comment on May 19, 2009

Your post was very very helpful!
My little “ToolBarFolders” tool would only be half as good without this information (but then again – it’s up to others to tell how good my tool is anyway.)
I’ll have to try it on Vista though – maybe I’ll fire up a VM later.

Gaurav wrote a comment on October 6, 2009

Immensely helpful…Thank You… :)

I am trying to use your code on an XP machine (VS 2008 in a WPF application) along with the WshShell method but, have run into a weird problem. The return value from MsiGetShortcutTarget is 0 (indicating ERROR_SUCCESS, i.e. the function call was successful), but the InstallState returned from MsiGetComponentPath is InstallState.InvalidArg.

I haven’t modified your code (except for the namespace), so the error is quite puzzling…

Any ideas?

GeekTieGuy wrote a comment on October 7, 2009

Well, I guess I’d trust the InstallState.InvalidArg and look that up. Maybe the product isn’t installed properly. Or maybe the shortcut you’re trying to open isn’t an advertised shortcut, but some other type. Maybe a Shell Namespace item or something like that.

JackZhong wrote a comment on October 28, 2009

Thank you very much,This problem has troubled me for long time ,now see your code ,wo have resolved
it,very gratefull to you!

GeekTieGuy wrote a comment on October 28, 2009

Glad it helped you. Thanks for leaving your comment.

Matthew Clark wrote a comment on October 29, 2009

Lifesaver! I had to convert this to VB.NET, but I couldn’t have come up with a solution without your article. Thanks to you, I went from a blank .vb module to a perfect solution.

GeekTieGuy wrote a comment on October 29, 2009

Cool! Glad you found it useful.

Man wrote a comment on November 20, 2009

Thank you soooo much, useful, clear and simple and works like a charm too. Perfect! :)

TcoUpLoad wrote a comment on September 6, 2010

You can convert that class into vbnet DLL?

GeekTieGuy wrote a comment on September 6, 2010

I don’t see why it couldn’t be converted to VB.NET. Are you asking me if I can do it for you?

Goverdhan A wrote a comment on October 4, 2010

I am trying to use the class MsiShortcutParser.cs as I have the requirement to read the target path from link file. I tried using IWshShortcut.CreateShortcut(filename) which works in most of the cases. This doesn’t work for me when i try to read the target path from MSGames such as FreeCell, Minesweeper and Purble Place. Using of the class MsiShortcutParser.cs also returns me null path(MsiGetComponentPath is InstallState.InvalidArg). Let me know if there is anything more to get the “ParseShortcut” function work and return me the target path for MS Games.

GeekTieGuy wrote a comment on October 4, 2010

Sorry, I don’t have time to investigate this at the moment. I hope you’ll be able to find a method that works, though.

cosmin wrote a comment on May 19, 2011

Thanks mate, your code works flawlessly.

Jonathan wrote a comment on February 2, 2012

Please see: http://stackoverflow.com/questions/4490361/c-sharp-dllimport-of-msigetshortcuttargetmsi-dll-failed-with-error-1603-under
– the return value for MsiGetShortcutTarget is UInt32 and is important (you want to check that…), and
– the [Out] modifier is missing from the StringBuilder parameters:
/*
UINT MsiGetShortcutTarget(
LPCTSTR szShortcutTarget,
LPTSTR szProductCode,
LPTSTR szFeatureId,
LPTSTR szComponentCode
);
*/
[DllImport("msi.dll", CharSet = CharSet.Auto)]
static extern UInt32 MsiGetShortcutTarget(
string targetFile,
[Out] StringBuilder productCode,
[Out] StringBuilder featureID,
[Out] StringBuilder componentCode);
and…
/*
INSTALLSTATE MsiGetComponentPath(
LPCTSTR szProduct,
LPCTSTR szComponent,
LPTSTR lpPathBuf,
DWORD* pcchBuf);
*/
[DllImport("msi.dll", CharSet = CharSet.Auto)]
static extern InstallState MsiGetComponentPath(
string productCode,
string componentCode,
[Out] StringBuilder componentPath,
ref int componentPathBufferSize);

Jonathan wrote a comment on February 2, 2012

Oh, forgot to mention:
1. I’m using .net 4.0
2. [STAThread] on your main method *may* be required too… see the SO thread please.
3. Most important – thanks for posting this!

GeekTieGuy wrote a comment on February 4, 2012

My pleasure. And thanks for the additional findings!

Rus wrote a comment on March 31, 2012

Hi,
Now I see that suggested solution works just for Windows7. Does anybody know solution for previous version Windows?
Thanks

Rus wrote a comment on March 31, 2012

Thank all, I already solve my problem and now my lnk parser works well on all versions Windows. I use suggested method together with ShellLink methods. Thank you for code.

How to view advertised shortcut sent a pingback on May 6, 2012

[...] a programmatic answer at How to parse “special” .lnk files, aka. MSI shortcuts aka. Windows Installer advertised …. Answered by Hugh [...]

Care to comment?

%d bloggers like this: