changeset 0:209d9210c18f default tip

It works.
author Brad Greco <brad@bgreco.net>
date Sat, 25 Jun 2016 13:42:54 +1000
parents
children
files .hgignore ShortcutKeyFinder.sln ShortcutKeyFinder/App.config ShortcutKeyFinder/Hotkey.cs ShortcutKeyFinder/Images/keyboard.ico ShortcutKeyFinder/MainForm.Designer.cs ShortcutKeyFinder/MainForm.cs ShortcutKeyFinder/MainForm.resx ShortcutKeyFinder/Program.cs ShortcutKeyFinder/Properties/AssemblyInfo.cs ShortcutKeyFinder/Properties/Resources.Designer.cs ShortcutKeyFinder/Properties/Resources.resx ShortcutKeyFinder/Properties/Settings.Designer.cs ShortcutKeyFinder/Properties/Settings.settings ShortcutKeyFinder/Shortcut.cs ShortcutKeyFinder/ShortcutKeyFinder.cs ShortcutKeyFinder/ShortcutKeyFinder.csproj ShortcutKeyFinder/ShortcutKeyFinder.csproj.user ShortcutKeyFinder/Win32Helpers.cs ShortcutKeyFinder/keyboard.ico
diffstat 20 files changed, 1948 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,4 @@
+syntax: glob
+bin
+obj
+*.suo
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder.sln	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutKeyFinder", "ShortcutKeyFinder\ShortcutKeyFinder.csproj", "{0BFA5ACD-DDA4-4723-AFE2-3E66EECFEAB1}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{0BFA5ACD-DDA4-4723-AFE2-3E66EECFEAB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0BFA5ACD-DDA4-4723-AFE2-3E66EECFEAB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0BFA5ACD-DDA4-4723-AFE2-3E66EECFEAB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0BFA5ACD-DDA4-4723-AFE2-3E66EECFEAB1}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/App.config	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        
+    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" /></startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-2.6.8.0" newVersion="2.6.8.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-2.6.8.0" newVersion="2.6.8.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Hotkey.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,89 @@
+using System.Collections.Generic;
+using System.Windows.Forms;
+
+namespace ShortcutKeyFinder
+{
+    /// <summary>Provides a means to encode and decode hotkeys in the format expected by the Win32 API</summary>
+    class Hotkey
+    {
+        private List<Keys> _modifierKeys = new List<Keys>() { Keys.ShiftKey, Keys.ControlKey, Keys.Menu };
+        // Windows API values
+        public ushort HOTKEYF_SHIFT   = 0x01 << 8;
+        public ushort HOTKEYF_CONTROL = 0x02 << 8;
+        public ushort HOTKEYF_ALT     = 0x04 << 8;
+        public ushort HOTKEYF_EXT     = 0x08 << 8;
+
+        /// <summary>Raw hotkey value to use in Win32 functions</summary>
+        public ushort RawHotkey { get; set; }
+
+        /// <summary>Shift key state</summary>
+        public bool Shift
+        {
+            get { return (RawHotkey & HOTKEYF_SHIFT) != 0; }
+            set { if (value) RawHotkey |= HOTKEYF_SHIFT; else RawHotkey &= (ushort)~HOTKEYF_SHIFT; }
+        }
+
+        /// <summary>Control key state</summary>
+        public bool Control
+        {
+            get { return (RawHotkey & HOTKEYF_CONTROL) != 0; }
+            set { if (value) RawHotkey |= HOTKEYF_CONTROL; else RawHotkey &= (ushort)~HOTKEYF_CONTROL; }
+        }
+
+        /// <summary>Alt key state</summary>
+        public bool Alt
+        {
+            get { return (RawHotkey & HOTKEYF_ALT) != 0; }
+            set { if (value) RawHotkey |= HOTKEYF_ALT; else RawHotkey &= (ushort)~HOTKEYF_ALT; }
+        }
+
+        /// <summary>Ext key state</summary>
+        /// <remarks>Probably useless nowadays</remarks>
+        public bool Ext
+        {
+            get { return (RawHotkey & HOTKEYF_EXT) != 0; }
+            set { if (value) RawHotkey |= HOTKEYF_EXT; else RawHotkey &= (ushort)~HOTKEYF_EXT; }
+        }
+
+        /// <summary>The key code portion of the hotkey data</summary>
+        /// <remarks>The key code is stored in the lower byte of the hotkey</remarks>
+        public byte KeyCode
+        {
+            get { return (byte)(RawHotkey & 0x00FF); }
+            set { RawHotkey = (ushort)((RawHotkey & 0xFF00) | value); }
+        }
+
+        /// <summary>Default constructor</summary>
+        public Hotkey() { }
+
+        /// <summary>Constructor with hotkey initializer</summary>
+        /// <param name="hotkey">Hotkey value for Win32 API calls</param>
+        public Hotkey(ushort hotkey)
+        {
+            RawHotkey = hotkey;
+        }
+
+        /// <summary>Converts the hotkey data into a friendly string</summary>
+        public override string ToString()
+        {
+            return string.Format("{0}{1}{2}{3}{4}",
+                Control ? "Ctrl+" : "",
+                Alt ? "Alt+" : "",
+                Shift ? "Shift+" : "",
+                Ext ? "Ext+" : "",
+                _modifierKeys.Contains((Keys)KeyCode) ? "" : new KeysConverter().ConvertToString((int)KeyCode));
+        }
+
+        /// <summary>Checks if two Hotkey objects have the same key combination</summary>
+        public override bool Equals(object obj)
+        {
+            Hotkey hotkey = obj as Hotkey;
+            return hotkey != null && hotkey.RawHotkey == RawHotkey;
+        }
+
+        public override int GetHashCode()
+        {
+            return RawHotkey.GetHashCode();
+        }
+    }
+}
Binary file ShortcutKeyFinder/Images/keyboard.ico has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/MainForm.Designer.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,226 @@
+namespace ShortcutKeyFinder
+{
+    partial class MainForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.components = new System.ComponentModel.Container();
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
+            this.ShortcutGrid = new System.Windows.Forms.DataGridView();
+            this.ShortcutColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.HotkeyColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.LocationColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.FindShortcutWorker = new System.ComponentModel.BackgroundWorker();
+            this.panel1 = new System.Windows.Forms.Panel();
+            this.ClearButton = new System.Windows.Forms.Button();
+            this.ClearAllButton = new System.Windows.Forms.Button();
+            this.ElevateButton = new System.Windows.Forms.Button();
+            this.ShowAllCheckBox = new System.Windows.Forms.CheckBox();
+            this.InitialProgressBar = new System.Windows.Forms.ProgressBar();
+            this.MainFormToolTip = new System.Windows.Forms.ToolTip(this.components);
+            ((System.ComponentModel.ISupportInitialize)(this.ShortcutGrid)).BeginInit();
+            this.panel1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // ShortcutGrid
+            // 
+            this.ShortcutGrid.AllowUserToAddRows = false;
+            this.ShortcutGrid.AllowUserToDeleteRows = false;
+            this.ShortcutGrid.AllowUserToResizeRows = false;
+            this.ShortcutGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.ShortcutGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
+            this.ShortcutGrid.ClipboardCopyMode = System.Windows.Forms.DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
+            this.ShortcutGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+            this.ShortcutGrid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
+            this.ShortcutColumn,
+            this.HotkeyColumn,
+            this.LocationColumn});
+            this.ShortcutGrid.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnF2;
+            this.ShortcutGrid.Location = new System.Drawing.Point(12, 54);
+            this.ShortcutGrid.Name = "ShortcutGrid";
+            this.ShortcutGrid.RowHeadersVisible = false;
+            this.ShortcutGrid.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
+            this.ShortcutGrid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
+            this.ShortcutGrid.Size = new System.Drawing.Size(912, 619);
+            this.ShortcutGrid.StandardTab = true;
+            this.ShortcutGrid.TabIndex = 0;
+            this.ShortcutGrid.Visible = false;
+            this.ShortcutGrid.CellBeginEdit += new System.Windows.Forms.DataGridViewCellCancelEventHandler(this.ShortcutGrid_CellBeginEdit);
+            this.ShortcutGrid.CellDoubleClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.ShortcutGrid_CellDoubleClick);
+            this.ShortcutGrid.CellEndEdit += new System.Windows.Forms.DataGridViewCellEventHandler(this.ShortcutGrid_CellEndEdit);
+            this.ShortcutGrid.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.ShortcutGrid_CellFormatting);
+            this.ShortcutGrid.CellPainting += new System.Windows.Forms.DataGridViewCellPaintingEventHandler(this.ShortcutGrid_CellPainting);
+            this.ShortcutGrid.EditingControlShowing += new System.Windows.Forms.DataGridViewEditingControlShowingEventHandler(this.ShortcutGrid_EditingControlShowing);
+            this.ShortcutGrid.SelectionChanged += new System.EventHandler(this.ShortcutGrid_SelectionChanged);
+            this.ShortcutGrid.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ShortcutGrid_KeyDown);
+            // 
+            // ShortcutColumn
+            // 
+            this.ShortcutColumn.DataPropertyName = "Name";
+            this.ShortcutColumn.HeaderText = "Program";
+            this.ShortcutColumn.Name = "ShortcutColumn";
+            this.ShortcutColumn.ReadOnly = true;
+            // 
+            // HotkeyColumn
+            // 
+            this.HotkeyColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells;
+            this.HotkeyColumn.DataPropertyName = "Hotkey";
+            this.HotkeyColumn.HeaderText = "Shortcut key";
+            this.HotkeyColumn.Name = "HotkeyColumn";
+            this.HotkeyColumn.Width = 92;
+            // 
+            // LocationColumn
+            // 
+            this.LocationColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells;
+            this.LocationColumn.DataPropertyName = "DisplayLocation";
+            this.LocationColumn.HeaderText = "Location";
+            this.LocationColumn.Name = "LocationColumn";
+            this.LocationColumn.ReadOnly = true;
+            this.LocationColumn.Width = 73;
+            // 
+            // FindShortcutWorker
+            // 
+            this.FindShortcutWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.FindShortcutWorker_DoWork);
+            this.FindShortcutWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.FindShortcutWorker_RunWorkerCompleted);
+            // 
+            // panel1
+            // 
+            this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.panel1.Controls.Add(this.ClearButton);
+            this.panel1.Controls.Add(this.ClearAllButton);
+            this.panel1.Controls.Add(this.ElevateButton);
+            this.panel1.Controls.Add(this.ShowAllCheckBox);
+            this.panel1.Location = new System.Drawing.Point(12, 12);
+            this.panel1.Name = "panel1";
+            this.panel1.Size = new System.Drawing.Size(912, 36);
+            this.panel1.TabIndex = 1;
+            // 
+            // ClearButton
+            // 
+            this.ClearButton.Enabled = false;
+            this.ClearButton.Location = new System.Drawing.Point(180, 3);
+            this.ClearButton.Name = "ClearButton";
+            this.ClearButton.Size = new System.Drawing.Size(140, 30);
+            this.ClearButton.TabIndex = 2;
+            this.ClearButton.Text = "&Clear shortcut key";
+            this.ClearButton.UseVisualStyleBackColor = true;
+            this.ClearButton.Click += new System.EventHandler(this.ClearButton_Click);
+            // 
+            // ClearAllButton
+            // 
+            this.ClearAllButton.Enabled = false;
+            this.ClearAllButton.Location = new System.Drawing.Point(340, 3);
+            this.ClearAllButton.Name = "ClearAllButton";
+            this.ClearAllButton.Size = new System.Drawing.Size(140, 30);
+            this.ClearAllButton.TabIndex = 3;
+            this.ClearAllButton.Text = "C&lear all shortcut keys";
+            this.ClearAllButton.UseVisualStyleBackColor = true;
+            this.ClearAllButton.Click += new System.EventHandler(this.ClearAllButton_Click);
+            // 
+            // ElevateButton
+            // 
+            this.ElevateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.ElevateButton.Location = new System.Drawing.Point(665, 3);
+            this.ElevateButton.Name = "ElevateButton";
+            this.ElevateButton.Size = new System.Drawing.Size(244, 30);
+            this.ElevateButton.TabIndex = 4;
+            this.ElevateButton.Text = "&Allow editing shortcuts shared by all users";
+            this.MainFormToolTip.SetToolTip(this.ElevateButton, "Shared shortcuts (in the common Start Menu and Desktop)\r\nmay only be edited by Ad" +
+        "ministrators.\r\nClick this button to relaunch this program as an Administrator.");
+            this.ElevateButton.UseVisualStyleBackColor = true;
+            this.ElevateButton.Click += new System.EventHandler(this.ElevateButton_Click);
+            // 
+            // ShowAllCheckBox
+            // 
+            this.ShowAllCheckBox.AutoSize = true;
+            this.ShowAllCheckBox.Location = new System.Drawing.Point(3, 11);
+            this.ShowAllCheckBox.Name = "ShowAllCheckBox";
+            this.ShowAllCheckBox.Size = new System.Drawing.Size(157, 17);
+            this.ShowAllCheckBox.TabIndex = 1;
+            this.ShowAllCheckBox.Text = "&Show all available programs";
+            this.MainFormToolTip.SetToolTip(this.ShowAllCheckBox, "Unchecked - Show only shortcuts with assigned hotkeys\r\nChecked - Show all shortcu" +
+        "ts to which hotkeys may be assigned");
+            this.ShowAllCheckBox.UseVisualStyleBackColor = true;
+            this.ShowAllCheckBox.CheckedChanged += new System.EventHandler(this.ShowAllCheckBox_CheckedChanged);
+            // 
+            // InitialProgressBar
+            // 
+            this.InitialProgressBar.Location = new System.Drawing.Point(312, 273);
+            this.InitialProgressBar.MarqueeAnimationSpeed = 10;
+            this.InitialProgressBar.Name = "InitialProgressBar";
+            this.InitialProgressBar.Size = new System.Drawing.Size(312, 23);
+            this.InitialProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+            this.InitialProgressBar.TabIndex = 5;
+            // 
+            // MainFormToolTip
+            // 
+            this.MainFormToolTip.AutoPopDelay = 30000;
+            this.MainFormToolTip.InitialDelay = 500;
+            this.MainFormToolTip.ReshowDelay = 100;
+            this.MainFormToolTip.ShowAlways = true;
+            // 
+            // MainForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(936, 685);
+            this.Controls.Add(this.InitialProgressBar);
+            this.Controls.Add(this.panel1);
+            this.Controls.Add(this.ShortcutGrid);
+            this.Cursor = System.Windows.Forms.Cursors.WaitCursor;
+            this.DoubleBuffered = true;
+            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+            this.MinimumSize = new System.Drawing.Size(790, 38);
+            this.Name = "MainForm";
+            this.Text = "Shortcut Key Manager";
+            ((System.ComponentModel.ISupportInitialize)(this.ShortcutGrid)).EndInit();
+            this.panel1.ResumeLayout(false);
+            this.panel1.PerformLayout();
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.DataGridView ShortcutGrid;
+        private System.ComponentModel.BackgroundWorker FindShortcutWorker;
+        private System.Windows.Forms.DataGridViewTextBoxColumn ShortcutColumn;
+        private System.Windows.Forms.DataGridViewTextBoxColumn HotkeyColumn;
+        private System.Windows.Forms.DataGridViewTextBoxColumn LocationColumn;
+        private System.Windows.Forms.Panel panel1;
+        private System.Windows.Forms.Button ElevateButton;
+        private System.Windows.Forms.CheckBox ShowAllCheckBox;
+        private System.Windows.Forms.Button ClearAllButton;
+        private System.Windows.Forms.Button ClearButton;
+        private System.Windows.Forms.ProgressBar InitialProgressBar;
+        private System.Windows.Forms.ToolTip MainFormToolTip;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/MainForm.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,397 @@
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Linq;
+using System.Security.Principal;
+using System.Windows.Forms;
+
+namespace ShortcutKeyFinder
+{
+    /// <summary>Shortcut key manager main form</summary>
+    public partial class MainForm : Form
+    {
+        private const int _iconSize = 16;
+        private const int _iconMargin = 4;
+        private const string _registryKey = "Software\\ShortcutKeyManager", _showAllPreference = "ShowAllShortcuts";
+        private bool _suspendUpdates, _suspendUpdateButtons;
+        private int _firstDisplayedRowIndex;
+        private ShortcutKeyFinder _shortcutKeyFinder;
+        private BindingSource _dataSource;
+        private SolidBrush _textBrush;
+        private StringFormat _cellFormat;
+        private List<Shortcut> _oldSelection;
+        private Hotkey _hotkey;
+
+        /// <summary>List of shortcuts currently selected by the user</summary>
+        private List<Shortcut> SelectedShortcuts
+        {
+            get
+            {
+                List<Shortcut> shortcuts = new List<Shortcut>();
+                foreach (DataGridViewRow row in ShortcutGrid.SelectedRows)
+                    if ((ShortcutGrid.DataSource as BindingSource).Count > row.Index)
+                        shortcuts.Add((Shortcut)row.DataBoundItem);
+                return shortcuts;
+            }
+        }
+
+        /// <summary>Data source for the shortcut grid</summary>
+        private BindingList<Shortcut> Shortcuts { get { return _shortcutKeyFinder != null ? _shortcutKeyFinder.Shortcuts : null; } }
+
+        /// <summary>Constructor and form initialization</summary>
+        public MainForm()
+        {
+            InitializeComponent();
+            // Set up grid data source
+            _shortcutKeyFinder = new ShortcutKeyFinder();
+            _dataSource = new BindingSource() { DataSource = _shortcutKeyFinder.Shortcuts };
+            // Cell format for custom painting of shortcut icons and names in the same cell
+            _cellFormat = new StringFormat(StringFormatFlags.NoWrap) { LineAlignment = StringAlignment.Center, Trimming = StringTrimming.EllipsisCharacter };
+
+            // Set up grid
+            ShortcutGrid.AutoGenerateColumns = false;
+            HotkeyColumn.ReadOnly = false;
+            ShortcutGrid.DataSource = _dataSource;
+            ShortcutGrid.CellParsing += ShortcutGrid_CellParsing;
+            _shortcutKeyFinder.Shortcuts.ListChanged += Shortcuts_ListChanged;
+
+            // Set up UI
+            SetupElevateButton();
+            LoadPreferences();
+
+            // Reduce grid flicker
+            typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, ShortcutGrid, new object[] { true });
+        }
+
+        /// <summary>Refreshes data when form receives focus</summary>
+        protected override void OnActivated(EventArgs e)
+        {
+            base.OnActivated(e);
+            if (!_suspendUpdates)
+                FindShortcutWorker.RunWorkerAsync();
+        }
+
+        /// <summary>Cancels the inline editor if the user leaves the program</summary>
+        protected override void OnDeactivate(EventArgs e)
+        {
+            ShortcutGrid.CancelEdit();
+            ShortcutGrid.EndEdit();
+        }
+
+        /// <summary>Scans for shortcuts in the background</summary>
+        private void FindShortcutWorker_DoWork(object sender, DoWorkEventArgs e)
+        {
+            // Disable list change events while scan is in progress
+            _dataSource.RaiseListChangedEvents = false;
+            _shortcutKeyFinder.LoadShortcuts(IsAdministrator());
+        }
+
+        /// <summary>Updates the grid after the scan is complete</summary>
+        private void FindShortcutWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
+        {
+            _suspendUpdateButtons = true; // Prevent button flicker
+            // Save UI state
+            _firstDisplayedRowIndex = ShortcutGrid.FirstDisplayedScrollingRowIndex;
+            _oldSelection = SelectedShortcuts.ToList();
+
+            // Refresh the grid
+            _dataSource.RaiseListChangedEvents = true;
+            _dataSource.ResetBindings(false);
+            ShortcutGrid.ClearSelection();
+
+            // Restore UI state
+            if (ShortcutGrid.Rows.Count > 0)
+                ShortcutGrid.FirstDisplayedScrollingRowIndex = Math.Max(0, Math.Min(_firstDisplayedRowIndex, ShortcutGrid.Rows.Count - 1));
+            foreach (DataGridViewRow row in ShortcutGrid.Rows)
+                row.Selected = _oldSelection.Contains((Shortcut)row.DataBoundItem);
+            _suspendUpdateButtons = false;
+            UpdateButtons();
+
+            // Hide busy indicators that appear when the form is first launched
+            InitialProgressBar.Visible = false;
+            ShortcutGrid.Visible = true;
+            Cursor = Cursors.Default;
+        }
+
+        /// <summary>Updates the UI when a shortcut's properties have changed</summary>
+        void Shortcuts_ListChanged(object sender, ListChangedEventArgs e)
+        {
+            // Update the "Clear shortcut key" button since the selection's shortcut may have been set or unset
+            if (!_suspendUpdateButtons)
+            {
+                if (this.InvokeRequired)
+                    this.Invoke(new Action(() => UpdateButtons()));
+                else
+                    UpdateButtons();
+            }
+            // Force repainting of changed rows so the background color gets updated
+            if (e.ListChangedType == ListChangedType.ItemChanged && e.NewIndex < ShortcutGrid.Rows.Count)
+                ShortcutGrid.InvalidateRow(e.NewIndex);
+        }
+
+        /// <summary>Enables or disables the buttons depending on the current selection</summary>
+        private void UpdateButtons()
+        {
+            ClearButton.Enabled = ShortcutGrid.SelectedRows.Count > 0 && SelectedShortcuts.Any(s => s.Hotkey != null);
+            ClearAllButton.Enabled = Shortcuts.Any(s => s.Hotkey != null);
+        }
+
+        /// <summary>Sets and saves a shortcut's hotkey</summary>
+        private void SetShortcutHotkey(Shortcut shortcut, Hotkey hotkey)
+        {
+            try
+            {
+                shortcut.Hotkey = hotkey;
+                shortcut.Save();
+            }
+            catch
+            {
+                MessageBox.Show("Error setting shortcut key", "Set Shortcut Key", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        /// <summary>Sets cell background color based on its shortcut's properties, and inserts the shortcut's icon next to its name</summary>
+        private void ShortcutGrid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
+        {
+            // Header row
+            if (e.RowIndex < 0)
+                return;
+
+            DataGridViewRow row = ShortcutGrid.Rows[e.RowIndex];
+            Shortcut shortcut = (Shortcut)row.DataBoundItem;
+
+            // Paint duplicate shortcut keys red
+            if (shortcut.HasDuplicateHotkey)
+            {
+                e.CellStyle.BackColor = Color.DarkRed;
+                e.CellStyle.ForeColor = Color.White;
+                e.CellStyle.SelectionBackColor = Color.OrangeRed;
+                e.CellStyle.SelectionForeColor = Color.White;
+            }
+            // Paint readonly shortcuts gray
+            else if (shortcut.ReadOnly)
+            {
+                e.CellStyle.BackColor = Color.LightGray;
+                e.CellStyle.ForeColor = Color.Black;
+                e.CellStyle.SelectionBackColor = Color.SteelBlue;
+                e.CellStyle.SelectionForeColor = Color.White;
+            }
+
+            // Paint the shortcut's icon next to its name
+            if (e.ColumnIndex == ShortcutColumn.Index)
+            {
+                Color textColor = row.Selected ? e.CellStyle.SelectionForeColor : e.CellStyle.ForeColor;
+                if (_textBrush == null || _textBrush.Color != textColor)
+                    _textBrush = new SolidBrush(textColor);
+                e.PaintBackground(e.ClipBounds, row.Selected);
+                Rectangle textRect = new Rectangle(e.CellBounds.X + _iconSize + _iconMargin * 2, e.CellBounds.Y, e.CellBounds.Width - _iconSize - _iconMargin * 2, e.CellBounds.Height);
+                e.Graphics.DrawImage(shortcut.Icon, _iconMargin, textRect.Y + ((textRect.Height - _iconSize) / 2));
+                e.Graphics.DrawString(e.FormattedValue.ToString(), e.CellStyle.Font, _textBrush, textRect, _cellFormat);
+                e.Handled = true;
+            }
+        }
+
+        /// <summary>Triggers direct hotkey editing if the hotkey column is double clicked, or opens the shortcut's Properties window for any other column</summary>
+        private void ShortcutGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
+        {
+            if (e.ColumnIndex == HotkeyColumn.Index)
+            {
+                ShortcutGrid.CurrentCell = ShortcutGrid.Rows[e.RowIndex].Cells[e.ColumnIndex];
+                ShortcutGrid.BeginEdit(true);
+            }
+            else
+            {
+                ((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem).ShowExplorerPropertiesWindow(Handle);
+            }
+        }
+
+        /// <summary>Opens the Properties window for the selected row on Enter, or activates the inline shortcut editor on F2</summary>
+        private void ShortcutGrid_KeyDown(object sender, KeyEventArgs e)
+        {
+            if (e.KeyCode == Keys.Enter)
+            {
+                e.Handled = true;
+                if (SelectedShortcuts.Count == 1)
+                    SelectedShortcuts[0].ShowExplorerPropertiesWindow(Handle);
+            }
+            else if (e.KeyCode == Keys.F2)
+            {
+                if (ShortcutGrid.SelectedRows.Count == 1)
+                {
+                    ShortcutGrid.CurrentCell = ShortcutGrid.SelectedRows[0].Cells[HotkeyColumn.Index];
+                    ShortcutGrid.BeginEdit(true);
+                }
+            }
+        }
+
+        /// <summary>Prepares the inline editor by clearing the text and registering event handlers</summary>
+        private void ShortcutGrid_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
+        {
+            (e.Control as TextBox).Text = "";
+            (e.Control as TextBox).KeyDown += CellEditor_KeyDown;
+            (e.Control as TextBox).PreviewKeyDown += CellEditor_PreviewKeyDown;
+            (e.Control as TextBox).Leave += CellEditor_Leave;
+        }
+
+        /// <summary>Removes event handlers from inline editor when it loses focus</summary>
+        void CellEditor_Leave(object sender, EventArgs e)
+        {
+            (sender as TextBox).KeyDown -= CellEditor_KeyDown;
+            (sender as TextBox).PreviewKeyDown -= CellEditor_PreviewKeyDown;
+            (sender as TextBox).Leave -= CellEditor_Leave;
+        }
+
+        /// <summary>Finishes inline hotkey editing on Enter</summary>
+        private void CellEditor_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
+        {
+            if (e.KeyCode == Keys.Enter)
+                ShortcutGrid.EndEdit();
+        }
+
+        /// <summary>Listens for key events when the inline editor is active and converts them to a hotkey</summary>
+        void CellEditor_KeyDown(object sender, KeyEventArgs e)
+        {
+            System.Diagnostics.Debug.WriteLine("keydown");
+            if (e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete)
+            {
+                _hotkey = null;
+                (sender as TextBox).Clear();
+            }
+            else if (e.KeyCode != Keys.Enter)
+            {
+                _hotkey = new Hotkey()
+                {
+                    KeyCode = (byte)e.KeyCode,
+                    Control = e.Control,
+                    Alt = e.Alt,
+                    Shift = e.Shift
+                };
+                (sender as TextBox).Text = _hotkey.ToString();
+            }
+            e.Handled = true;
+            e.SuppressKeyPress = true;
+        }
+
+        /// <summary>Saves the new hotkey to the data source and the shortcut when inline editing is complete</summary>
+        private void ShortcutGrid_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
+        {
+            e.ParsingApplied = true;
+            e.Value = _hotkey;
+            SetShortcutHotkey((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem, _hotkey);
+        }
+
+        /// <summary>Updates the list view to show all shorcuts or only shortcuts with hotkeys</summary>
+        private void ShowAllCheckBox_CheckedChanged(object sender, EventArgs e)
+        {
+            _shortcutKeyFinder.ShowAll = ShowAllCheckBox.Checked;
+            ShortcutGrid.ClearSelection();
+            SavePreferences();
+        }
+
+        /// <summary>Removes the hotkeys from the selected shortcuts when the Clear button is clicked</summary>
+        private void ClearButton_Click(object sender, EventArgs e)
+        {
+            foreach (Shortcut shortcut in SelectedShortcuts.Where(s => !s.ReadOnly && s.Hotkey != null))
+                SetShortcutHotkey(shortcut, null);
+        }
+
+        /// <summary>Clears shortcuts from all hotkeys when the Clear All button is clicked</summary>
+        private void ClearAllButton_Click(object sender, EventArgs e)
+        {
+            _suspendUpdates = true;
+            if (MessageBox.Show("Are you sure you want to clear all assigned shortcut keys?", "Clear Shortcut Keys", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+            {
+                foreach (Shortcut shortcut in Shortcuts.Where(s => !s.ReadOnly && s.Hotkey != null))
+                    SetShortcutHotkey(shortcut, null);
+            }
+            _suspendUpdates = false;
+        }
+
+        /// <summary>Prepares for inline shortcut editing, or prevents it if the shortcut is read only</summary>
+        private void ShortcutGrid_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
+        {
+            _hotkey = null;
+            e.Cancel = ((Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem).ReadOnly;
+        }
+
+        /// <summary>Updates the button state when the grid selection changes</summary>
+        private void ShortcutGrid_SelectionChanged(object sender, EventArgs e)
+        {
+            if (!_suspendUpdateButtons)
+                UpdateButtons();
+        }
+
+        /// <summary>Updates the button state since the hotkey of the current item may have changed</summary>
+        private void ShortcutGrid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
+        {
+            if (!_suspendUpdateButtons)
+                UpdateButtons();
+        }
+
+        /// <summary>Checks whether the program is running with elevated permissions</summary>
+        /// <remarks>http://stackoverflow.com/a/11660205</remarks>
+        public static bool IsAdministrator()
+        {
+            return (new WindowsPrincipal(WindowsIdentity.GetCurrent())).IsInRole(WindowsBuiltInRole.Administrator);
+        }
+
+        /// <summary>Shows or hides the button to elevate to administrator based on whether the program is already running as an administrator</summary>
+        private void SetupElevateButton()
+        {
+            if (IsAdministrator())
+                ElevateButton.Visible = false;
+            else
+                Win32Helpers.AddUacShield(ElevateButton);
+        }
+
+        /// <summary>Restarts the program, requesting elevation</summary>
+        private void ElevateButton_Click(object sender, EventArgs e)
+        {
+            ProcessStartInfo info = new ProcessStartInfo() { UseShellExecute = true, WorkingDirectory = Environment.CurrentDirectory, FileName = Application.ExecutablePath, Verb = "runas" };
+            try
+            {
+                Process.Start(info);
+                Application.Exit();
+            }
+            catch { } // Do nothing if the user canceled the consent dialog
+        }
+
+        /// <summary>Saves checkbox state so it can be restored when the program is relaunched later</summary>
+        private void SavePreferences()
+        {
+            try
+            {
+                RegistryKey key = Registry.CurrentUser.CreateSubKey(_registryKey);
+                key.SetValue(_showAllPreference, ShowAllCheckBox.Checked, RegistryValueKind.DWord);
+            }
+            catch { } // Silently fail if we can't save preferences
+        }
+
+        /// <summary>Restores the checkbox state to the same as when the program last exited</summary>
+        private void LoadPreferences()
+        {
+            try
+            {
+                RegistryKey key = Registry.CurrentUser.OpenSubKey(_registryKey);
+                ShowAllCheckBox.Checked = Convert.ToBoolean((int)key.GetValue(_showAllPreference, 0));
+            }
+            catch { } // Silently fail if we can't load preferences
+        }
+
+        private void ShortcutGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
+        {
+            string tooltip = null;
+            Shortcut shortcut = (Shortcut)ShortcutGrid.Rows[e.RowIndex].DataBoundItem;
+
+            if (shortcut.HasDuplicateHotkey)
+                tooltip = "More than one shortcut has the same hotkey assigned";
+            else if (shortcut.ReadOnly)
+                tooltip = "Administrative privileges are required to edit this shortcut." + Environment.NewLine + "To edit, click the \"Allow editing shortcuts shared by all users\" button.";
+
+            ShortcutGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].ToolTipText = tooltip;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/MainForm.resx	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="ShortcutColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <metadata name="HotkeyColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <metadata name="LocationColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <metadata name="FindShortcutWorker.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+  <metadata name="MainFormToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>178, 17</value>
+  </metadata>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAg
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKy8vVys3N/8rNzf/Kzc3/ys3N/8rN
+        zf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rN
+        zf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/zMG//8jNzfDUjUL/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9WLPP/Kzc3/yM3N8NWG
+        M//pu4v/782p/9aCKf/vzan/782p/9eGMf/tyaL/782p/9aCKf/vzan/782p/+/Nqf/vzan/782p/+/N
+        qf/vzan/782p/+/Nqf/vzan/14Yx/+3Jov/vzan/7cmi/9eGMf/vzan/782p/+/Nqf/sxJr/1oIp/8rN
+        zf/Izc3w1YYz//Xizf//////1oIp////////////2Ik2//348v//////1oIp////////////////////
+        ///////////////////////////////////YiTb//fjy///////9+PL/2Ik2//////////////////rw
+        5v/Wgin/ys3N/8jNzfDVhjP/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Kzc3/yM3N8NWGM//tyaL/782p/+/Nqf/Wgin/782p/+/Nqf/XhjH/7cmi/+/N
+        qf/XhjH/7cmi/+3Jov/XhjH/782p/+/Nqf/XhjH/7cmi/+/Nqf/XhjH/7cmi/+/Nqf/XhjH/7cmi/+/N
+        qf/Wgin/782p/+/Nqf/tyaL/1oIp/8rNzf/Izc3w1YYz//348v///////////9aCKf///////////9iJ
+        Nv/9+PL//////9iJNv/9+PL//fjy/9iJNv///////////9iJNv/9+PL//////9iJNv/9+PL//////9iJ
+        Nv/9+PL//////9aCKf////////////348v/Wgin/ys3N/8jNzfDVhjP/1oIp/9aCKf/Wgin/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Kzc3/yM3N8NWGM//tyaL/782p/9aC
+        Kf/vzan/782p/9aCKf/vzan/782p/9eGMf/tyaL/782p/9aCKf/vzan/782p/9aCKf/vzan/782p/9aC
+        Kf/vzan/7cmi/9eGMf/vzan/782p/9aCKf/vzan/782p/+/Nqf/tyaL/1oIp/8rNzf/Izc3w1YYz//34
+        8v//////1oIp////////////1oIp////////////2Ik2//348v//////1oIp////////////1oIp////
+        ////////1oIp///////9+PL/2Ik2////////////1oIp//////////////////348v/Wgin/ys3N/8jN
+        zfDViTj/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9WG
+        Mf/Kzc3/yM3N8MrNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rN
+        zf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rN
+        zf/Kzc3/ys3N/8rNzf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////////////////////
+        //////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAD/////////////////////////////////////////////////////KAAAABAAAAAgAAAAAQAgAAAA
+        AAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMnMzLLKzc3/ys3N/8rNzf/Kzc3/ys3N/8rN
+        zf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8nMzLLKzc3/1oIp/9aCKf/Wgin/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Kzc3/ys3N/9aCKf/17eT/9e3k/9aC
+        Kf/17eT/9e3k//Xt5P/17eT/9e3k//Xt5P/Wgin/9e3k//Xt5P/Wgin/ys3N/8rNzf/Wgin/1oIp/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/8rNzf/Kzc3/1oIp//Xt
+        5P/17eT/9e3k/9aCKf/17eT/1oIp//Xt5P/Wgin/9e3k/9aCKf/17eT/9e3k/9aCKf/Kzc3/ys3N/9aC
+        Kf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/ys3N/8rN
+        zf/Wgin/9e3k//Xt5P/Wgin/9e3k/9aCKf/17eT/1oIp//Xt5P/Wgin/9e3k/9aCKf/17eT/1oIp/8rN
+        zf/Kzc3/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aCKf/Wgin/1oIp/9aC
+        Kf/Kzc3/yczMssrNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rNzf/Kzc3/ys3N/8rN
+        zf/Kzc3/yczMsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAD//wAA//8AAP//AAD//wAA
+</value>
+  </data>
+</root>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Program.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace ShortcutKeyFinder
+{
+    static class Program
+    {
+        /// <summary>
+        /// The main entry point for the application.
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new MainForm());
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Properties/AssemblyInfo.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Shortcut Key Manager")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Brad Greco")]
+[assembly: AssemblyProduct("Shortcut Key Manager")]
+[assembly: AssemblyCopyright("Copyright © Brad Greco 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("04eab118-b2ce-4d90-acd0-475fb7973793")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Properties/Resources.Designer.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.17929
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ShortcutKeyFinder.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ShortcutKeyFinder.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Properties/Resources.resx	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Properties/Settings.Designer.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.17929
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ShortcutKeyFinder.Properties {
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default {
+            get {
+                return defaultInstance;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Properties/Settings.settings	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Shortcut.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,135 @@
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.IO;
+
+namespace ShortcutKeyFinder
+{
+    /// <summary>Class to perform operations on shortcut (.lnk) files</summary>
+    class Shortcut : INotifyPropertyChanged
+    {
+        private bool _hasDuplicateHotkey;
+        private Hotkey _hotkey;
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        /// <summary>Name of the shortcut as displayed by Windows Explorer</summary>
+        /// <remarks>Not necessarily the same as the shortcut's filename</remarks>
+        public string Name { get; private set; }
+
+        /// <summary>Full path of the shortcut file</summary>
+        public string Path { get; private set; }
+
+        /// <summary>Full path of the shortcut's parent directory</summary>
+        public string Location
+        {
+            get { return System.IO.Path.GetDirectoryName(Path); }
+        }
+
+        /// <summary>Display name of the shortcut's parent directory</summary>
+        /// <remarks>Gives the user a general idea of where the shortcut is located rather than the full path</remarks>
+        public string DisplayLocation
+        {
+            get {
+                return Location
+                    .Replace(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Start Menu")
+                    .Replace(Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu), "Start Menu")
+                    .Replace(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "Desktop")
+                    .Replace(Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory), "Desktop");
+            }
+
+        }
+
+        /// <summary>Hotkey associated with the shortcut</summary>
+        public Hotkey Hotkey
+        {
+            get { return _hotkey; }
+            set
+            {
+                if (value != _hotkey)
+                {
+                    _hotkey = value;
+                    NotifyPropertyChanged("Hotkey");
+                }
+            }
+        }
+
+        /// <summary>Icon associated with the shortcut</summary>
+        public Image Icon { get; private set; }
+
+        /// <summary>Indicates whether this shortcut's hotkey is the same as that of another shortcut</summary>
+        /// <remarks>Always false unless set to true by some other class</remarks>
+        public bool HasDuplicateHotkey
+        {
+            get { return _hasDuplicateHotkey; }
+            set
+            {
+                if (value != _hasDuplicateHotkey)
+                {
+                    _hasDuplicateHotkey = value;
+                    NotifyPropertyChanged("HasDuplicateHotkey");
+                }
+            }
+        }
+
+        /// <summary>Indicates whether the user has permissions to modify the shortcut</summary>
+        /// <remarks>Always false unless set to true by some other class</remarks>
+        public bool ReadOnly { get; set; }
+
+        /// <summary>Constructor</summary>
+        /// <param name="path">Full path to a shortcut (.lnk) file</param>
+        public Shortcut(string path)
+        {
+            Path = path;
+            LoadShortcutInfo();
+        }
+
+        /// <summary>Implements the <see cref="INotifyPropertyChanged"/> interface</summary>
+        private void NotifyPropertyChanged(String info)
+        {
+            if (PropertyChanged != null)
+            {
+                PropertyChanged(this, new PropertyChangedEventArgs(info));
+            }
+        }
+
+        /// <summary>Constructor</summary>
+        private void LoadShortcutInfo()
+        {
+            ushort hotkey = Win32Helpers.GetShortcutHotkey(Path);
+            if (hotkey != 0)
+                Hotkey = new Hotkey(hotkey);
+            FileInfo info = Win32Helpers.GetFileInfo(Path, true, true);
+            if (info != null)
+            {
+                Name = info.DisplayName;
+                Icon = info.Icon.ToBitmap();
+            }
+        }
+
+        /// <summary>Shows the Windows Explorer properties window for the shortcut</summary>
+        /// <param name="parentWindow">Optional handle to a parent window for use when displaying error messages</param>
+        public void ShowExplorerPropertiesWindow(IntPtr parentWindow = default(IntPtr))
+        {
+            Win32Helpers.ShowFilePropertiesWindow(Path, parentWindow);
+        }
+
+        /// <summary>Writes the shortcut data to disk</summary>
+        public void Save()
+        {
+            if (!Win32Helpers.SetShortcutHotkey(Path, Hotkey != null ? Hotkey.RawHotkey : (ushort)0))
+                throw new UnauthorizedAccessException();
+        }
+
+        /// <summary>Determine whether two shortcuts are the same file</summary>
+        public override bool Equals(object obj)
+        {
+            Shortcut shortcut = obj as Shortcut;
+            return shortcut != null && shortcut.Path == Path;
+        }
+
+        public override int GetHashCode()
+        {
+            return Path.GetHashCode();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/ShortcutKeyFinder.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+
+namespace ShortcutKeyFinder
+{
+    /// <summary>Class to find all shortcuts that have hotkeys assigned</summary>
+    /// <remarks>As far as I can tell, Windows only looks for shortcut hotkeys in the Start Menu and on the Desktop. So this class does as well.</remarks>
+    class ShortcutKeyFinder
+    {
+        private bool _showAll;
+        private List<Shortcut> _allShortcuts = new List<Shortcut>(); // List to hold all the shortcuts we find, regardless of whether they have hotkeys
+
+        /// <summary>List of all shortcuts found in the search locations</summary>
+        /// <remarks>If <see cref="ShowAll"/> is true, shortcuts without assigned hotkeys are included</remarks>
+        public BindingList<Shortcut> Shortcuts { get; private set; }
+
+        /// <summary>Indicates whether to include shortcuts without assigned hotkeys in the list of <see cref="Shortcuts"/></summary>
+        public bool ShowAll
+        {
+            get { return _showAll; }
+            set
+            {
+                _showAll = value;
+                UpdateShortcutList();
+            }
+        }
+
+        /// <summary>Constructor</summary>
+        public ShortcutKeyFinder()
+        {
+            Shortcuts = new BindingList<Shortcut>();
+        }
+
+        /// <summary>Scans the search locations for shortcut files</summary>
+        /// <param name="Admin">Whether to consider shortcuts owned by all users as read-only</param>
+        public void LoadShortcuts(bool Admin = false)
+        {
+            // Reset the master list and rescan the search directories
+            _allShortcuts.Clear();
+            LoadShortcuts(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), false);
+            LoadShortcuts(Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu), !Admin);
+            LoadShortcuts(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), false);
+            LoadShortcuts(Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory), !Admin);
+            // Update the public list with what we found
+            UpdateShortcutList();
+        }
+
+        /// <summary>Recursively scans a directory shortcut files</summary>
+        /// <param name="path">Directory to scan</param>
+        /// <param name="readOnly">Whether to consider the found shortcuts as read-only</param>
+        /// <remarks>The <paramref name="readOnly"/> parameter exists for speed, to avoid checking permissions on every file. Good enough for our purposes.</remarks>
+        private void LoadShortcuts(string path, bool readOnly)
+        {
+            try
+            {
+                foreach (string lnk in Directory.EnumerateFiles(path, "*.lnk", SearchOption.AllDirectories))
+                    _allShortcuts.Add(new Shortcut(lnk) { ReadOnly = readOnly });
+            }
+            catch { } // Silently ignore access denied errors, likely nothing the user can do about it anyway
+        }
+
+        /// <summary>Updates the public list of <see cref="Shortcuts"/> with the results of the most recent scan</summary>
+        private void UpdateShortcutList()
+        {
+            // Remove event handlers while we're manipulating the list
+            Shortcuts.ListChanged -= Shortcuts_ListChanged;
+
+            // Reset the list and add all appropriate shortcuts based on the current value of ShowAll
+            Shortcuts.Clear();
+            foreach (Shortcut shortcut in _allShortcuts)
+                if (shortcut.Hotkey != null || _showAll)
+                    Shortcuts.Add(shortcut);
+
+            // Go through the list and look for duplicates
+            FindDuplicateShortcuts();
+
+            // Re-add event handlers
+            Shortcuts.ListChanged += Shortcuts_ListChanged;
+        }
+
+        /// <summary>Triggers a search for duplicate shortcuts when one of the shortcuts changes</summary>
+        void Shortcuts_ListChanged(object sender, ListChangedEventArgs e)
+        {
+            FindDuplicateShortcuts();
+        }
+
+        /// <summary>Scans for shortcuts that have duplicate hotkeys</summary>
+        private void FindDuplicateShortcuts()
+        {
+            Shortcuts.GroupBy(shortcut => shortcut.Hotkey).ToList().ForEach(group => group.ToList().ForEach(shortcut => shortcut.HasDuplicateHotkey = group.Count() > 1 && group.Key != null));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/ShortcutKeyFinder.csproj	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{0BFA5ACD-DDA4-4723-AFE2-3E66EECFEAB1}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ShortcutKeyFinder</RootNamespace>
+    <AssemblyName>ShortcutKeyFinder</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <TargetFrameworkProfile />
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Prefer32Bit>false</Prefer32Bit>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Prefer32Bit>false</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>false</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <NoWin32Manifest>true</NoWin32Manifest>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>keyboard.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Net" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Hotkey.cs" />
+    <Compile Include="MainForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="MainForm.Designer.cs">
+      <DependentUpon>MainForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Shortcut.cs" />
+    <Compile Include="ShortcutKeyFinder.cs" />
+    <Compile Include="Win32Helpers.cs" />
+    <EmbeddedResource Include="MainForm.resx">
+      <DependentUpon>MainForm.cs</DependentUpon>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+      <DesignTime>True</DesignTime>
+    </Compile>
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.0">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Windows.Installer.4.5">
+      <Visible>False</Visible>
+      <ProductName>Windows Installer 4.5</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="keyboard.ico" />
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Images\" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/ShortcutKeyFinder.csproj.user	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <PublishUrlHistory>publish\</PublishUrlHistory>
+    <InstallUrlHistory />
+    <SupportUrlHistory />
+    <UpdateUrlHistory />
+    <BootstrapperUrlHistory />
+    <ErrorReportUrlHistory />
+    <FallbackCulture>en-US</FallbackCulture>
+    <VerifyUploadedFiles>false</VerifyUploadedFiles>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ShortcutKeyFinder/Win32Helpers.cs	Sat Jun 25 13:42:54 2016 +1000
@@ -0,0 +1,297 @@
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using System.Text;
+
+namespace ShortcutKeyFinder
+{
+    /// <summary>Various functions to call the Win32 API</summary>
+    /// <remarks>Method signatures taken from http://www.pinvoke.net/ </remarks>
+    class Win32Helpers
+    {
+        public const int STGM_READ = 0x00000000;
+        public const int STGM_READWRITE = 0x00000002;
+        public const int ILD_NORMAL = 0;
+        public const int SW_SHOWNORMAL = 1;
+        public const uint SEE_MASK_INVOKEIDLIST = 0x0000000C;
+        public const uint BCM_SETSHIELD = 0x160C;
+        public const int SHGFI_SMALLICON = 0x000000001;
+        public const int SHGFI_ICON         = 0x000000100;
+        public const int SHGFI_DISPLAYNAME  = 0x000000200;
+        public const int SHGFI_SYSICONINDEX = 0x000004000;
+
+
+        #region GetShortcutHotkey, SetShortcutHotkey
+
+        // The CharSet must match the CharSet of the corresponding PInvoke signature
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+        struct WIN32_FIND_DATAW
+        {
+            public uint dwFileAttributes;
+            public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
+            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
+            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
+            public uint nFileSizeHigh;
+            public uint nFileSizeLow;
+            public uint dwReserved0;
+            public uint dwReserved1;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
+            public string cFileName;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
+            public string cAlternateFileName;
+        }
+
+        /// <summary>IShellLink.GetPath fFlags: Flags that specify the type of path information to retrieve</summary>
+        [Flags()]
+        enum SLGP_FLAGS
+        {
+            /// <summary>Retrieves the standard short (8.3 format) file name</summary>
+            SLGP_SHORTPATH = 0x1,
+            /// <summary>Retrieves the Universal Naming Convention (UNC) path name of the file</summary>
+            SLGP_UNCPRIORITY = 0x2,
+            /// <summary>Retrieves the raw path name. A raw path is something that might not exist and may include environment variables that need to be expanded</summary>
+            SLGP_RAWPATH = 0x4
+        }
+
+        /// <summary>IShellLink.Resolve fFlags</summary>
+        [Flags()]
+        enum SLR_FLAGS
+        {
+            /// <summary>
+            /// Do not display a dialog box if the link cannot be resolved. When SLR_NO_UI is set,
+            /// the high-order word of fFlags can be set to a time-out value that specifies the
+            /// maximum amount of time to be spent resolving the link. The function returns if the
+            /// link cannot be resolved within the time-out duration. If the high-order word is set
+            /// to zero, the time-out duration will be set to the default value of 3,000 milliseconds
+            /// (3 seconds). To specify a value, set the high word of fFlags to the desired time-out
+            /// duration, in milliseconds.
+            /// </summary>
+            SLR_NO_UI = 0x1,
+            /// <summary>Obsolete and no longer used</summary>
+            SLR_ANY_MATCH = 0x2,
+            /// <summary>If the link object has changed, update its path and list of identifiers.
+            /// If SLR_UPDATE is set, you do not need to call IPersistFile::IsDirty to determine
+            /// whether or not the link object has changed.</summary>
+            SLR_UPDATE = 0x4,
+            /// <summary>Do not update the link information</summary>
+            SLR_NOUPDATE = 0x8,
+            /// <summary>Do not execute the search heuristics</summary>
+            SLR_NOSEARCH = 0x10,
+            /// <summary>Do not use distributed link tracking</summary>
+            SLR_NOTRACK = 0x20,
+            /// <summary>Disable distributed link tracking. By default, distributed link tracking tracks
+            /// removable media across multiple devices based on the volume name. It also uses the
+            /// Universal Naming Convention (UNC) path to track remote file systems whose drive letter
+            /// has changed. Setting SLR_NOLINKINFO disables both types of tracking.</summary>
+            SLR_NOLINKINFO = 0x40,
+            /// <summary>Call the Microsoft Windows Installer</summary>
+            SLR_INVOKE_MSI = 0x80
+        }
+
+        /// <summary>The IShellLink interface allows Shell links to be created, modified, and resolved</summary>
+        [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")]
+        interface IShellLinkW
+        {
+            /// <summary>Retrieves the path and file name of a Shell link object</summary>
+            void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
+            /// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
+            void GetIDList(out IntPtr ppidl);
+            /// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
+            void SetIDList(IntPtr pidl);
+            /// <summary>Retrieves the description string for a Shell link object</summary>
+            void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
+            /// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
+            void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
+            /// <summary>Retrieves the name of the working directory for a Shell link object</summary>
+            void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
+            /// <summary>Sets the name of the working directory for a Shell link object</summary>
+            void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
+            /// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
+            void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
+            /// <summary>Sets the command-line arguments for a Shell link object</summary>
+            void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
+            /// <summary>Retrieves the hot key for a Shell link object</summary>
+            void GetHotkey(out ushort pwHotkey);
+            /// <summary>Sets a hot key for a Shell link object</summary>
+            void SetHotkey(ushort wHotkey);
+            /// <summary>Retrieves the show command for a Shell link object</summary>
+            void GetShowCmd(out int piShowCmd);
+            /// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
+            void SetShowCmd(int iShowCmd);
+            /// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
+            void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
+                int cchIconPath, out int piIcon);
+            /// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
+            void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
+            /// <summary>Sets the relative path to the Shell link object</summary>
+            void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
+            /// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
+            void Resolve(IntPtr hwnd, SLR_FLAGS fFlags);
+            /// <summary>Sets the path and file name of a Shell link object</summary>
+            void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
+        }
+
+        /// <summary>Retrieves the hotkey assigned to a shortcut (.lnk) file</summary>
+        /// <param name="path">Path to a shortcut (.lnk) file</param>
+        /// <returns>A binary value representing the shortcut's hotkey</returns>
+        public static ushort GetShortcutHotkey(string path)
+        {
+            Type obj = Type.GetTypeFromCLSID(new Guid("00021401-0000-0000-C000-000000000046"), true);
+            IShellLinkW link = Activator.CreateInstance(obj) as IShellLinkW;
+
+            ((IPersistFile)link).Load(path, STGM_READ);
+            ushort hotkey;
+            link.GetHotkey(out hotkey);
+            return hotkey;
+        }
+
+        /// <summary>Assigns a hotkey to a shortcut (.lnk) file</summary>
+        /// <param name="path">Path to an exising shortcut (.lnk) file to modify</param>
+        /// <param name="hotkey">Binary value containing the hotkey to set</param>
+        public static bool SetShortcutHotkey(string path, ushort hotkey)
+        {
+            Type obj = Type.GetTypeFromCLSID(new Guid("00021401-0000-0000-C000-000000000046"), true);
+            IShellLinkW link = Activator.CreateInstance(obj) as IShellLinkW;
+
+            try
+            {
+                ((IPersistFile)link).Load(path, STGM_READWRITE);
+                link.SetHotkey(hotkey);
+                ((IPersistFile)link).Save(path, false);
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        #endregion
+
+        #region GetFileInfo
+
+        [DllImport("shell32.dll", CharSet = CharSet.Auto)]
+        public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
+
+        [DllImport("comctl32.dll", SetLastError = true)]
+        public static extern IntPtr ImageList_GetIcon(IntPtr himl, int i, int flags);
+
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+        public struct SHFILEINFO
+        {
+            public IntPtr hIcon;
+            public int iIcon;
+            public uint dwAttributes;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
+            public string szDisplayName;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
+            public string szTypeName;
+        };
+
+        /// <summary>Retrieves the hotkey assigned to a shortcut (.lnk) file</summary>
+        /// <param name="path">Path of the file to retreive properties from</param>
+        /// <param name="smallIcon">Set to true to retreive the 16x16 icon. By default, the 32x32 icon is retrieved.</param>
+        /// <param name="noOverlays">Set to true to remove icon overlays as displayed in Windows Explorer (for example, the shortcut arrow)</param>
+        public static FileInfo GetFileInfo(string path, bool smallIcon = false, bool noOverlays = false)
+        {
+            SHFILEINFO info = new SHFILEINFO();
+            uint flags = SHGFI_DISPLAYNAME;
+            if (smallIcon)
+                flags |= SHGFI_SMALLICON;
+            if (noOverlays)
+                flags |= SHGFI_SYSICONINDEX;
+            else
+                flags |= SHGFI_ICON;
+            IntPtr shgfi = SHGetFileInfo(path, 0, ref info, (uint)Marshal.SizeOf(info), flags);
+
+            if (shgfi == IntPtr.Zero)
+                return null;
+
+            if (noOverlays)
+            {
+                IntPtr icon = ImageList_GetIcon(shgfi, info.iIcon, ILD_NORMAL);
+                return new FileInfo(info.szDisplayName, icon != IntPtr.Zero ? Icon.FromHandle(icon) : null);
+            }
+            else
+                return new FileInfo(info.szDisplayName, Icon.FromHandle(info.hIcon));
+        }
+
+        #endregion
+
+        #region ShowFilePropertiesWindow
+
+        [DllImport("shell32.dll", CharSet = CharSet.Auto)]
+        static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct SHELLEXECUTEINFO
+        {
+            public int cbSize;
+            public uint fMask;
+            public IntPtr hwnd;
+            [MarshalAs(UnmanagedType.LPTStr)]
+            public string lpVerb;
+            [MarshalAs(UnmanagedType.LPTStr)]
+            public string lpFile;
+            [MarshalAs(UnmanagedType.LPTStr)]
+            public string lpParameters;
+            [MarshalAs(UnmanagedType.LPTStr)]
+            public string lpDirectory;
+            public int nShow;
+            public IntPtr hInstApp;
+            public IntPtr lpIDList;
+            [MarshalAs(UnmanagedType.LPTStr)]
+            public string lpClass;
+            public IntPtr hkeyClass;
+            public uint dwHotKey;
+            public IntPtr hIcon;
+            public IntPtr hProcess;
+        }
+
+        /// <summary>Displays the Properties window of a file</summary>
+        /// <param name="path">File to display the properties window for</param>
+        /// <param name="parentWindow">Handle to the parent window for error messages to be displayed</param>
+        public static void ShowFilePropertiesWindow(string path, IntPtr parentWindow = default(IntPtr))
+        {
+            SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
+            info.cbSize = Marshal.SizeOf(info);
+            info.fMask = SEE_MASK_INVOKEIDLIST;
+            info.hwnd = parentWindow;
+            info.lpVerb = "properties";
+            info.lpFile = path;
+            info.nShow = SW_SHOWNORMAL;
+            ShellExecuteEx(ref info);
+        }
+
+        #endregion
+
+        #region AddUacShield
+
+        [DllImport("user32", CharSet = CharSet.Auto, SetLastError = true)]
+        static extern int SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, IntPtr lParam);
+
+        /// <summary>Adds a UAC shield icon to a button</summary>
+        /// <param name="button">Button to add the shield icon to</param>
+        public static void AddUacShield(System.Windows.Forms.Button button)
+        {
+            button.FlatStyle = System.Windows.Forms.FlatStyle.System;
+            SendMessage(button.Handle, BCM_SETSHIELD, 0, (IntPtr)1);
+        }
+
+        #endregion
+    }
+
+    /// <summary>Class to hold basic file information</summary>
+    class FileInfo
+    {
+        public string DisplayName;
+        public Icon Icon;
+
+        public FileInfo(string DisplayName, Icon Icon)
+        {
+            this.DisplayName = DisplayName;
+            this.Icon = Icon;
+        }
+    }
+}
Binary file ShortcutKeyFinder/keyboard.ico has changed