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.
Categories: Software development
16 Comments »