diff --git a/Windows.Shell/ShellCommand.cs b/Windows.Shell/ShellCommand.cs
new file mode 100644
index 00000000..07fdba8b
--- /dev/null
+++ b/Windows.Shell/ShellCommand.cs
@@ -0,0 +1,53 @@
+using System;
+using Vanara.PInvoke;
+using static Vanara.PInvoke.Ole32;
+using static Vanara.PInvoke.Shell32;
+
+namespace Vanara.Windows.Shell
+{
+ ///
+ /// Wraps the functionality of IInitializeCommand. When deriving, handling the event is optional.
+ ///
+ ///
+ ///
+ public abstract class ShellCommand : ComObject, IInitializeCommand
+ {
+ /// Initializes a new instance of the class.
+ protected ShellCommand() : base()
+ {
+ }
+
+ /// Initializes a new instance of the class.
+ /// The context within which the COM object is to be run.
+ /// Indicates how connections are made to the class object.
+ protected ShellCommand(CLSCTX classContext, REGCLS classUse) : base(classContext, classUse)
+ {
+ }
+
+ /// Occurs when the shell command is initialized.
+ public event EventHandler InitializeCommand;
+
+ /// Gets the name of the command returned by IInitializeCommand.Initialize.
+ ///
+ /// The name of the command as found in the registry. This value is until IInitializeCommand.Initialize
+ /// is called by the host.
+ ///
+ public string CommandName { get; private set; }
+
+ /// Gets the properties exposed through IInitializeCommand.Initialize.
+ ///
+ /// Gets a instance. This value is until IInitializeCommand.Initialize is
+ /// called by the host.
+ ///
+ public PropertyBag Properties { get; private set; }
+
+ ///
+ HRESULT IInitializeCommand.Initialize(string pszCommandName, IPropertyBag ppb)
+ {
+ CommandName = pszCommandName;
+ Properties = new PropertyBag(ppb);
+ InitializeCommand?.Invoke(this, EventArgs.Empty);
+ return HRESULT.S_OK;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Windows.Shell/ShellExecuteCommand.cs b/Windows.Shell/ShellExecuteCommand.cs
new file mode 100644
index 00000000..ab9a5fd3
--- /dev/null
+++ b/Windows.Shell/ShellExecuteCommand.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Drawing;
+using Vanara.PInvoke;
+using static Vanara.PInvoke.Ole32;
+using static Vanara.PInvoke.Shell32;
+
+namespace Vanara.Windows.Shell
+{
+ ///
+ /// Wraps the functionality of IExecuteCommand. To implement, derive from this class and override the method. All Shell items
+ /// passed to the command are available through the property.
+ ///
+ ///
+ /// This class provides an easy means of creating a COM based out-of-process DropTarget.
+ ///
+ /// - Create a .NET application project.
+ /// - Delete the Program.cs file that includes the Main method.
+ /// - Create a new class and derive it from with the attributes in the example below.
+ /// - Ensure the project is built as "X86".
+ ///
+ /// // Full implementation of a shell context menu handler using IExecuteCommand.
+ ///[ComVisible(true), Guid("<Your GUID here>"), ClassInterface(ClassInterfaceType.None)]
+ ///public class MyExecCmd : ShellExecuteCommand
+ ///{
+ /// // *** Replace with your ProgID, verb name and display name ***
+ /// const string progID = "txtfile";
+ /// const string verbName = "ExecuteCommandVerb";
+ /// const string verbDisplayName = "ExecuteCommand Verb Sample";
+ ///
+ /// // Overridden method performs all the functionality of your verb handler. The properties from ShellExecuteCommand should all be set
+ /// // and can be used for your implementation. Once you have completed all work required of this command, you must call the
+ /// // QuitMessageLoop method to finish processing all messages and then exit. If you fail to call QuitMessageLoop, this process will run
+ /// // indefinitely and future context menu requests will not be handled.
+ /// public override void OnExecute()
+ /// {
+ /// var szMsg = $"Found {SelectedItems.Count} item(s) called with '{CommandName}' verb: " + string.Join(", ", SelectedItems.Select(i => i.Name));
+ /// if (!UIDisplayBlocked)
+ /// MessageBox.Show(szMsg, verbDisplayName);
+ /// // Don't fail to call QuitMessageLoop once you're done processing the Shell call.
+ /// QuitMessageLoop();
+ /// }
+ ///
+ /// // This is the main thread of the application. When called from the Shell, it will append the -embedding argument to indicate that a
+ /// // message loop needs to start running and the COM server registered. All that is accomplished by calling the 'Run' method on your
+ /// // new class. You may choose to register or unregister based on alternate command-line arguments. It is highly recommended that you
+ /// // supply a timeout to the Run method to prevent a failure from leaving this process running indefinitely. The timeout is automatically
+ /// // canceled once the OnExecute method is called so as to prevent long-running code from being terminated. You must specify the
+ /// // [STAThread] attribute on this method for the handler to function.
+ /// [STAThread]
+ /// private static void Main(string[] args)
+ /// {
+ /// if (args.Length > 0 && args[0] == "-embedding")
+ /// new MyExecCmd().Run(TimeSpan.FromSeconds(30));
+ /// }
+ ///
+ /// // This method registers this out-of-proc server to handle the DelegateExecute. This can be omitted if done by an installer.
+ /// private static void Register()
+ /// {
+ /// ShellRegistrar.RegisterLocalServer<MyExecCmd>(verbDisplayName, systemWide: false);
+ /// using (var progid = new ProgId(progID, false))
+ /// using (var verb = progid.Verbs.Add(verbName, verbDisplayName))
+ /// verb.DelegateExecute = Marshal.GenerateGuidForType(typeof(MyExecCmd));
+ /// }
+ ///
+ /// // This method unregisters this out-of-proc server from handling the DelegateExecute. This can be omitted if done by an uninstaller.
+ /// private static void Unregister()
+ /// {
+ /// using (var progid = new ProgId(progID, false))
+ /// progid.Verbs.Remove(verbName);
+ /// ShellRegistrar.UnregisterLocalServer<MyExecCmd>(false);
+ /// }
+ ///}
+ ///
+ ///
+ ///
+ public abstract class ShellExecuteCommand : ShellCommand, IExecuteCommand, IObjectWithSelection
+ {
+ /// Initializes a new instance of the class.
+ protected ShellExecuteCommand() : base()
+ {
+ }
+
+ /// Initializes a new instance of the class.
+ /// The context within which the COM object is to be run.
+ /// Indicates how connections are made to the class object.
+ protected ShellExecuteCommand(CLSCTX classContext, REGCLS classUse) : base(classContext, classUse)
+ {
+ }
+
+ /// Gets a value based on the current state of the keys CTRL and SHIFT.
+ /// The value based on the current state of the keys CTRL and SHIFT.
+ public User32.MouseButtonState KeyState { get; private set; }
+
+ /// Gets a new working directory. This value is if the current working directory is to be used.
+ /// Returns a value.
+ public string NewWorkingDirectory { get; private set; }
+
+ /// Gets the parameter values for the verb.
+ /// Returns a value.
+ public string Parameters { get; private set; }
+
+ ///
+ /// Gets the screen coordinates at which the user right-clicked to invoke the shortcut menu from which a command was chosen.
+ /// Applications can use this information to present any UI. This is particularly useful in a multi-monitor situation. The default
+ /// position is the center of the default monitor.
+ ///
+ /// Returns a value.
+ public Point Position { get; private set; }
+
+ /// Gets or sets the selected shell items.
+ /// The selected shell items.
+ public ShellItemArray SelectedItems { get; private set; }
+
+ /// Gets a value indicating whether any UI associated with the selected Shell item should be displayed.
+ /// if display of any associated UI is blocked; otherwise, .
+ public bool UIDisplayBlocked { get; private set; }
+
+ /// Gets the specified window's visual state.
+ /// Returns a value.
+ public ShowWindowCommand WindowState { get; private set; }
+
+ /// Called in response to IExecuteCommand.Execute().
+ public abstract void OnExecute();
+
+ ///
+ HRESULT IExecuteCommand.Execute()
+ {
+ QueueNonBlockingCallback(o => OnExecute());
+ CancelTimeout();
+ return HRESULT.S_OK;
+ }
+
+ ///
+ HRESULT IObjectWithSelection.GetSelection(in Guid riid, out object ppv)
+ {
+ if (SelectedItems is null)
+ {
+ ppv = null;
+ return HRESULT.E_NOINTERFACE;
+ }
+ ppv = ShellUtil.QueryInterface(SelectedItems, riid);
+ return HRESULT.S_OK;
+ }
+
+ ///
+ HRESULT IExecuteCommand.SetDirectory(string pszDirectory)
+ {
+ NewWorkingDirectory = pszDirectory;
+ return HRESULT.S_OK;
+ }
+
+ ///
+ HRESULT IExecuteCommand.SetKeyState(User32.MouseButtonState grfKeyState)
+ {
+ KeyState = grfKeyState;
+ return HRESULT.S_OK;
+ }
+
+ ///
+ HRESULT IExecuteCommand.SetNoShowUI(bool fNoShowUI)
+ {
+ UIDisplayBlocked = fNoShowUI;
+ return HRESULT.S_OK;
+ }
+
+ ///
+ HRESULT IExecuteCommand.SetParameters(string pszParameters)
+ {
+ Parameters = pszParameters;
+ return HRESULT.S_OK;
+ }
+
+ ///
+ HRESULT IExecuteCommand.SetPosition(Point pt)
+ {
+ Position = pt;
+ return HRESULT.S_OK;
+ }
+
+ ///
+ HRESULT IObjectWithSelection.SetSelection(IShellItemArray psia)
+ {
+ SelectedItems = new ShellItemArray(psia);
+ return HRESULT.S_OK;
+ }
+
+ ///
+ HRESULT IExecuteCommand.SetShowWindow(ShowWindowCommand nShow)
+ {
+ WindowState = nShow;
+ return HRESULT.S_OK;
+ }
+ }
+}
\ No newline at end of file