Leandro Taset Leandro Taset - 28 days ago 11
C# Question

Windows 8 start screen tile, based on desktop shortcut

I have a currently working code (C#, but not important) that adds a tile to the Windows 8 start screen by means of creating a shortcut file [

.lnk
] within special directories like
%APPDATA%\Microsoft\Windows\Start Menu\Programs
and
%LOCALAPPDATA%\Microsoft\Windows\Application Shortcuts
. I have searched for and found several useful tools like
lnk-parser
and OblyTile, which have helped in clearing several aspects about the quirks involved. I have also found many articles and questions related to the topic, but none quite exactly addresses my specific needs.

I'm using
IShellLink
(COM interop) to create the shortcut and
IPropertyStore
(same object instance) to add some apparently required properties to the link file. My specific problem and question are these:

The created tile works as expected in almost every aspect. It runs the intended command, has the intended background and foreground colors, and displays the custom associated icon (
.png
image 144x144). The problem is that, even though it has a custom label string embedded in the link file [
System.ItemTypeText
], it just displays the name of the physical file minus the extension. That's not so horrible, but I would prefer that Windows honored the embedded property over the file name.

What might be missing in the link file that causes such behavior and not the desired one?




Bonus



By using
IPropertyStore
, I have come across several so called "property sets", which are just properties grouped by their
GUID
. I have special interest in the sets
{b725f130-47ef-101a-a5f1-02608c9eebac}
{86d40b4d-9069-443c-819a-2a54090dccec}
and
{9f4c2855-9f79-4b39-a8d0-e1d42de1d5f3}
(the latter contains the ubiquitous
System.AppUserModel.ID
).

Extra question: can you please point to any resource which documents any (or both) of these two property sets? I mean, what do each property mean and how are they interpreted by Windows. This might be even more helpful than the main question. No matter if it's official or unofficial.

Note



Using
OblyTile
by calling it with a command line is not an option, as per requirements. According to what I've observed, I might need to add a custom
resources.pri
file in the link's target file parent directory. It would be desirable to avoid that, if at all possible, because the format is not documented.

Sample code



The
ShellLink
class is a plain-old & boring wrapper around the
IShellLink
and
IPropertyStore
COM interfaces. It works just fine, I'm completely sure the problem is not there.

using System;
using System.IO;

