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

[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 Comments

  1. Pingback: Parsing those *.lnk type files - VB.NET version | Hodson Report

  2. Greg Zelesnik

    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!

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

  4. 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?

  5. 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!

  6. 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.)?

  7. Ben

    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?

  8. 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.

  9. Gaurav

    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?

  10. 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.

  11. JackZhong

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

  12. 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.

  13. Man

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

  14. Goverdhan A

    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.

  15. 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.

  16. Jonathan

    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);

  17. Jonathan

    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!

  18. Rus

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

  19. Rus

    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.

  20. Pingback: How to view advertised shortcut

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.