Saturday, May 25, 2013

Language neutral Pin shortcut to Start Menu using C#

Problem: I needed a program that would automatically pin an existing shortcut on the desktop to the start menu, regardless what localization the Windows user was using. I was using Windows Server 2008 R2 with a couple of alternate languages to English installed for testing. The program was written in C#. I hit the wall a copule of times while trying to make c# code from an earlier implementation written in VB.NET - this is a summary of what I got to work in the end:
Type shellAppType = Type.GetTypeFromProgID("Shell.Application");
Object oShell = Activator.CreateInstance(shellAppType);
Shell32.Folder oFolder = (Shell32.Folder)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, oShell, new object[] { 0 }); //25 = common desktop, 0 is local desktop

if (System.IO.File.Exists(ShortcutTarget) & (oFolder != null))
   Shell32.FolderItem oFolderItem = oFolder.ParseName("somefile.lnk");
   Shell32.ShellLinkObject oShellLink = (Shell32.ShellLinkObject)oFolderItem.GetLink;
   if (oShellLink != null)
      // Find localized shell32 verb used to pin shortcut to the start menu (so that it works in any language)
      StringBuilder szPinToStartLocalized = new StringBuilder(MAX_PATH);
      IntPtr hShell32 = LoadLibrary("SHELL32");
      LoadString(hShell32, 5381, szPinToStartLocalized, MAX_PATH); // 5381 is the DLL index for "Pin to start menu", ref.
      string localizedVerb = szPinToStartLocalized.ToString();

      foreach (Shell32.FolderItemVerb verb in oFolderItem.Verbs())
         if (verb.Name == localizedVerb)

You need to reference Shell32 - i.e. Microsoft Shell Controls and Automation on the COM tab of your "Add reference" dialog in VS2010. Also make sure you copy the shell32.interop.dll along with your application when you move the application out of your bin folder.

The following problems were encountered and solved on the way:

BadImageFormatException when entering the method containing this code. The code worked well on a Windows Vista computer, but crashed when running on Server 2008 R2. I assume this happened because of the fact that Server 2008 R2 is a 64 bit OS, and this did not work well with using the unmanaged Shell32.
Solution: Change the target CPU of your application to x86. If you use VS2010 Express, this tip on StackOverflow may be helpful to you - Make sure to read all the answers if you are stuck.

InvokeVerb did not work. Before ending up using the line verb.DoIt(), I tried using oFolderItem.InvokeVerb(verb.Name), which used to work in an earlier VB.NET implementation of the same code. I have no explanation to why this did not work, but it did not. A conspiracy theory is that Nike paid them off to just do it...

By the way, this earlier post mentions a problem I encountered trying to do the same in VB.NET some time ago. It probably applies here too, which is why the top three lines of the code looks like they do.