namespace Shell32NET
{
public static class Win8Tiles
{
#region Fields

/// <summary>
/// b725f130-47ef-101a-a5f102608c9eebac
/// </summary>
static readonly Guid ItemTypeGroup = new Guid(
0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac
);

/// <summary>
/// b725f130-47ef-101a-a5f102608c9eebac, 4
/// </summary>
static readonly PropertyKey SystemItemTypeText = new PropertyKey(ItemTypeGroup, 4);

/// <summary>
/// 86d40b4d-9069-443c-819a2a54090dccec
/// </summary>
static readonly Guid TilePropertiesGroup = new Guid(
0x86d40b4d, 0x9069, 0x443c, 0x81, 0x9a, 0x2a, 0x54, 0x09, 0x0d, 0xcc, 0xec
);

/// <summary>
/// 86d40b4d-9069-443c-819a2a54090dccec, 2
/// </summary>
static readonly PropertyKey TileSmallImageLocation = new PropertyKey(TilePropertiesGroup, 2);

/// <summary>
/// 86d40b4d-9069-443c-819a2a54090dccec, 4
/// </summary>
static readonly PropertyKey TileBackgroundColor = new PropertyKey(TilePropertiesGroup, 4);

/// <summary>
/// 86d40b4d-9069-443c-819a2a54090dccec, 5
/// </summary>
static readonly PropertyKey TileForegroundColor = new PropertyKey(TilePropertiesGroup, 5);

/// <summary>
/// 86d40b4d-9069-443c-819a2a54090dccec, 11
/// </summary>
static readonly PropertyKey TileDisplayName = new PropertyKey(TilePropertiesGroup, 11);

/// <summary>
/// 86d40b4d-9069-443c-819a2a54090dccec, 12
/// </summary>
static readonly PropertyKey TileImageLocation = new PropertyKey(TilePropertiesGroup, 12);

/// <summary>
/// 86d40b4d-9069-443c-819a2a54090dccec, 14
/// </summary>
static readonly PropertyKey TileUnknownFlags = new PropertyKey(TilePropertiesGroup, 14);

/// <summary>
/// 9f4c2855-9f79-4b39-a8d0e1d42de1d5f3
/// </summary>
public static readonly Guid MetadataGroup = new Guid(
0x9f4c2855, 0x9f79, 0x4b39, 0xa8, 0xd0, 0xe1, 0xd4, 0x2d, 0xe1, 0xd5, 0xf3
);

/// <summary>
/// 9f4c2855-9f79-4b39-a8d0e1d42de1d5f3, 5
/// </summary>
public static readonly PropertyKey AppUserModelID = new PropertyKey(MetadataGroup, 5);

/// <summary>
/// b6578b39-11f9-449b-8438cb5cf03b7d9c
/// </summary>
static readonly Guid UnknownGroup1 = new Guid(
0xb6578b39, 0x11f9, 0x449b, 0x84, 0x38, 0xcb, 0x5c, 0xf0, 0x3b, 0x7d, 0x9c
);

#endregion

/// <summary>
/// Creates a tile in the Windows 8 start screen.
/// </summary>
/// <param name="target">The file to be executed when the tile is clicked.</param>
/// <param name="appId">The registering application's ID.</param>
/// <param name="title">The caption text for the tile.</param>
/// <param name="imageFilename">The image to be shown in the tile.</param>
/// <param name="background">Background color for the tile.<para>
/// Must be in the hex ARGB form: 0xAARRGGBB.
/// Where AA is the alpha channel value, RR is for red, GG for green and BB for blue.
/// </para></param>
/// <param name="foreground">Foregreound color for the tile, in the
/// same format as <paramref name="background"/>.</param>
public static void CreateTile(
string target,
string appId,
string title,
string imageFilename,
uint background,
uint foreground
)
{
string appdata = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (appdata == null)
throw new NotSupportedException("The user's roaming application data directory does not exist.");

using (var link = new ShellLink())
{
link.TargetPath = target;

// The .lnk icon location and index (not quite important). Just using some defaults.
link.SetIconLocation(@"%SystemRoot%\System32\SHELL32.DLL", 135);

link.SetProperty(AppUserModelID, appId);

// Apparently required properties (AS-IS).
link.SetProperty(new PropertyKey(MetadataGroup, 11), true);
link.SetProperty(new PropertyKey(MetadataGroup, 19), "Microsoft.InternetExplorer.Default");
link.SetProperty(
new PropertyKey(MetadataGroup, 20),
"-contentTile -formatVersion 0x00000002 -securityFlags 0x00000000 -url 0x00000057"
);

/**
* These are not really working at the moment; the tile just shows the name of the .lnk file.
*/
link.SetProperty(SystemItemTypeText, title);
link.SetProperty(TileDisplayName, title);

// Background and foreground.
link.SetProperty(TileBackgroundColor, background);
link.SetProperty(TileForegroundColor, foreground);

// Small and normal tile icon. Set your own.
string fileUri = new Uri(imageFilename).AbsoluteUri;
link.SetProperty(TileImageLocation, fileUri);
link.SetProperty(TileSmallImageLocation, fileUri);

// Apparently required, not the tiniest clue of why.
link.SetProperty(TileUnknownFlags, 0x41u);
link.SetProperty(new PropertyKey(UnknownGroup1, 4), 0x41u);

// The file should be saved to %LOCALAPPDATA%\Microsoft\Windows\Application Shortcuts
string filename = Path.Combine(appdata, @"Microsoft\Windows\Application Shortcuts");
filename = Path.Combine(filename, "Win8Tiles-Test");
Directory.CreateDirectory(filename);

filename = Path.Combine(filename, title + ".lnk");
link.Save(filename);
}
}
}
}

Answer
function GetPropertyKeyCanonicalName(const AKey: TPropertyKey): UnicodeString;
var
  PropertySystem: IPropertySystem;
  PropertyDescription: IPropertyDescription;
  N: PWideChar;
begin
  Result := '';
  if Succeeded(CoCreateInstance(CLSID_IPropertySystem, nil, CLSCTX_INPROC_SERVER, IPropertySystem, PropertySystem)) then
    try
      if Succeeded(PropertySystem.GetPropertyDescription(AKey, IPropertyDescription, PropertyDescription)) then
        try
          if Succeeded(PropertyDescription.GetCanonicalName(N)) then
            try
              Result := N
            finally
              CoTaskMemFree(N);
            end;
        finally
          PropertyDescription := nil;
        end;
    finally
      PropertySystem := nil;
    end;
end;
Comments