Extracting keyboard layouts from windows

Microsoft Keyboard Layout Creator can load system keyboards and export them as .klc files. Since it’s written in .NET you can use Reflector to see how it does that, and use reflection to drive it. Here's a zip file of .klc files for the 187 keyboards in Windows 8 created using the below C# code. Note that I originally wrote this for Windows XP, and now with Windows 8 and the on-screen keyboard, it is really slow and seems to crash the taskbar :/ However, it does work :)

using System;
using System.Collections;
using System.IO;
using System.Reflection;

class KeyboardExtractor {

    static Object InvokeNonPublicStaticMethod(Type t, String name,
            Object[] args)
    {
        return t.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)
            .Invoke(null, args);
    }

    static void InvokeNonPublicInstanceMethod(Object o, String name,
            Object[] args)
    {
        o.GetType().GetMethod(name, BindingFlags.Instance |
                BindingFlags.NonPublic) .Invoke(o, args);
    }

    static Object GetNonPublicProperty(Object o, String propertyName) {
        return o.GetType().GetField(propertyName,
                BindingFlags.Instance | BindingFlags.NonPublic)
            .GetValue(o);
    }

    static void SetNonPublicField(Object o, String propertyName, Object v) {
        o.GetType().GetField(propertyName,
                BindingFlags.Instance | BindingFlags.NonPublic)
            .SetValue(o, v);
    }

    [STAThread] public static void Main() {
        System.Console.WriteLine("Keyboard Extractor...");

        KeyboardExtractor ke = new KeyboardExtractor();
        ke.extractAll();

        System.Console.WriteLine("Done.");
    }

    Assembly msklcAssembly;
    Type utilitiesType;
    Type keyboardType;
    String baseDirectory;

    public KeyboardExtractor() {
        msklcAssembly = Assembly.LoadFile("C:\\Program Files\\Microsoft Keyboard Layout Creator 1.4\\MSKLC.exe");
        utilitiesType = msklcAssembly.GetType("Microsoft.Globalization.Tools.KeyboardLayoutCreator.Utilities");
        keyboardType = msklcAssembly.GetType("Microsoft.Globalization.Tools.KeyboardLayoutCreator.Keyboard");

        baseDirectory = Directory.GetCurrentDirectory();
    }

    public void extractAll() {

        DateTime startTime = DateTime.UtcNow;

        SortedList keyboards = (SortedList)InvokeNonPublicStaticMethod(
                utilitiesType, "KeyboardsOnMachine", new Object[] {false});

        DateTime loopStartTime = DateTime.UtcNow;

        int i = 0;
        foreach (DictionaryEntry e in keyboards) {
            i += 1;
            Object k = e.Value;

            String name = (String)GetNonPublicProperty(k, "m_stLayoutName");
            String layoutHexString = ((UInt32)GetNonPublicProperty(k, "m_hkl"))
                .ToString("X");

            TimeSpan elapsed = DateTime.UtcNow - loopStartTime;
            Double ticksRemaining = ((Double)elapsed.Ticks * keyboards.Count)
                        / i - elapsed.Ticks;
            TimeSpan remaining = new TimeSpan((Int64)ticksRemaining);
            String msgTimeRemaining = "";
            if (i > 1) {
                // Trim milliseconds
                remaining = new TimeSpan(remaining.Hours, remaining.Minutes,
                        remaining.Seconds);
                msgTimeRemaining = String.Format(", about {0} remaining",
                        remaining);
            }
            System.Console.WriteLine(
                    "Saving {0} {1}, keyboard {2} of {3}{4}",
                    layoutHexString, name, i, keyboards.Count,
                    msgTimeRemaining);

            SaveKeyboard(name, layoutHexString);

        }

        System.Console.WriteLine("{0} elapsed", DateTime.UtcNow - startTime);

    }

    private void SaveKeyboard(String name, String layoutHexString) {
        Object k = keyboardType.GetConstructors(
                BindingFlags.Instance | BindingFlags.NonPublic)[0]
            .Invoke(new Object[] {
                        new String[] {"", layoutHexString},
                    false});

        SetNonPublicField(k, "m_fSeenOrHeardAboutPropertiesDialog", true);
        SetNonPublicField(k, "m_stKeyboardTextFileName",
                String.Format("{0}\\{1} {2}.klc",
                    baseDirectory, layoutHexString, name));
        InvokeNonPublicInstanceMethod(k, "mnuFileSave_Click",
                new Object[] {new Object(), new EventArgs()});

        ((IDisposable)k).Dispose();
    }

}

Basically, it gets a list of all the keyboards on the system, then for each one, loads it in MSKLC, sets the "Save As" filename, lies about whether it's already configured the custom keyboard properties, and then simulates a click on the File -> Save menu item.


Why don't you use the on-screen keyboard (osk.exe)? Looks like you re-inventing the wheel. And not the easiest one!


I know where are these DLL files' path:

In your registry, you see:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts

where each branch has some value like "Layout File"="KBDSP.dll". The root directory is

C:\Windows\System32

and

C:\Windows\SystemWOW64

Those are all the keyboard layout files are located. For example, KBDUS.dll means "keyboard for US".

I tried to substitute the DLL file with my custom DLL made by MSKLC, and I found it loads the layout mapping images automatically in the "Language" - "input method" - "preview":

enter image description here

So we know that the mapping is there in the DLL.