├── .gitignore ├── myuuid.h ├── LICENSE ├── ExecuteCommand.sln ├── readme.txt ├── VerbHelpers.h ├── RegisterExtension.h ├── README.md ├── ShellHelpers.h ├── ExecuteCommand.vcxproj ├── ExecuteCommandVerb.cpp └── RegisterExtension.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | x64 3 | Release 4 | Debug 5 | build32 6 | build64 7 | *.vcxproj.user -------------------------------------------------------------------------------- /myuuid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define MYUUID "FFA07888-75BD-471A-B325-59274E73401F" 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | Portions of this repo are provided under the SIL Open Font License. 24 | See the LICENSE file in individual samples for additional details. 25 | -------------------------------------------------------------------------------- /ExecuteCommand.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExecuteCommand", "ExecuteCommand.vcxproj", "{3FD4769C-8073-4A26-AB36-7F3C776F07BF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Release|Win32 = Release|Win32 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF}.Debug|Win32.Build.0 = Debug|Win32 18 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF}.Debug|x64.ActiveCfg = Debug|x64 19 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF}.Debug|x64.Build.0 = Debug|x64 20 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF}.Release|Win32.ActiveCfg = Release|Win32 21 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF}.Release|Win32.Build.0 = Release|Win32 22 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF}.Release|x64.ActiveCfg = Release|x64 23 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {8A31B2B0-0F42-474B-96A7-096F72784FD4} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Execute Command Verb Sample 2 | ================================ 3 | Demonstrates how implement a shell verb using the ExecuteCommand method. This method is preferred for verb implementations as it provides the most flexibility, it is simple, and supports out of process activation. This sample implements a standalone local server COM object but it is expected that the verb implementation will be integrated into existing applications. to do that have your main application object register a class factory for itself and have that object implement IDropTarget for the verbs of your application. Note that COM will launch your application if it is not already running and will connect to an already running instance of your application if it is already running. These are features of the COM based verb implementation methods. 4 | 5 | 6 | Sample Language Implementations 7 | =============================== 8 | C++ 9 | 10 | Files: 11 | ============================================= 12 | ExecuteCommand.sln 13 | ExecuteCommand.vcproj 14 | ExecuteCommandVerb.cpp 15 | RegisterExtension.cpp 16 | RegisterExtension.h 17 | ShellHelpers.h 18 | VerbHelpers.h 19 | 20 | 21 | To build the sample using the command prompt: 22 | ============================================= 23 | 1. Open the Command Prompt window and navigate to the ExecuteCommandVerb directory. 24 | 2. Type msbuild ExecuteCommand.sln. 25 | 26 | 27 | To build the sample using Visual Studio (preferred method): 28 | =========================================================== 29 | 1. Open Windows Explorer and navigate to the ExecuteCommandVerb directory. 30 | 2. Double-click the icon for the ExecuteCommand.sln file to open the file in Visual Studio. 31 | 3. In the Build menu, select Build Solution. The application will be built in the default \Debug or \Release directory. 32 | 33 | 34 | To run the sample: 35 | ================= 36 | 1. Navigate to the directory that contains the new executable using the command prompt. 37 | 2. Run ExecuteCommand.exe 38 | 3. Follow the instructions in the displayed dialog 39 | -------------------------------------------------------------------------------- /VerbHelpers.h: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | 8 | #pragma once 9 | 10 | // template class that encapsulates a local server class factory to be declared on the stack 11 | // that will factory an already existing object provided to the constructor 12 | // usually that object is the application or a sub object within the 13 | // application. 14 | // 15 | // class __declspec(uuid("")) CMyClass : public IUnknown 16 | // { 17 | // public: 18 | // IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv); 19 | // 20 | // as follows 21 | // 22 | // CStaticClassFactory classFactory(this); 23 | // hr = classFactory.Register(CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE); 24 | // if (SUCCEEDED(classFactory.Register(CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE))) 25 | // { 26 | // classFactory.MessageLoop() 27 | // } 28 | 29 | template class CStaticClassFactory : public IClassFactory 30 | { 31 | public: 32 | CStaticClassFactory(IUnknown *punkObject) : _dwRegisterClass(0), _punkObject(punkObject) 33 | { 34 | _punkObject->AddRef(); 35 | } 36 | 37 | ~CStaticClassFactory() 38 | { 39 | if (_dwRegisterClass) 40 | { 41 | CoRevokeClassObject(_dwRegisterClass); 42 | } 43 | _punkObject->Release(); 44 | } 45 | 46 | HRESULT Register(CLSCTX classContent, REGCLS classUse) 47 | { 48 | return CoRegisterClassObject(__uuidof(TObjectToFactory), static_cast(this), 49 | classContent, classUse, &_dwRegisterClass); 50 | } 51 | 52 | // IUnknown 53 | IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv) 54 | { 55 | static const QITAB qit[] = { QITABENT(CStaticClassFactory, IClassFactory), { 0 }, }; 56 | return QISearch(this, qit, riid, ppv); 57 | } 58 | 59 | IFACEMETHODIMP_(ULONG) AddRef() 60 | { 61 | return 2; 62 | } 63 | 64 | IFACEMETHODIMP_(ULONG) Release() 65 | { 66 | return 1; 67 | } 68 | 69 | // IClassFactory 70 | IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) 71 | { 72 | if (punkOuter) 73 | { 74 | *ppv = NULL; 75 | return CLASS_E_NOAGGREGATION; 76 | } 77 | return _punkObject->QueryInterface(riid, ppv); // : TObjectToFactory::CreateInstance(riid, ppv); 78 | } 79 | 80 | IFACEMETHODIMP LockServer(BOOL /* fLock */) 81 | { 82 | return S_OK; 83 | } 84 | 85 | private: 86 | DWORD _dwRegisterClass; 87 | IUnknown *_punkObject; 88 | }; 89 | 90 | 91 | // this class encapsulates the management of a message loop for an 92 | // application. it supports queing a callback to the application via the message 93 | // loop to enable the app to return from a call and continue processing that call 94 | // later. this behavior is needed when implementing a shell verb as verbs 95 | // must not block the caller. to use this class: 96 | // 97 | // 1) inherit from this class 98 | // 99 | // class CApplication : CAppMessageLoop 100 | // 101 | // 2) in the invoke function, for example IExecuteCommand::Execute() or IDropTarget::Drop() 102 | // queue a callback by callong QueueAppCallback(); 103 | // 104 | // IFACEMETHODIMP CExecuteCommandVerb::Execute() 105 | // { 106 | // QueueAppCallback(); 107 | // } 108 | // 109 | // 3) implement OnAppCallback, this is the code that will execute the queued callback 110 | // void OnAppCallback() 111 | // { 112 | // // do the work here 113 | // } 114 | 115 | class CAppMessageLoop 116 | { 117 | protected: 118 | // this timer is used to exit the message loop if the the application is 119 | // activated but not invoked. this is needed if there is a failure when the 120 | // verb is being invoked due to low resources or some other uncommon reason. 121 | // without this the app would not exit in this case. this timer needs to be 122 | // canceled once the app learns that it has should remain running. 123 | static UINT const uTimeout = 30 * 1000; // 30 seconds 124 | CAppMessageLoop() : _uTimeoutTimerId(SetTimer(NULL, 0, uTimeout, NULL)), _uPostTimerId(0) 125 | { 126 | } 127 | 128 | void QueueAppCallback() 129 | { 130 | // queue a callback on OnAppCallback() by setting a timer of zero seconds 131 | _uPostTimerId = SetTimer(NULL, 0, 0, NULL); 132 | if (_uPostTimerId) 133 | { 134 | CancelTimeout(); 135 | } 136 | } 137 | 138 | // cancel the timeout timer, this should be called when the appliation 139 | // knows that it wants to keep running, for example when it recieves the 140 | // incomming call to invoke the verb, this is done implictly when the 141 | // app queues a callback 142 | 143 | void CancelTimeout() 144 | { 145 | if (_uTimeoutTimerId) 146 | { 147 | KillTimer(NULL, _uTimeoutTimerId); 148 | _uTimeoutTimerId = 0; 149 | } 150 | } 151 | 152 | void MessageLoop() 153 | { 154 | MSG msg; 155 | while (GetMessage(&msg, NULL, 0, 0)) 156 | { 157 | if (msg.message == WM_TIMER) 158 | { 159 | KillTimer(NULL, msg.wParam); // all are one shot timers 160 | 161 | if (msg.wParam == _uTimeoutTimerId) 162 | { 163 | _uTimeoutTimerId = 0; 164 | } 165 | else if (msg.wParam == _uPostTimerId) 166 | { 167 | _uPostTimerId = 0; 168 | OnAppCallback(); 169 | } 170 | PostQuitMessage(0); 171 | } 172 | 173 | TranslateMessage(&msg); 174 | DispatchMessage(&msg); 175 | } 176 | } 177 | 178 | void virtual OnAppCallback() = 0; // app must implement 179 | 180 | private: 181 | UINT_PTR _uTimeoutTimerId; // timer id used to exit the app if the app is not called back within a certain time 182 | UINT_PTR _uPostTimerId; // timer id used to to queue a callback to the app 183 | }; 184 | 185 | -------------------------------------------------------------------------------- /RegisterExtension.h: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | // production code should use an installer technology like MSI to register its handlers 13 | // rather than this class. 14 | // this class is used for demontration purpouses, it encapsulate the different types 15 | // of handler registrations, schematizes those by provding methods that have parameters 16 | // that map to the supported extension schema and makes it easy to create self registering 17 | // .exe and .dlls 18 | 19 | class CRegisterExtension 20 | { 21 | public: 22 | CRegisterExtension(REFCLSID clsid = CLSID_NULL, HKEY hkeyRoot = HKEY_CURRENT_USER); 23 | ~CRegisterExtension(); 24 | void SetHandlerCLSID(REFCLSID clsid); 25 | void SetInstallScope(HKEY hkeyRoot); 26 | HRESULT SetModule(PCWSTR pszModule); 27 | HRESULT SetModule(HINSTANCE hinst); 28 | 29 | HRESULT RegisterInProcServer(PCWSTR pszFriendlyName, PCWSTR pszThreadingModel) const; 30 | HRESULT RegisterInProcServerAttribute(PCWSTR pszAttribute, DWORD dwValue) const; 31 | 32 | // register the COM local server for the current running module 33 | // this is for self registering applications 34 | HRESULT RegisterAppAsLocalServer(PCWSTR pszFriendlyName, PCWSTR pszCmdLine = NULL) const; 35 | HRESULT RegisterElevatableLocalServer(PCWSTR pszFriendlyName, UINT idLocalizeString, UINT idIconRef) const; 36 | HRESULT RegisterElevatableInProcServer(PCWSTR pszFriendlyName, UINT idLocalizeString, UINT idIconRef) const; 37 | 38 | // remove a COM object registration 39 | HRESULT UnRegisterObject() const; 40 | 41 | // this enables drag drop directly onto the .exe, useful if you have a 42 | // shortcut to the exe somewhere (or the .exe is accessable via the send to menu) 43 | /* 44 | HRESULT RegisterAppDropTarget() const; 45 | 46 | // create registry entries for drop target based static verb. 47 | // the specified clsid will be 48 | 49 | HRESULT RegisterCreateProcessVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszCmdLine, PCWSTR pszVerbDisplayName) const; 50 | HRESULT RegisterDropTargetVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const; 51 | HRESULT RegisterExecuteCommandVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const; 52 | HRESULT RegisterExplorerCommandVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const; 53 | HRESULT RegisterExplorerCommandStateHandler(PCWSTR pszProgID, PCWSTR pszVerb) const; 54 | HRESULT RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName) const; 55 | HRESULT RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName, PCWSTR pszValue) const; 56 | HRESULT RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName, DWORD dwValue) const; 57 | HRESULT RegisterVerbDefaultAndOrder(PCWSTR pszProgID, PCWSTR pszVerbOrderFirstIsDefault) const; 58 | 59 | HRESULT RegisterPlayerVerbs(PCWSTR const rgpszAssociation[], UINT countAssociation, 60 | PCWSTR pszVerb, PCWSTR pszTitle) const; 61 | 62 | HRESULT UnRegisterVerb(PCWSTR pszProgID, PCWSTR pszVerb) const; 63 | HRESULT UnRegisterVerbs(PCWSTR const rgpszAssociation[], UINT countAssociation, PCWSTR pszVerb) const; 64 | 65 | HRESULT RegisterContextMenuHandler(PCWSTR pszProgID, PCWSTR pszDescription) const; 66 | HRESULT RegisterRightDragContextMenuHandler(PCWSTR pszProgID, PCWSTR pszDescription) const; 67 | 68 | HRESULT RegisterAppShortcutInSendTo() const; 69 | 70 | HRESULT RegisterThumbnailHandler(PCWSTR pszExtension) const; 71 | HRESULT RegisterPropertyHandler(PCWSTR pszExtension) const; 72 | HRESULT UnRegisterPropertyHandler(PCWSTR pszExtension) const; 73 | 74 | HRESULT RegisterLinkHandler(PCWSTR pszProgID) const; 75 | 76 | HRESULT RegisterProgID(PCWSTR pszProgID, PCWSTR pszTypeName, UINT idIcon) const; 77 | HRESULT UnRegisterProgID(PCWSTR pszProgID, PCWSTR pszFileExtension) const; 78 | HRESULT RegisterExtensionWithProgID(PCWSTR pszFileExtension, PCWSTR pszProgID) const; 79 | HRESULT RegisterOpenWith(PCWSTR pszFileExtension, PCWSTR pszProgID) const; 80 | HRESULT RegisterNewMenuNullFile(PCWSTR pszFileExtension, PCWSTR pszProgID) const; 81 | HRESULT RegisterNewMenuData(PCWSTR pszFileExtension, PCWSTR pszProgID, PCSTR pszBase64) const; 82 | HRESULT RegisterKind(PCWSTR pszFileExtension, PCWSTR pszKindValue) const; 83 | HRESULT UnRegisterKind(PCWSTR pszFileExtension) const; 84 | HRESULT RegisterPropertyHandlerOverride(PCWSTR pszProperty) const; 85 | 86 | HRESULT RegisterHandlerSupportedProtocols(PCWSTR pszProtocol) const; 87 | 88 | HRESULT RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName) const; 89 | HRESULT RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName, PCWSTR pszValue) const; 90 | HRESULT RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName, DWORD dwValue) const; 91 | */ 92 | // this should probably be private but they are useful 93 | HRESULT RegSetKeyValuePrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValueName, PCWSTR pszValue, ...) const; 94 | HRESULT RegSetKeyValuePrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValueName, DWORD dwValue, ...) const; 95 | HRESULT RegSetKeyValuePrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValueName, const unsigned char pc[], DWORD dwSize, ...) const; 96 | HRESULT RegSetKeyValueBinaryPrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValueName, PCSTR pszBase64, ...) const; 97 | 98 | HRESULT RegDeleteKeyPrintf(HKEY hkey, PCWSTR pszKeyFormatString, ...) const; 99 | HRESULT RegDeleteKeyValuePrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValue, ...) const; 100 | 101 | PCWSTR GetCLSIDString() const { return _szCLSID; }; 102 | 103 | bool HasClassID() const { return _clsid != CLSID_NULL ? true : false; }; 104 | 105 | private: 106 | 107 | HRESULT _EnsureModule() const; 108 | //bool _IsBaseClassProgID(PCWSTR pszProgID) const; 109 | //HRESULT _EnsureBaseProgIDVerbIsNone(PCWSTR pszProgID) const; 110 | void _UpdateAssocChanged(HRESULT hr, PCWSTR pszKeyFormatString) const; 111 | 112 | CLSID _clsid; 113 | HKEY _hkeyRoot; 114 | WCHAR _szCLSID[39]; 115 | WCHAR _szModule[MAX_PATH]; 116 | bool _fAssocChanged; 117 | }; 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExecuteCommand-Pipe 2 | A simple tool to run arbitrary commands with paths (of files or directories) passed from various context menus in Windows Explorer. Paths can be passed to the commands either as arguments or through stdin/stdout (pipe). 3 | Implemented with COM (Component Object Model) technology to avoid limitations for path length or number of files. 4 | 5 | # Usage 6 | 7 | ## (If you use Windows 11 and don't want to press Shift or click "Show More Options" everytime) Revert to old context menu 8 | 9 | Run this command in cmd.exe or Win+R dialog and restart explorer.exe. 10 | 11 | ``` 12 | reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32 /f /ve" 13 | ``` 14 | 15 | ## Register as a class 16 | 17 | Simply double-click to run `ExecuteCommandXXXX.exe` with no arguments, and it properly registers itself in the registry. 18 | - Each released executable `ExecuteCommandXXXX.exe` has CLSID `{FFA07888-75BD-471A-B325-59274E73XXXX}`. 19 | - Since the CLSID must be determined in compile time and we have no method to pass arguments from outside the CLSID, we need a separate executable file for each command. 20 | - When run without admin right, it registers itself in `HKCU\Software\Classes\CLSID\{FFA07888-75BD-471A-B325-59274E73XXXX}`. 21 | - The full path of the executable is stored in the default value of `HKCU\Software\Classes\CLSID\{FFA07888-75BD-471A-B325-59274E73XXXX}\LocalServer32`. 22 | - When run with admin right, it asks if it should use `HKLM` instead of `HKCU`. 23 | - But user-specific `HKCU` will be safer than system-wide `HKLM`. 24 | - If you use `HKLM`, you should not place the executable file inside `C:\Users\username`. 25 | - You can also register it manually. 26 | - Note that `HKCR` is the result of merging `HKCU\Software\Classes` and `HKLM\Software\Classes` together. 27 | - You can edit things in `HKCR`, but you should be aware which of `HKCU` or `HKLM` they come from. 28 | - If you move the executable file, you should update the path in the registry. 29 | ## Modify the argument 30 | Once the CLSID is registered, you can append any argument to the executable path in `[HKCU or HKLM]\Software\Classes\CLSID\{FFA07888-75BD-471A-B325-59274E73XXXX}\LocalServer32`, according to your purpose (see examples below). 31 | 32 | After the modification, rename the "LocalServer32" to any other name and then return it back (this seems the easiest way to reset some cache and apply the change). 33 | ### Available Options for ExecuteCommandXXXX.exe 34 | - Basic usage: `ExecuteCommandXXXX.exe [mode] [prefix] commandline` 35 | - `[mode]` is either `a`, `ah`, `p`, or `ph`. 36 | - `a` and `ah` are "**a**rgument-mode", where stdin/stdout is not used. 37 | - Typically this mode is easier to setup, but note that Windows has 32767 character command line length limit. 38 | - `p` and `ph` are "**p**ipe-mode", where files are passed through pipe. 39 | - "\n" (LF, 0x0A) is appended to each path (including the last one). 40 | - If `ah` or `ph` is specified, the console window is **h**idden when the commandline is executed. 41 | - `[prefix]` can be any string without whitespaces. Symbols are recommended (e.g. `#` or `$$`). 42 | - Note that `[prefix]` must be specified even if it's actually not used (typically, in pipe-mode). 43 | - In `commandline`, some replacements are done: 44 | - If a `[prefix]` is followed by `f`, they will be replaced by the (whitespace-separated) list of double-quoted paths of the all files and directories passed. 45 | - If a `[prefix]` is followed by `v`, they will be replaced by the verb name (i.e. registry key name). 46 | - For directory background (`HKCR\Directory\Background\shell`), the background directory path is used instead of the passed file paths. 47 | - (for debugging) If no argument is specified, it shows some information in a messagebox and exit. 48 | 49 | ### Examples 50 | - For a single directory 51 | - Open Git Bash 52 | - `C:\path\to\ExecuteCommand4000.exe ph # cmd /c ""C:\Program Files\Git\usr\bin\cygpath" -f - | "C:\Program Files\Git\usr\bin\xargs" -d '\n' -I {} "C:\Program Files\Git\git-bash.exe" -c "cd \"{}\";exec bash""` 53 | - Open Git Bash in Windows' default terminal 54 | - `C:\path\to\ExecuteCommand4000.exe ph # cmd /c ""C:\Program Files\Git\usr\bin\cygpath" -f - | "C:\Program Files\Git\usr\bin\xargs.exe" -d '\n' -I {} cmd /c start "" "C:\Program Files\Git\usr\bin\env.exe" MSYSTEM=MINGW64 "C:\Program Files\Git\usr\bin\bash.exe" --login -i -c "cd \"{}\";exec bash""` 55 | - You need `//` in order for Git Bash's executables to pass `/` to Windows executables. You won't need it if you use Cygwin's xargs instead. 56 | - Open Cygwin bash 57 | - `C:\path\to\ExecuteCommand4000.exe ph # cmd /c ""C:\cygwin64\bin\cygpath" -f - | "C:\Program Files\Git\usr\bin\xargs" -d '\n' -I {} "C:\cygwin64\bin\mintty.exe" -e "C:\cygwin64\bin\bash.exe" --login -i -c "cd \"{}\";exec bash""` 58 | - Here you can use cygwin's xargs instead. 59 | - Open Cygwin Bash in Windows' default terminal 60 | - `C:\path\to\ExecuteCommand4000.exe ph # cmd /c ""C:\cygwin64\bin\cygpath" -f - | "C:\cygwin64\bin\xargs.exe" -d '\n' -I {} cmd /c start "" C:\cygwin64\bin\bash.exe --login -i -c "cd \"{}\";exec bash""` 61 | - Open VS Code 62 | - `C:\path\to\ExecuteCommand4000.exe a # "C:\Program Files\Microsoft VS Code\Code.exe" #f` 63 | - For multiple files or directories 64 | - pass files as a string argument, opening interactive window 65 | - `C:\path\to\ExecuteCommand4000.exe a # "C:\path\to\script.bat" #f` 66 | - Here, `script.bat` may contain interactive commands (like `pause`) 67 | - (If you use pipe-mode to do this, you need some other command to keep alive the desired program after EOF.) 68 | - `C:\path\to\ExecuteCommand4000.exe ph # "C:\cygwin64\bin\xargs.exe" -d '\n' -- cmd /c start "" cmd /c "C:\path\to\script.bat"` 69 | - pass to mpv's stdin 70 | - `C:\path\to\ExecuteCommand4000.exe p # C:\path\to\mpv\mpv.exe --player-operation-mode=pseudo-gui --playlist=-` 71 | - write paths to file 72 | - `C:\path\to\ExecuteCommand4000.exe ph # busybox sh -c "cat > $HOME/out.txt"` 73 | - run a GUI application for each file (don't run this for too many files!) (use cygwin's xargs in recent Windows 11; refer to #3) 74 | - `C:\path\to\ExecuteCommand4000.exe ph # "C:\cygwin64\bin\xargs.exe" -d '\n' -n1 -P0 "/cygdrive/c/Program Files/Windows NT/Accessories/wordpad.exe"` 75 | - Ordinary "Default" registry value may be sufficient in this case. 76 | ## Invoke the class by `DelegateExecute` 77 | If properly registered as a class, it can be invoked by writing the CLSID (including `{}`) to the `DelegateExecute` value of `command` keys for respective context menus. 78 | At least the following registry keys work. Tested in Windows 11. 79 | - `HKCR\SystemFileAssociations\.xxx\shell` or `HKCR\SystemFileAssociations\XXXXXXX\shell` (right-click) 80 | - `HKCR\*\shell` (right-click, for all files) 81 | - `HKCR\Directory\shell` (right-click, for directories) 82 | - `HKCR\Directory\Background\shell` (right-clicking blank area in directories) 83 | - We don't need this for most cases, because a single path can be easily handled by ordinary "Default" registry value. `NoWorkingDirectory` can be used to avoid errors with directories of long (>258) paths. 84 | - `HKCR\XXXXXXX\shell`, specified by `HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.xxx\UserChoice` (right-click or as default apps (double-click or return key)) 85 | - Since the values of `UserChoice` cannot be directly changed (even by administrators), you should use a dummy file to associate .xxx with and then change value in `HKCR\XXXXXXX\shell`. 86 | ### Example .reg files 87 | 88 | - for .txt 89 | ``` 90 | Windows Registry Editor Version 5.00 91 | 92 | [HKEY_CURRENT_USER\Software\Classes\SystemFileAssociations\.txt\shell\mycommand] 93 | @="mycommand_name" 94 | "Icon"="C:\\WINDOWS\\notepad.exe" 95 | 96 | [HKEY_CURRENT_USER\Software\Classes\SystemFileAssociations\.txt\shell\mycommand\command] 97 | "DelegateExecute"="{FFA07888-75BD-471A-B325-59274E734000}" 98 | 99 | ``` 100 | - for directory background 101 | ``` 102 | Windows Registry Editor Version 5.00 103 | 104 | [HKEY_CURRENT_USER\Software\Classes\Directory\Background\shell\VSCode] 105 | @="MyVSCode" 106 | "Icon"="C:\\Program Files\\Microsoft VS Code\\Code.exe" 107 | 108 | [HKEY_CURRENT_USER\Software\Classes\Directory\Background\shell\VSCode\command] 109 | "DelegateExecute"="{FFA07888-75BD-471A-B325-59274E734000}" 110 | 111 | ``` 112 | # Building 113 | Use Visual Studio or MSBuild.exe. `build.sh` generates multiple exe files with different UUIDs. 114 | # License 115 | - MIT (inherited from [the original Microsoft sample](https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/winui/shell/appshellintegration/ExecuteCommandVerb)) 116 | - public domain for my revision 117 | -------------------------------------------------------------------------------- /ShellHelpers.h: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | 8 | #pragma once 9 | 10 | #define STRICT_TYPED_ITEMIDS // in case you do IDList stuff you want this on for better type saftey 11 | #define UNICODE 1 12 | 13 | #include 14 | #include // for WM_COMMAND handling macros 15 | #include // shell stuff 16 | #include // QISearch, easy way to implement QI 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #pragma comment(lib, "shlwapi.lib") // link to this 23 | #pragma comment(lib, "comctl32.lib") // link to this 24 | #pragma comment(lib, "propsys.lib") // link to this 25 | 26 | // set up common controls v6 the easy way 27 | #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 28 | 29 | __inline HRESULT ResultFromKnownLastError() { const DWORD err = GetLastError(); return err == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32(err); } 30 | 31 | // map Win32 APIs that follow the return BOOL/set last error protocol 32 | // into HRESULT 33 | // 34 | // example: MoveFileEx() 35 | 36 | __inline HRESULT ResultFromWin32Bool(BOOL b) 37 | { 38 | return b ? S_OK : ResultFromKnownLastError(); 39 | } 40 | 41 | #if (NTDDI_VERSION >= NTDDI_VISTA) 42 | 43 | __inline HRESULT ShellExecuteItem(HWND hwnd, PCWSTR pszVerb, IShellItem *psi) 44 | { 45 | // how to activate a shell item, use ShellExecute(). 46 | PIDLIST_ABSOLUTE pidl; 47 | HRESULT hr = SHGetIDListFromObject(psi, &pidl); 48 | if (SUCCEEDED(hr)) 49 | { 50 | SHELLEXECUTEINFO ei = { sizeof(ei) }; 51 | ei.fMask = SEE_MASK_INVOKEIDLIST; 52 | ei.hwnd = hwnd; 53 | ei.nShow = SW_NORMAL; 54 | ei.lpIDList = pidl; 55 | ei.lpVerb = pszVerb; 56 | 57 | hr = ResultFromWin32Bool(ShellExecuteEx(&ei)); 58 | 59 | CoTaskMemFree(pidl); 60 | } 61 | return hr; 62 | } 63 | 64 | __inline HRESULT GetItemFromView(IFolderView2 *pfv, int iItem, REFIID riid, void **ppv) 65 | { 66 | *ppv = NULL; 67 | 68 | HRESULT hr = S_OK; 69 | 70 | if (iItem == -1) 71 | { 72 | hr = pfv->GetSelectedItem(-1, &iItem); // Returns S_FALSE if none selected 73 | } 74 | 75 | if (S_OK == hr) 76 | { 77 | hr = pfv->GetItem(iItem, riid, ppv); 78 | } 79 | else 80 | { 81 | hr = E_FAIL; 82 | } 83 | return hr; 84 | } 85 | 86 | // set the icon for your window using WM_SETICON from one of the set of stock system icons 87 | // caller must call ClearDialogIcon() to free the HICON that is created 88 | __inline void SetDialogIcon(HWND hdlg, SHSTOCKICONID siid) 89 | { 90 | SHSTOCKICONINFO sii = {sizeof(sii)}; 91 | if (SUCCEEDED(SHGetStockIconInfo(siid, SHGFI_ICON | SHGFI_SMALLICON, &sii))) 92 | { 93 | SendMessage(hdlg, WM_SETICON, ICON_SMALL, (LPARAM) sii.hIcon); 94 | } 95 | if (SUCCEEDED(SHGetStockIconInfo(siid, SHGFI_ICON | SHGFI_LARGEICON, &sii))) 96 | { 97 | SendMessage(hdlg, WM_SETICON, ICON_BIG, (LPARAM) sii.hIcon); 98 | } 99 | } 100 | #endif 101 | 102 | // free the HICON that was set using SetDialogIcon() 103 | __inline void ClearDialogIcon(HWND hdlg) 104 | { 105 | DestroyIcon((HICON)SendMessage(hdlg, WM_GETICON, ICON_SMALL, 0)); 106 | DestroyIcon((HICON)SendMessage(hdlg, WM_GETICON, ICON_BIG, 0)); 107 | } 108 | 109 | __inline HRESULT SetItemImageImageInStaticControl(HWND hwndStatic, IShellItem *psi) 110 | { 111 | HBITMAP hbmp = NULL; 112 | HRESULT hr = S_OK; 113 | if (psi) 114 | { 115 | IShellItemImageFactory *psiif; 116 | hr = psi->QueryInterface(IID_PPV_ARGS(&psiif)); 117 | if (SUCCEEDED(hr)) 118 | { 119 | RECT rc; 120 | GetWindowRect(hwndStatic, &rc); 121 | const UINT dxdy = min(rc.right - rc.left, rc.bottom - rc.top); // make it square 122 | const SIZE size = { (LONG)dxdy, (LONG)dxdy }; 123 | 124 | hr = psiif->GetImage(size, SIIGBF_RESIZETOFIT, &hbmp); 125 | psiif->Release(); 126 | } 127 | } 128 | 129 | if (SUCCEEDED(hr)) 130 | { 131 | HGDIOBJ hgdiOld = (HGDIOBJ) SendMessage(hwndStatic, STM_SETIMAGE, (WPARAM) IMAGE_BITMAP, (LPARAM) hbmp); 132 | if (hgdiOld) 133 | { 134 | DeleteObject(hgdiOld); // if there was an old one clean it up 135 | } 136 | } 137 | 138 | return hr; 139 | } 140 | 141 | 142 | __inline HRESULT SHILCloneFull(PCUIDLIST_ABSOLUTE pidl, PIDLIST_ABSOLUTE *ppidl) 143 | { 144 | *ppidl = ILCloneFull(pidl); 145 | return *ppidl ? S_OK : E_OUTOFMEMORY; 146 | } 147 | 148 | __inline HRESULT SHILClone(PCUIDLIST_RELATIVE pidl, PIDLIST_RELATIVE *ppidl) 149 | { 150 | *ppidl = ILClone(pidl); 151 | return *ppidl ? S_OK : E_OUTOFMEMORY; 152 | } 153 | 154 | __inline HRESULT SHILCombine(PCIDLIST_ABSOLUTE pidl1, PCUIDLIST_RELATIVE pidl2, PIDLIST_ABSOLUTE *ppidl) 155 | { 156 | *ppidl = ILCombine(pidl1, pidl2); 157 | return *ppidl ? S_OK : E_OUTOFMEMORY; 158 | } 159 | 160 | __inline HRESULT GetItemAt(IShellItemArray *psia, DWORD i, REFIID riid, void **ppv) 161 | { 162 | *ppv = NULL; 163 | IShellItem *psi = NULL; // avoid error C4701 164 | HRESULT hr = psia ? psia->GetItemAt(i, &psi) : E_NOINTERFACE; 165 | if (SUCCEEDED(hr)) 166 | { 167 | hr = psi->QueryInterface(riid, ppv); 168 | psi->Release(); 169 | } 170 | return hr; 171 | } 172 | 173 | #define MAP_ENTRY(x) {L#x, x} 174 | 175 | __inline HRESULT ShellAttributesToString(SFGAOF sfgaof, PWSTR *ppsz) 176 | { 177 | *ppsz = NULL; 178 | 179 | static const struct { PCWSTR pszName; SFGAOF sfgaof; } c_rgItemAttributes[] = 180 | { 181 | // note, SFGAO_HASSUBFOLDER is too expesnive to compute 182 | // and has been excluded from this list 183 | MAP_ENTRY(SFGAO_STREAM), 184 | MAP_ENTRY(SFGAO_FOLDER), 185 | MAP_ENTRY(SFGAO_FILESYSTEM), 186 | MAP_ENTRY(SFGAO_FILESYSANCESTOR), 187 | MAP_ENTRY(SFGAO_STORAGE), 188 | MAP_ENTRY(SFGAO_STORAGEANCESTOR), 189 | MAP_ENTRY(SFGAO_LINK), 190 | MAP_ENTRY(SFGAO_CANCOPY), 191 | MAP_ENTRY(SFGAO_CANMOVE), 192 | MAP_ENTRY(SFGAO_CANLINK), 193 | MAP_ENTRY(SFGAO_CANRENAME), 194 | MAP_ENTRY(SFGAO_CANDELETE), 195 | MAP_ENTRY(SFGAO_HASPROPSHEET), 196 | MAP_ENTRY(SFGAO_DROPTARGET), 197 | MAP_ENTRY(SFGAO_ENCRYPTED), 198 | MAP_ENTRY(SFGAO_ISSLOW), 199 | MAP_ENTRY(SFGAO_GHOSTED), 200 | MAP_ENTRY(SFGAO_SHARE), 201 | MAP_ENTRY(SFGAO_READONLY), 202 | MAP_ENTRY(SFGAO_HIDDEN), 203 | MAP_ENTRY(SFGAO_REMOVABLE), 204 | MAP_ENTRY(SFGAO_COMPRESSED), 205 | MAP_ENTRY(SFGAO_BROWSABLE), 206 | MAP_ENTRY(SFGAO_NONENUMERATED), 207 | MAP_ENTRY(SFGAO_NEWCONTENT), 208 | }; 209 | 210 | WCHAR sz[512] = {}; 211 | PWSTR psz = sz; 212 | size_t cch = ARRAYSIZE(sz); 213 | 214 | StringCchPrintfEx(psz, cch, &psz, &cch, 0, L"0x%08X", sfgaof); 215 | 216 | for (int i = 0; i < ARRAYSIZE(c_rgItemAttributes); i++) 217 | { 218 | if (c_rgItemAttributes[i].sfgaof & sfgaof) 219 | { 220 | StringCchPrintfEx(psz, cch, &psz, &cch, 0, L", %s", c_rgItemAttributes[i].pszName); 221 | } 222 | } 223 | return SHStrDup(sz, ppsz); 224 | } 225 | 226 | template void SafeRelease(T **ppT) 227 | { 228 | if (*ppT) 229 | { 230 | (*ppT)->Release(); 231 | *ppT = NULL; 232 | } 233 | } 234 | 235 | // assign an interface pointer, release old, capture ref to new, can be used to set to zero too 236 | 237 | template HRESULT SetInterface(T **ppT, IUnknown *punk) 238 | { 239 | SafeRelease(ppT); 240 | return punk ? punk->QueryInterface(ppT) : E_NOINTERFACE; 241 | } 242 | 243 | // remote COM methods are dispatched in the context of an exception handler that consumes 244 | // all SEH exceptions including crahses and C++ exceptions. this is undesirable as it 245 | // means programs will continue to run after such an exception has been thrown, 246 | // leaving the process in a inconsistent state. 247 | // 248 | // this applies to COM methods like IDropTarget::Drop() 249 | // 250 | // this code turns off that behavior 251 | 252 | __inline void DisableComExceptionHandling() 253 | { 254 | IGlobalOptions *pGlobalOptions; 255 | HRESULT hr = CoCreateInstance(CLSID_GlobalOptions, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pGlobalOptions)); 256 | if (SUCCEEDED(hr)) 257 | { 258 | #if (NTDDI_VERSION >= NTDDI_WIN7) 259 | hr = pGlobalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY); 260 | #else 261 | hr = pGlobalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE); 262 | #endif 263 | pGlobalOptions->Release(); 264 | } 265 | } 266 | 267 | __inline void GetWindowRectInClient(HWND hwnd, RECT *prc) 268 | { 269 | GetWindowRect(hwnd, prc); 270 | MapWindowPoints(GetDesktopWindow(), GetParent(hwnd), (POINT*)prc, 2); 271 | } 272 | 273 | // retrieve the HINSTANCE for the current DLL or EXE using this symbol that 274 | // the linker provides for every module, avoids the need for a global HINSTANCE variable 275 | // and provides access to this value for static libraries 276 | EXTERN_C IMAGE_DOS_HEADER __ImageBase; 277 | __inline HINSTANCE GetModuleHINSTANCE() { return (HINSTANCE)&__ImageBase; } 278 | -------------------------------------------------------------------------------- /ExecuteCommand.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 17.0 23 | {3FD4769C-8073-4A26-AB36-7F3C776F07BF} 24 | DynamicNavigate 25 | Win32Proj 26 | 27 | 28 | 29 | Application 30 | v143 31 | Unicode 32 | true 33 | 34 | 35 | Application 36 | v143 37 | Unicode 38 | 39 | 40 | Application 41 | v143 42 | Unicode 43 | true 44 | 45 | 46 | Application 47 | v143 48 | Unicode 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | <_ProjectFileVersion>17.0.33205.214 68 | 69 | 70 | $(SolutionDir)$(Configuration)\ 71 | $(Configuration)\ 72 | true 73 | 74 | 75 | $(SolutionDir)$(Configuration)\ 76 | $(Configuration)\ 77 | false 78 | 79 | 80 | $(SolutionDir)$(Platform)\$(Configuration)\ 81 | $(Platform)\$(Configuration)\ 82 | true 83 | 84 | 85 | $(SolutionDir)$(Platform)\$(Configuration)\ 86 | $(Platform)\$(Configuration)\ 87 | false 88 | 89 | 90 | 91 | Disabled 92 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 93 | true 94 | EnableFastChecks 95 | MultiThreadedDebugDLL 96 | 97 | Level4 98 | true 99 | EditAndContinue 100 | 101 | 102 | LIBCMT.LIB;%(IgnoreSpecificDefaultLibraries) 103 | true 104 | Windows 105 | MachineX86 106 | 107 | 108 | 109 | 110 | MaxSpeed 111 | true 112 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 113 | MultiThreadedDLL 114 | true 115 | 116 | Level4 117 | true 118 | ProgramDatabase 119 | 120 | 121 | false 122 | Windows 123 | true 124 | true 125 | MachineX86 126 | 127 | 128 | 129 | 130 | 131 | X64 132 | 133 | 134 | Disabled 135 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 136 | true 137 | EnableFastChecks 138 | MultiThreadedDebugDLL 139 | 140 | Level4 141 | true 142 | ProgramDatabase 143 | 144 | 145 | LIBCMT.LIB;%(IgnoreSpecificDefaultLibraries) 146 | true 147 | Windows 148 | MachineX64 149 | 150 | 151 | 152 | 153 | X64 154 | 155 | 156 | MaxSpeed 157 | true 158 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 159 | MultiThreadedDLL 160 | true 161 | 162 | Level4 163 | true 164 | ProgramDatabase 165 | 166 | 167 | false 168 | Windows 169 | true 170 | true 171 | MachineX64 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /ExecuteCommandVerb.cpp: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | // 8 | // This demonstrates how implement a shell verb using the ExecuteCommand method 9 | // this method is preferred for verb implementations as it provides the most flexibility, 10 | // it is simple, and supports out of process activation. 11 | // 12 | // This sample implements a standalone local server COM object but 13 | // it is expected that the verb implementation will be integrated 14 | // into existing applications. to do that have your main application object 15 | // register a class factory for itself and have that object implement IDropTarget 16 | // for the verbs of your application. Note that COM will launch your application 17 | // if it is not already running and will connect to an already running instance 18 | // of your application if it is already running. These are features of the COM 19 | // based verb implementation methods. 20 | // 21 | // It is also possible (but not recommended) to create in process implementations 22 | // of this object. To do that follow this sample but replace the local 23 | // server COM object with an in-proc server. 24 | 25 | #include "ShellHelpers.h" 26 | #include "VerbHelpers.h" 27 | #include "RegisterExtension.h" 28 | #include 29 | #include // std::nothrow 30 | #include 31 | #include 32 | #include "myuuid.h" 33 | #include 34 | 35 | // Each ExecuteCommand handler needs to have a unique COM object, run UUIDGEN.EXE to 36 | // create new CLSID values for your handler. These handlers can implement multiple 37 | // different verbs using the information provided via IInitializeCommand, for example the verb name. 38 | // your code can switch off those different verb names or the properties provided 39 | // in the property bag. 40 | 41 | WCHAR const c_szVerbDisplayName[] = L"ExecuteCommand Verb Sample"; 42 | const int MAX_PATH_CHARS = 3000;//32767 in NTFS, but that's too much 43 | 44 | class __declspec(uuid(MYUUID)) 45 | CExecuteCommandVerb : public IExecuteCommand, 46 | public IObjectWithSelection, 47 | public IInitializeCommand, 48 | public IObjectWithSite, 49 | CAppMessageLoop 50 | { 51 | public: 52 | CExecuteCommandVerb(PWSTR pszCmdLine) : _cRef(1), _psia(NULL), _punkSite(NULL) 53 | { 54 | pszCmdLine_field = pszCmdLine; 55 | } 56 | 57 | HRESULT Run(); 58 | 59 | // IUnknown 60 | IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv) 61 | { 62 | static const QITAB qit[] = 63 | { 64 | QITABENT(CExecuteCommandVerb, IExecuteCommand), // required 65 | QITABENT(CExecuteCommandVerb, IObjectWithSelection), // required 66 | QITABENT(CExecuteCommandVerb, IInitializeCommand), // optional 67 | QITABENT(CExecuteCommandVerb, IObjectWithSite), // optional 68 | { 0 }, 69 | }; 70 | return QISearch(this, qit, riid, ppv); 71 | } 72 | 73 | IFACEMETHODIMP_(ULONG) AddRef() 74 | { 75 | return InterlockedIncrement(&_cRef); 76 | } 77 | 78 | IFACEMETHODIMP_(ULONG) Release() 79 | { 80 | long cRef = InterlockedDecrement(&_cRef); 81 | if (!cRef) 82 | { 83 | delete this; 84 | } 85 | return cRef; 86 | } 87 | 88 | // IExecuteCommand 89 | IFACEMETHODIMP SetKeyState(DWORD grfKeyState) 90 | { 91 | _grfKeyState = grfKeyState; 92 | return S_OK; 93 | } 94 | 95 | IFACEMETHODIMP SetParameters(PCWSTR /* pszParameters */) 96 | { return S_OK; } 97 | 98 | IFACEMETHODIMP SetPosition(POINT pt) 99 | { 100 | _pt = pt; 101 | return S_OK; 102 | } 103 | 104 | IFACEMETHODIMP SetShowWindow(int nShow) 105 | { 106 | _nShow = nShow; 107 | return S_OK; 108 | } 109 | 110 | IFACEMETHODIMP SetNoShowUI(BOOL /* fNoShowUI */) 111 | { return S_OK; } 112 | 113 | IFACEMETHODIMP SetDirectory(PCWSTR pszDirectory) 114 | { 115 | wcscpy_s(pszName0, MAX_PATH_CHARS, pszDirectory); 116 | return S_OK; } 117 | 118 | IFACEMETHODIMP Execute(); 119 | 120 | // IObjectWithSelection 121 | IFACEMETHODIMP SetSelection(IShellItemArray *psia) 122 | { 123 | HRESULT hr = SetInterface(&_psia, psia); 124 | files_selected = SUCCEEDED(hr); 125 | return S_OK; 126 | } 127 | 128 | IFACEMETHODIMP GetSelection(REFIID riid, void **ppv) 129 | { 130 | *ppv = NULL; 131 | return _psia ? _psia->QueryInterface(riid, ppv) : E_FAIL; 132 | } 133 | 134 | // IInitializeCommand 135 | IFACEMETHODIMP Initialize(PCWSTR pszCommandName, IPropertyBag * /* ppb */) 136 | { 137 | pszCommandName_field = pszCommandName; 138 | // The verb name is in pszCommandName, this handler can varry its behavior 139 | // based on the command name (implementing different verbs) or the 140 | // data stored under that verb in the registry can be read via ppb 141 | return S_OK; 142 | } 143 | 144 | // IObjectWithSite 145 | IFACEMETHODIMP SetSite(IUnknown *punkSite) 146 | { 147 | SetInterface(&_punkSite, punkSite); 148 | return S_OK; 149 | } 150 | 151 | IFACEMETHODIMP GetSite(REFIID riid, void **ppv) 152 | { 153 | *ppv = NULL; 154 | return _punkSite ? _punkSite->QueryInterface(riid, ppv) : E_FAIL; 155 | } 156 | 157 | void OnAppCallback() 158 | { 159 | WCHAR uuid_wide[50]; 160 | size_t converted; 161 | mbstowcs_s(&converted, uuid_wide, MYUUID, sizeof(MYUUID)); 162 | DWORD count=0; 163 | if (files_selected) _psia->GetCount(&count); 164 | CComPtr psi; 165 | PWSTR pszName = pszName0; 166 | bool is_debug = pszCmdLine_field[0] == '-';//"-Embedding" 167 | if (is_debug) { 168 | if (!files_selected) {//directory mode 169 | std::wstring msg = L"Verb name: " + pszCommandName_field + L"\nDirectory: " + pszName0; 170 | MessageBox(NULL, msg.c_str(), uuid_wide, MB_OK | MB_SETFOREGROUND); 171 | } 172 | else { 173 | GetItemAt(_psia, 0, IID_PPV_ARGS(&psi));//the first item 174 | psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName); 175 | 176 | CComPtr psi2; 177 | WCHAR* pszName2 = new WCHAR[MAX_PATH_CHARS]; 178 | GetItemAt(_psia, count - 1, IID_PPV_ARGS(&psi2));//the last item 179 | psi2->GetDisplayName(SIGDN_FILESYSPATH, &pszName2); 180 | 181 | WCHAR* szMsg = new WCHAR[MAX_PATH_CHARS * 2 + 400]; 182 | std::wstring msg = L"Verb name: " + pszCommandName_field + L"\nItems: " + std::to_wstring(count) + L"\nFirst item: " + pszName + L"\nLast item: " + pszName2; 183 | MessageBox(NULL, msg.c_str(), uuid_wide, MB_OK | MB_SETFOREGROUND); 184 | delete[] pszName2; 185 | delete[] szMsg; 186 | } 187 | return; 188 | } 189 | pszCmdLine_field[wcslen(pszCmdLine_field) - 11] = 0;//ignore " -Embedding" 190 | bool hidden = pszCmdLine_field[1] == L'h'; 191 | std::wstring ws = pszCmdLine_field + (hidden ? 3 : 2);//remove options at the beginning 192 | size_t pref_pos = ws.find(L' '); 193 | if (pref_pos == std::wstring::npos) return;//fatal (no prefix) 194 | std::wstring pref = ws.substr(0, pref_pos);//the prefix, like "$$" 195 | ws.erase(0, pref_pos + 1); //remove "$$ " at the beginning 196 | 197 | auto verb_len = pszCommandName_field.size(); 198 | //create file list string 199 | std::wstring files = L""; 200 | if (!files_selected) {//directory background 201 | files = files + L"\"" + pszName0 + L"\""; 202 | } 203 | else { 204 | for (DWORD i = 0; i < count; i++) { 205 | GetItemAt(_psia, i, IID_PPV_ARGS(&psi)); 206 | psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName); 207 | files = files + L"\"" + pszName + L"\" "; 208 | } 209 | files.pop_back();//remove the trailing space 210 | } 211 | auto files_len = files.length(); 212 | 213 | //replace prefixed strings in ws 214 | std::wstring::size_type i = 0; 215 | while (1) { 216 | i = ws.find(pref, i); 217 | if (i == std::wstring::npos) break; 218 | if (ws[i + pref.size()] == L'v') { 219 | ws.replace(i, pref.size() + 1, pszCommandName_field); 220 | i += verb_len; 221 | } 222 | else if (ws[i + pref.size()] == L'f') { 223 | ws.replace(i, pref.size() + 1, files); 224 | i += files_len; 225 | } 226 | else {//skip 227 | i += pref.size(); 228 | } 229 | } 230 | WCHAR* wchar = new WCHAR[ws.size() + 1]; // +1 for the null-terminator 231 | std::copy(ws.begin(), ws.end(), wchar); 232 | wchar[ws.size()] = L'\0'; 233 | 234 | if (pszCmdLine_field[0] == 'a') {//argument mode 235 | STARTUPINFO si = { sizeof(STARTUPINFO) }; 236 | PROCESS_INFORMATION pi; 237 | si.dwFlags = STARTF_USESHOWWINDOW; 238 | si.wShowWindow = TRUE; 239 | CreateProcess(NULL, wchar, NULL, NULL, FALSE, hidden ? CREATE_NO_WINDOW : 0, NULL, NULL, &si, &pi); 240 | CloseHandle(pi.hProcess); 241 | CloseHandle(pi.hThread); 242 | return; 243 | } 244 | //pipe mode 245 | if (!files_selected) {//directory background 246 | run_cmd_pipe(wchar, hidden, [this](HANDLE stdIn) { 247 | DWORD written = 0; 248 | WideCharToMultiByte(CP_UTF8, 0, pszName0, -1, mbfile, MAX_PATH_CHARS * 4, NULL, NULL);//to UTF-8 249 | WriteFile(stdIn, mbfile, (DWORD)strlen(mbfile), &written, NULL); 250 | WriteFile(stdIn, L"\n", 1, &written, NULL);//newline 251 | }); 252 | } 253 | else { 254 | run_cmd_pipe(wchar, hidden, [count, this, &pszName, &psi](HANDLE stdIn) { 255 | DWORD written = 0; 256 | for (DWORD i = 0; i < count; i++) { 257 | GetItemAt(_psia, i, IID_PPV_ARGS(&psi)); 258 | psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName); 259 | WideCharToMultiByte(CP_UTF8, 0, pszName, -1, mbfile, MAX_PATH_CHARS * 4, NULL, NULL);//to UTF-8 260 | WriteFile(stdIn, mbfile, (DWORD)strlen(mbfile), &written, NULL); 261 | WriteFile(stdIn, L"\n", 1, &written, NULL);//newline 262 | } 263 | }); 264 | } 265 | } 266 | void run_cmd_pipe(WCHAR* cmdline, bool hidden, std::function writer_func) { 267 | 268 | //create pipe 269 | HANDLE hPipe1[2]; 270 | HANDLE hChildRead; 271 | CreatePipe(&hPipe1[0], &hPipe1[1], NULL, 0); 272 | DuplicateHandle(GetCurrentProcess(), hPipe1[0], GetCurrentProcess(), &hChildRead, 0, TRUE, DUPLICATE_SAME_ACCESS); 273 | CloseHandle(hPipe1[0]); 274 | 275 | //prepare STARTUPINFO 276 | STARTUPINFO si; 277 | ZeroMemory(&si, sizeof(si)); 278 | si.cb = sizeof(STARTUPINFO); 279 | si.dwFlags = STARTF_USESTDHANDLES; 280 | si.hStdInput = hChildRead; 281 | si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); 282 | si.hStdError = GetStdHandle(STD_ERROR_HANDLE); 283 | //if (hidden) si.wShowWindow = SW_HIDE; // hide window 284 | 285 | PROCESS_INFORMATION pi; 286 | ZeroMemory(&pi, sizeof(pi)); 287 | //ignore first 2 chars & optionally hide window 288 | CreateProcess(NULL, cmdline, NULL, NULL, TRUE, hidden ? CREATE_NO_WINDOW : 0, NULL, NULL, &si, &pi); 289 | CloseHandle(pi.hProcess); 290 | CloseHandle(pi.hThread); 291 | CloseHandle(hChildRead); 292 | 293 | //write to stdin 294 | writer_func(hPipe1[1]); 295 | CloseHandle(hPipe1[1]); 296 | } 297 | 298 | private: 299 | ~CExecuteCommandVerb() 300 | { 301 | } 302 | PWSTR pszCmdLine_field; 303 | std::wstring pszCommandName_field; 304 | long _cRef; 305 | CComPtr _psia; 306 | CComPtr _punkSite; 307 | HWND _hwnd; 308 | POINT _pt; 309 | int _nShow; 310 | DWORD _grfKeyState; 311 | 312 | CHAR mbfile[MAX_PATH_CHARS * 4]; 313 | WCHAR pszName0[MAX_PATH_CHARS]; 314 | bool files_selected = false; 315 | }; 316 | 317 | // this is called to invoke the verb but this call must not block the caller. to accomidate that 318 | // this function captures the state it needs to invoke the verb and queues a callback via 319 | // the message queue. if your application has a message queue of its own this can be accomilished 320 | // using PostMessage() or setting a timer of zero seconds 321 | 322 | IFACEMETHODIMP CExecuteCommandVerb::Execute() 323 | { 324 | // capture state from the site needed to invoke the verb here 325 | // note the HWND can be retrieved here but it should not be used for modal UI as 326 | // all shell verbs should be modeless as well as not block the caller 327 | IUnknown_GetWindow(_punkSite, &_hwnd); 328 | 329 | // queue the execution of the verb via the message pump 330 | QueueAppCallback(); 331 | 332 | return S_OK; 333 | } 334 | 335 | HRESULT CExecuteCommandVerb::Run() 336 | { 337 | CStaticClassFactory classFactory(static_cast(this)); 338 | 339 | HRESULT hr = classFactory.Register(CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE); 340 | if (SUCCEEDED(hr)) 341 | { 342 | MessageLoop(); 343 | } 344 | return S_OK; 345 | } 346 | 347 | int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR pszCmdLine, int) 348 | { 349 | 350 | BOOL runAsAdmin; 351 | {//Check if run as admin 352 | SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; 353 | PSID AdministratorsGroup; 354 | 355 | runAsAdmin = AllocateAndInitializeSid( 356 | &NtAuthority, 357 | 2, 358 | SECURITY_BUILTIN_DOMAIN_RID, 359 | DOMAIN_ALIAS_RID_ADMINS, 360 | 0, 0, 0, 0, 0, 0, 361 | &AdministratorsGroup); 362 | 363 | if (runAsAdmin == TRUE) 364 | { 365 | if (CheckTokenMembership(NULL, AdministratorsGroup, &runAsAdmin) == FALSE) runAsAdmin = FALSE; 366 | FreeSid(AdministratorsGroup); 367 | } 368 | } 369 | 370 | HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); 371 | if (SUCCEEDED(hr)) 372 | { 373 | DisableComExceptionHandling(); 374 | if (StrStrI(pszCmdLine, L"-Embedding")) 375 | { 376 | CExecuteCommandVerb* pAppDrop = new (std::nothrow) CExecuteCommandVerb(pszCmdLine); 377 | if (pAppDrop) 378 | { 379 | pAppDrop->Run(); 380 | pAppDrop->Release(); 381 | } 382 | } 383 | else 384 | { 385 | CRegisterExtension re(__uuidof(CExecuteCommandVerb)); 386 | if (runAsAdmin && IDYES == MessageBoxA(NULL, "Run as administrator. Install for all users (in HKEY_LOCAL_MACHINE) ?", "ExecuteCommand-Pipe", MB_YESNO)) { 387 | re.SetInstallScope(HKEY_LOCAL_MACHINE); 388 | } 389 | hr = re.RegisterAppAsLocalServer(c_szVerbDisplayName); 390 | MessageBoxA(NULL, 391 | "Installed ExecuteCommand Verb Sample as UUID:{" MYUUID "}", 392 | "ExecuteCommand-Pipe", MB_OK); 393 | } 394 | 395 | CoUninitialize(); 396 | } 397 | 398 | return 0; 399 | } 400 | -------------------------------------------------------------------------------- /RegisterExtension.cpp: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | 8 | #include "RegisterExtension.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #pragma comment(lib, "crypt32.lib") 15 | #pragma comment(lib, "shlwapi.lib") // link to this 16 | 17 | __inline HRESULT ResultFromKnownLastError() { const DWORD err = GetLastError(); return err == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32(err); } 18 | 19 | // retrieve the HINSTANCE for the current DLL or EXE using this symbol that 20 | // the linker provides for every module, avoids the need for a global HINSTANCE variable 21 | // and provides access to this value for static libraries 22 | EXTERN_C IMAGE_DOS_HEADER __ImageBase; 23 | __inline HINSTANCE GetModuleHINSTANCE() { return (HINSTANCE)&__ImageBase; } 24 | 25 | CRegisterExtension::CRegisterExtension(REFCLSID clsid /* = CLSID_NULL */, HKEY hkeyRoot /* = HKEY_CURRENT_USER */) : _hkeyRoot(hkeyRoot), _fAssocChanged(false) 26 | { 27 | SetHandlerCLSID(clsid); 28 | GetModuleFileName(GetModuleHINSTANCE(), _szModule, ARRAYSIZE(_szModule)); 29 | } 30 | 31 | CRegisterExtension::~CRegisterExtension() 32 | { 33 | if (_fAssocChanged) 34 | { 35 | // inform Explorer, et al that file association data has changed 36 | SHChangeNotify(SHCNE_ASSOCCHANGED, 0, 0, 0); 37 | } 38 | } 39 | 40 | void CRegisterExtension::SetHandlerCLSID(REFCLSID clsid) 41 | { 42 | _clsid = clsid; 43 | StringFromGUID2(_clsid, _szCLSID, ARRAYSIZE(_szCLSID)); 44 | } 45 | 46 | void CRegisterExtension::SetInstallScope(HKEY hkeyRoot) 47 | { 48 | // must be HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE 49 | _hkeyRoot = hkeyRoot; 50 | } 51 | 52 | HRESULT CRegisterExtension::SetModule(PCWSTR pszModule) 53 | { 54 | return StringCchCopy(_szModule, ARRAYSIZE(_szModule), pszModule); 55 | } 56 | 57 | HRESULT CRegisterExtension::SetModule(HINSTANCE hinst) 58 | { 59 | return GetModuleFileName(hinst, _szModule, ARRAYSIZE(_szModule)) ? S_OK : E_FAIL; 60 | } 61 | 62 | HRESULT CRegisterExtension::_EnsureModule() const 63 | { 64 | return _szModule[0] ? S_OK : E_FAIL; 65 | } 66 | 67 | // this sample registers its objects in the per user registry to avoid having 68 | // to elevate 69 | 70 | // register the COM local server for the current running module 71 | // this is for self registering applications 72 | 73 | HRESULT CRegisterExtension::RegisterAppAsLocalServer(PCWSTR pszFriendlyName, PCWSTR pszCmdLine) const 74 | { 75 | HRESULT hr = _EnsureModule(); 76 | if (SUCCEEDED(hr)) 77 | { 78 | WCHAR szCmdLine[MAX_PATH + 20]; 79 | if (pszCmdLine) 80 | { 81 | StringCchPrintf(szCmdLine, ARRAYSIZE(szCmdLine), L"%s %s", _szModule, pszCmdLine); 82 | } 83 | else 84 | { 85 | StringCchCopy(szCmdLine, ARRAYSIZE(szCmdLine), _szModule); 86 | } 87 | 88 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s\\LocalServer32", L"", szCmdLine, _szCLSID); 89 | if (SUCCEEDED(hr)) 90 | { 91 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s", L"AppId", _szCLSID, _szCLSID); 92 | if (SUCCEEDED(hr)) 93 | { 94 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s", L"", pszFriendlyName, _szCLSID); 95 | if (SUCCEEDED(hr)) 96 | { 97 | // hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\AppID\\%s", L"RunAs", L"Interactive User", _szCLSID); 98 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\AppID\\%s", L"", pszFriendlyName, _szCLSID); 99 | } 100 | } 101 | } 102 | } 103 | return hr; 104 | } 105 | 106 | HRESULT CRegisterExtension::RegisterElevatableLocalServer(PCWSTR pszFriendlyName, UINT idLocalizeString, UINT idIconRef) const 107 | { 108 | HRESULT hr = _EnsureModule(); 109 | if (SUCCEEDED(hr)) 110 | { 111 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s", L"", pszFriendlyName, _szCLSID); 112 | if (SUCCEEDED(hr)) 113 | { 114 | WCHAR szRes[MAX_PATH + 20]; 115 | StringCchPrintf(szRes, ARRAYSIZE(szRes), L"@%s,-%d", _szModule, idLocalizeString); 116 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s", L"LocalizedString", szRes, _szCLSID); 117 | if (SUCCEEDED(hr)) 118 | { 119 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s\\LocalServer32", L"", _szModule, _szCLSID); 120 | if (SUCCEEDED(hr)) 121 | { 122 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s\\Elevation", L"Enabled", 1, _szCLSID); 123 | if (SUCCEEDED(hr) && idIconRef) 124 | { 125 | StringCchPrintf(szRes, ARRAYSIZE(szRes), L"@%s,-%d", _szModule, idIconRef); 126 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s\\Elevation", L"IconReference", szRes, _szCLSID); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | return hr; 133 | } 134 | 135 | HRESULT CRegisterExtension::RegisterElevatableInProcServer(PCWSTR pszFriendlyName, UINT idLocalizeString, UINT idIconRef) const 136 | { 137 | HRESULT hr = _EnsureModule(); 138 | if (SUCCEEDED(hr)) 139 | { 140 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\AppId\\%s", L"", pszFriendlyName, _szCLSID); 141 | if (SUCCEEDED(hr)) 142 | { 143 | const unsigned char c_rgAccessPermission[] = 144 | {0x01,0x00,0x04,0x80,0x60,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14, 145 | 0x00,0x00,0x00,0x02,0x00,0x4c,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x03,0x00, 146 | 0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x12,0x00,0x00,0x00,0x00,0x00,0x14, 147 | 0x00,0x07,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x0a,0x00,0x00,0x00, 148 | 0x00,0x00,0x14,0x00,0x03,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x04, 149 | 0x00,0x00,0x00,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0x01,0x02,0x00,0x00,0x00,0x00, 150 | 0x00,0x05,0x20,0x00,0x00,0x00,0x20,0x02,0x00,0x00,0x01,0x02,0x00,0x00,0x00,0x00,0x00, 151 | 0x05,0x20,0x00,0x00,0x00,0x20,0x02,0x00,0x00}; 152 | // shell32\shell32.man uses this for InProcServer32 cases 153 | // 010004805800000068000000000000001400000002004400030000000000140003000000010100000000000504000000000014000700000001010000000000050a00000000001400030000000101000000000005120000000102000000000005200000002002000001020000000000052000000020020000 154 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\AppId\\%s", L"AccessPermission", c_rgAccessPermission, sizeof(c_rgAccessPermission), _szCLSID); 155 | 156 | const unsigned char c_rgLaunchPermission[] = 157 | {0x01,0x00,0x04,0x80,0x78,0x00,0x00,0x00,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14, 158 | 0x00,0x00,0x00,0x02,0x00,0x64,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x1f,0x00, 159 | 0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x12,0x00,0x00,0x00,0x00,0x00,0x18, 160 | 0x00,0x1f,0x00,0x00,0x00,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x05,0x20,0x00,0x00,0x00, 161 | 0x20,0x02,0x00,0x00,0x00,0x00,0x14,0x00,0x1f,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00, 162 | 0x00,0x00,0x05,0x04,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x0b,0x00,0x00,0x00,0x01,0x01, 163 | 0x00,0x00,0x00,0x00,0x00,0x05,0x12,0x00,0x00,0x00,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd, 164 | 0xcd,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x05,0x20,0x00,0x00,0x00,0x20,0x02,0x00,0x00, 165 | 0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x05,0x20,0x00,0x00,0x00,0x20,0x02,0x00,0x00}; 166 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\AppId\\%s", L"LaunchPermission", c_rgLaunchPermission, sizeof(c_rgLaunchPermission), _szCLSID); 167 | 168 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s", L"", pszFriendlyName, _szCLSID); 169 | if (SUCCEEDED(hr)) 170 | { 171 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s", L"AppId", _szCLSID, _szCLSID); 172 | if (SUCCEEDED(hr)) 173 | { 174 | WCHAR szRes[MAX_PATH + 20]; 175 | StringCchPrintf(szRes, ARRAYSIZE(szRes), L"@%s,-%d", _szModule, idLocalizeString); 176 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s", L"LocalizedString", szRes, _szCLSID); 177 | if (SUCCEEDED(hr)) 178 | { 179 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s\\InProcServer32", L"", _szModule, _szCLSID); 180 | if (SUCCEEDED(hr)) 181 | { 182 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s\\Elevation", L"Enabled", 1, _szCLSID); 183 | if (SUCCEEDED(hr) && idIconRef) 184 | { 185 | StringCchPrintf(szRes, ARRAYSIZE(szRes), L"@%s,-%d", _szModule, idIconRef); 186 | hr = RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Classes\\CLSID\\%s\\Elevation", L"IconReference", szRes, _szCLSID); 187 | } 188 | } 189 | } 190 | } 191 | } 192 | } 193 | } 194 | return hr; 195 | } 196 | 197 | HRESULT CRegisterExtension::RegisterInProcServer(PCWSTR pszFriendlyName, PCWSTR pszThreadingModel) const 198 | { 199 | HRESULT hr = _EnsureModule(); 200 | if (SUCCEEDED(hr)) 201 | { 202 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s", L"", pszFriendlyName, _szCLSID); 203 | if (SUCCEEDED(hr)) 204 | { 205 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s\\InProcServer32", L"", _szModule, _szCLSID); 206 | if (SUCCEEDED(hr)) 207 | { 208 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s\\InProcServer32", L"ThreadingModel", pszThreadingModel, _szCLSID); 209 | } 210 | } 211 | } 212 | return hr; 213 | } 214 | 215 | // use for 216 | // ManualSafeSave = REG_DWORD:<1> 217 | // EnableShareDenyNone = REG_DWORD:<1> 218 | // EnableShareDenyWrite = REG_DWORD:<1> 219 | 220 | HRESULT CRegisterExtension::RegisterInProcServerAttribute(PCWSTR pszAttribute, DWORD dwValue) const 221 | { 222 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s", pszAttribute, dwValue, _szCLSID); 223 | } 224 | 225 | HRESULT CRegisterExtension::UnRegisterObject() const 226 | { 227 | // might have an AppID value, try that 228 | HRESULT hr = RegDeleteKeyPrintf(_hkeyRoot, L"Software\\Classes\\AppID\\%s", _szCLSID); 229 | if (SUCCEEDED(hr)) 230 | { 231 | hr = RegDeleteKeyPrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s", _szCLSID); 232 | } 233 | return hr; 234 | } 235 | /* 236 | // 237 | // pszProtocol values: 238 | // "*" - all 239 | // "http" 240 | // "ftp" 241 | // "shellstream" - NYI in Win7 242 | 243 | HRESULT CRegisterExtension::RegisterHandlerSupportedProtocols(PCWSTR pszProtocol) const 244 | { 245 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s\\SupportedProtocols", pszProtocol, L"", _szCLSID); 246 | } 247 | 248 | // this enables drag drop directly onto the .exe, useful if you have a 249 | // shortcut to the exe somewhere (or the .exe is accessable via the send to menu) 250 | 251 | HRESULT CRegisterExtension::RegisterAppDropTarget() const 252 | { 253 | HRESULT hr = _EnsureModule(); 254 | if (SUCCEEDED(hr)) 255 | { 256 | // Windows7 supports per user App Paths, downlevel requires HKLM 257 | hr = RegSetKeyValuePrintf(_hkeyRoot, 258 | L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s", 259 | L"DropTarget", _szCLSID, PathFindFileName(_szModule)); 260 | } 261 | return hr; 262 | } 263 | 264 | // work around the missing "NeverDefault" feature for verbs on downlevel platforms 265 | // these ProgID values should need special treatment to keep the verbs registered there 266 | // from becoming default 267 | 268 | bool CRegisterExtension::_IsBaseClassProgID(PCWSTR pszProgID) const 269 | { 270 | return !StrCmpIC(pszProgID, L"AllFileSystemObjects") || 271 | !StrCmpIC(pszProgID, L"Directory") || 272 | !StrCmpIC(pszProgID, L"*") || 273 | StrStrI(pszProgID, L"SystemFileAssociations\\Directory."); // SystemFileAssociations\Directory.* values 274 | } 275 | 276 | HRESULT CRegisterExtension::_EnsureBaseProgIDVerbIsNone(PCWSTR pszProgID) const 277 | { 278 | // putting the value of "none" that does not match any of the verbs under this key 279 | // avoids those verbs from becoming the default. 280 | return _IsBaseClassProgID(pszProgID) ? 281 | RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell", L"", L"none", pszProgID) : 282 | S_OK; 283 | } 284 | 285 | HRESULT CRegisterExtension::RegisterCreateProcessVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszCmdLine, PCWSTR pszVerbDisplayName) const 286 | { 287 | UnRegisterVerb(pszProgID, pszVerb); // make sure no existing registration exists, ignore failure 288 | 289 | HRESULT hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\shell\\%s\\command", L"", pszCmdLine, pszProgID, pszVerb); 290 | if (SUCCEEDED(hr)) 291 | { 292 | hr = _EnsureBaseProgIDVerbIsNone(pszProgID); 293 | 294 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\shell\\%s", L"", pszVerbDisplayName, pszProgID, pszVerb); 295 | } 296 | return hr; 297 | } 298 | 299 | // create registry entries for drop target based static verb. 300 | // the specified clsid will be 301 | 302 | HRESULT CRegisterExtension::RegisterDropTargetVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const 303 | { 304 | UnRegisterVerb(pszProgID, pszVerb); // make sure no existing registration exists, ignore failure 305 | 306 | HRESULT hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell\\%s\\DropTarget", 307 | L"CLSID", _szCLSID, pszProgID, pszVerb); 308 | if (SUCCEEDED(hr)) 309 | { 310 | hr = _EnsureBaseProgIDVerbIsNone(pszProgID); 311 | 312 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell\\%s", 313 | L"", pszVerbDisplayName, pszProgID, pszVerb); 314 | } 315 | return hr; 316 | } 317 | 318 | HRESULT CRegisterExtension::RegisterExecuteCommandVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const 319 | { 320 | UnRegisterVerb(pszProgID, pszVerb); // make sure no existing registration exists, ignore failure 321 | 322 | HRESULT hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell\\%s\\command", 323 | L"DelegateExecute", _szCLSID, pszProgID, pszVerb); 324 | if (SUCCEEDED(hr)) 325 | { 326 | hr = _EnsureBaseProgIDVerbIsNone(pszProgID); 327 | 328 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell\\%s", 329 | L"", pszVerbDisplayName, pszProgID, pszVerb); 330 | } 331 | return hr; 332 | } 333 | 334 | // must be an inproc handler registered here 335 | 336 | HRESULT CRegisterExtension::RegisterExplorerCommandVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const 337 | { 338 | UnRegisterVerb(pszProgID, pszVerb); // make sure no existing registration exists, ignore failure 339 | 340 | HRESULT hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell\\%s", 341 | L"ExplorerCommandHandler", _szCLSID, pszProgID, pszVerb); 342 | if (SUCCEEDED(hr)) 343 | { 344 | hr = _EnsureBaseProgIDVerbIsNone(pszProgID); 345 | 346 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell\\%s", 347 | L"", pszVerbDisplayName, pszProgID, pszVerb); 348 | } 349 | return hr; 350 | } 351 | 352 | HRESULT CRegisterExtension::RegisterExplorerCommandStateHandler(PCWSTR pszProgID, PCWSTR pszVerb) const 353 | { 354 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell\\%s", 355 | L"CommandStateHandler", _szCLSID, pszProgID, pszVerb); 356 | } 357 | 358 | HRESULT CRegisterExtension::UnRegisterVerb(PCWSTR pszProgID, PCWSTR pszVerb) const 359 | { 360 | return RegDeleteKeyPrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell\\%s", pszProgID, pszVerb); 361 | } 362 | 363 | HRESULT CRegisterExtension::UnRegisterVerbs(PCWSTR const rgpszAssociation[], UINT countAssociation, PCWSTR pszVerb) const 364 | { 365 | HRESULT hr = S_OK; 366 | for (UINT i = 0; SUCCEEDED(hr) && (i < countAssociation); i++) 367 | { 368 | hr = UnRegisterVerb(rgpszAssociation[i], pszVerb); 369 | } 370 | 371 | if (SUCCEEDED(hr) && HasClassID()) 372 | { 373 | hr = UnRegisterObject(); 374 | } 375 | return hr; 376 | } 377 | 378 | HRESULT CRegisterExtension::RegisterThumbnailHandler(PCWSTR pszExtension) const 379 | { 380 | // IThumbnailHandler 381 | // HKEY_CLASSES_ROOT\.wma\ShellEx\{e357fccd-a995-4576-b01f-234630154e96}={9DBD2C50-62AD-11D0-B806-00C04FD706EC} 382 | 383 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", 384 | L"", _szCLSID, pszExtension); 385 | } 386 | 387 | // in process context menu handler for right drag context menu 388 | // need to create new method that allows out of proc handling of this 389 | 390 | // pszProgID "Folder" or "Directory" 391 | HRESULT CRegisterExtension::RegisterRightDragContextMenuHandler(PCWSTR pszProgID, PCWSTR pszDescription) const 392 | { 393 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\shellex\\DragDropHandlers\\%s", 394 | L"", pszDescription, pszProgID, _szCLSID); 395 | } 396 | 397 | // in process context menu handler 398 | 399 | HRESULT CRegisterExtension::RegisterContextMenuHandler(PCWSTR pszProgID, PCWSTR pszDescription) const 400 | { 401 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\shellex\\ContextMenuHandlers\\%s", 402 | L"", pszDescription, pszProgID, _szCLSID); 403 | } 404 | 405 | HRESULT CRegisterExtension::RegisterPropertyHandler(PCWSTR pszExtension) const 406 | { 407 | // IPropertyHandler 408 | // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PropertySystem\PropertyHandlers\.docx={993BE281-6695-4BA5-8A2A-7AACBFAAB69E} 409 | 410 | return RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers\\%s", 411 | L"", _szCLSID, pszExtension); 412 | } 413 | 414 | HRESULT CRegisterExtension::UnRegisterPropertyHandler(PCWSTR pszExtension) const 415 | { 416 | return RegDeleteKeyPrintf(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers\\%s", pszExtension); 417 | } 418 | 419 | // IResolveShellLink handler, used for custom link resolution behavior 420 | HRESULT CRegisterExtension::RegisterLinkHandler(PCWSTR pszProgID) const 421 | { 422 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\ShellEx\\LinkHandler", L"", _szCLSID, pszProgID); 423 | } 424 | 425 | // HKCR\ = 426 | // DefaultIcon= 427 | // =, 428 | 429 | HRESULT CRegisterExtension::RegisterProgID(PCWSTR pszProgID, PCWSTR pszTypeName, UINT idIcon) const 430 | { 431 | HRESULT hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s", L"", pszTypeName, pszProgID); 432 | if (SUCCEEDED(hr)) 433 | { 434 | if (idIcon) 435 | { 436 | WCHAR szIconRef[MAX_PATH]; 437 | StringCchPrintf(szIconRef, ARRAYSIZE(szIconRef), L"\"%s\",-%d", _szModule, idIcon); 438 | // HKCR\\DefaultIcon 439 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\DefaultIcon", L"", szIconRef, pszProgID); 440 | } 441 | } 442 | return hr; 443 | } 444 | 445 | HRESULT CRegisterExtension::UnRegisterProgID(PCWSTR pszProgID, PCWSTR pszFileExtension) const 446 | { 447 | HRESULT hr = RegDeleteKeyPrintf(_hkeyRoot, L"Software\\Classes\\%s", pszProgID); 448 | if (SUCCEEDED(hr) && pszFileExtension) 449 | { 450 | hr = RegDeleteKeyPrintf(_hkeyRoot, L"Software\\Classes\\%s\\%s", pszFileExtension, pszProgID); 451 | } 452 | return hr; 453 | } 454 | 455 | // value names that do not require a value 456 | // HKCR\ 457 | // NoOpen - display the "No Open" dialog for this file to disable double click 458 | // IsShortcut - report SFGAO_LINK for this item type, should have a IShellLink handler 459 | // NeverShowExt - never show the file extension 460 | // AlwaysShowExt - always show the file extension 461 | // NoPreviousVersions - don't display the "Previous Versions" verb for this file type 462 | 463 | HRESULT CRegisterExtension::RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName) const 464 | { 465 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s", pszValueName, L"", pszProgID); 466 | } 467 | 468 | // value names that require a string value 469 | // HKCR\ 470 | // NoOpen - display the "No Open" dialog for this file to disable double click, display this message 471 | // FriendlyTypeName - localized resource 472 | // ConflictPrompt 473 | // FullDetails 474 | // InfoTip 475 | // QuickTip 476 | // PreviewDetails 477 | // PreviewTitle 478 | // TileInfo 479 | // ExtendedTileInfo 480 | // SetDefaultsFor - right click.new will populate the file with these properties, example: "prop:System.Author;System.Document.DateCreated" 481 | 482 | HRESULT CRegisterExtension::RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName, PCWSTR pszValue) const 483 | { 484 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s", pszValueName, pszValue, pszProgID); 485 | } 486 | 487 | // value names that require a DWORD value 488 | // HKCR\ 489 | // EditFlags 490 | // ThumbnailCutoff 491 | 492 | HRESULT CRegisterExtension::RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName, DWORD dwValue) const 493 | { 494 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s", pszValueName, dwValue, pszProgID); 495 | } 496 | 497 | // NeverDefault 498 | // LegacyDisable 499 | // Extended 500 | // OnlyInBrowserWindow 501 | // ProgrammaticAccessOnly 502 | // SeparatorBefore 503 | // SeparatorAfter 504 | // CheckSupportedTypes, used SupportedTypes that is a file type filter registered under AppPaths (I think) 505 | HRESULT CRegisterExtension::RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName) const 506 | { 507 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\shell\\%s", pszValueName, L"", pszProgID, pszVerb); 508 | } 509 | 510 | // MUIVerb=@dll,-resid 511 | // MultiSelectModel=Single|Player|Document 512 | // Position=Bottom|Top 513 | // DefaultAppliesTo=System.ItemName:"foo" 514 | // HasLUAShield=System.ItemName:"bar" 515 | // AppliesTo=System.ItemName:"foo" 516 | HRESULT CRegisterExtension::RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName, PCWSTR pszValue) const 517 | { 518 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\shell\\%s", pszValueName, pszValue, pszProgID, pszVerb); 519 | } 520 | 521 | // BrowserFlags 522 | // ExplorerFlags 523 | // AttributeMask 524 | // AttributeValue 525 | // ImpliedSelectionModel 526 | // SuppressionPolicy 527 | HRESULT CRegisterExtension::RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName, DWORD dwValue) const 528 | { 529 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\shell\\%s", pszValueName, dwValue, pszProgID, pszVerb); 530 | } 531 | 532 | // "open explorer" is an example 533 | HRESULT CRegisterExtension::RegisterVerbDefaultAndOrder(PCWSTR pszProgID, PCWSTR pszVerbOrderFirstIsDefault) const 534 | { 535 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\Shell", L"", pszVerbOrderFirstIsDefault, pszProgID); 536 | } 537 | 538 | // register a verb on an array of ProgIDs 539 | 540 | HRESULT CRegisterExtension::RegisterPlayerVerbs(PCWSTR const rgpszAssociation[], UINT countAssociation, 541 | PCWSTR pszVerb, PCWSTR pszTitle) const 542 | { 543 | HRESULT hr = RegisterAppAsLocalServer(pszTitle, NULL); 544 | if (SUCCEEDED(hr)) 545 | { 546 | // enable this handler to work with OpenSearch results, avoiding the downlaod 547 | // and open behavior by indicating that we can accept all URL forms 548 | hr = RegisterHandlerSupportedProtocols(L"*"); 549 | 550 | for (UINT i = 0; SUCCEEDED(hr) && (i < countAssociation); i++) 551 | { 552 | hr = RegisterExecuteCommandVerb(rgpszAssociation[i], pszVerb, pszTitle); 553 | if (SUCCEEDED(hr)) 554 | { 555 | hr = RegisterVerbAttribute(rgpszAssociation[i], pszVerb, L"NeverDefault"); 556 | if (SUCCEEDED(hr)) 557 | { 558 | hr = RegisterVerbAttribute(rgpszAssociation[i], pszVerb, L"MultiSelectModel", L"Player"); 559 | } 560 | } 561 | } 562 | } 563 | return hr; 564 | } 565 | 566 | // this is where the file assocation is being taken over 567 | 568 | HRESULT CRegisterExtension::RegisterExtensionWithProgID(PCWSTR pszFileExtension, PCWSTR pszProgID) const 569 | { 570 | // HKCR\<.ext>= 571 | // "Content Type" 572 | // "PerceivedType" 573 | 574 | // TODO: to be polite if there is an existing mapping of extension to ProgID make sure it is 575 | // added to the OpenWith list so that users can get back to the old app using OpenWith 576 | // TODO: verify that HKLM/HKCU settings do not already exist as if they do they will 577 | // get in the way of the setting being made here 578 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s", L"", pszProgID, pszFileExtension); 579 | } 580 | 581 | // adds the ProgID to a file extension assuming that this ProgID will have 582 | // the "open" verb under it that will be used in Open With 583 | 584 | HRESULT CRegisterExtension::RegisterOpenWith(PCWSTR pszFileExtension, PCWSTR pszProgID) const 585 | { 586 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\OpenWithProgIds", pszProgID, L"", pszFileExtension); 587 | } 588 | 589 | HRESULT CRegisterExtension::RegisterNewMenuNullFile(PCWSTR pszFileExtension, PCWSTR pszProgID) const 590 | { 591 | // there are 2 forms of this 592 | // HKCR\<.ext>\ShellNew 593 | // HKCR\<.ext>\ShellNew\ - only 594 | // ItemName 595 | // NullFile 596 | // Data - REG_BINARY: 597 | // File 598 | // command 599 | // iconpath 600 | 601 | // another way that this works 602 | // HKEY_CLASSES_ROOT\.doc\Word.Document.8\ShellNew 603 | HRESULT hr; 604 | if (pszProgID) 605 | { 606 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\%s\\ShellNew", L"NullFile", L"", pszFileExtension, pszProgID); 607 | } 608 | else 609 | { 610 | hr = RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\%s\\ShellNew", L"NullFile", L"", pszFileExtension); 611 | } 612 | return hr; 613 | } 614 | 615 | HRESULT CRegisterExtension::RegisterNewMenuData(PCWSTR pszFileExtension, PCWSTR pszProgID, PCSTR pszBase64) const 616 | { 617 | HRESULT hr; 618 | if (pszProgID) 619 | { 620 | hr = RegSetKeyValueBinaryPrintf(_hkeyRoot, L"Software\\Classes\\%s\\%s\\ShellNew", L"Data", pszBase64, pszFileExtension, pszProgID); 621 | } 622 | else 623 | { 624 | hr = RegSetKeyValueBinaryPrintf(_hkeyRoot, L"Software\\Classes\\%s\\ShellNew", L"Data", pszBase64, pszFileExtension); 625 | } 626 | return hr; 627 | } 628 | 629 | // define the kind of a file extension. this is a multi-value property, see 630 | // HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\KindMap 631 | HRESULT CRegisterExtension::RegisterKind(PCWSTR pszFileExtension, PCWSTR pszKindValue) const 632 | { 633 | return RegSetKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\KindMap", pszFileExtension, pszKindValue); 634 | } 635 | 636 | HRESULT CRegisterExtension::UnRegisterKind(PCWSTR pszFileExtension) const 637 | { 638 | return RegDeleteKeyValuePrintf(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\KindMap", pszFileExtension); 639 | } 640 | 641 | // when indexing it is possible to override some of the file system property values, that includes the following 642 | // use this registration helper to set the override flag for each 643 | // 644 | // System.ItemNameDisplay 645 | // System.SFGAOFlags 646 | // System.Kind 647 | // System.FileName 648 | // System.ItemPathDisplay 649 | // System.ItemPathDisplayNarrow 650 | // System.ItemFolderNameDisplay 651 | // System.ItemFolderPathDisplay 652 | // System.ItemFolderPathDisplayNarrow 653 | 654 | HRESULT CRegisterExtension::RegisterPropertyHandlerOverride(PCWSTR pszProperty) const 655 | { 656 | return RegSetKeyValuePrintf(_hkeyRoot, L"Software\\Classes\\CLSID\\%s\\OverrideFileSystemProperties", pszProperty, 1, _szCLSID); 657 | } 658 | 659 | HRESULT CRegisterExtension::RegisterAppShortcutInSendTo() const 660 | { 661 | WCHAR szPath[MAX_PATH]; 662 | HRESULT hr = GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)) ? S_OK : ResultFromKnownLastError(); 663 | if (SUCCEEDED(hr)) 664 | { 665 | // Set the shortcut target 666 | IShellLink *psl; 667 | hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl)); 668 | if (SUCCEEDED(hr)) 669 | { 670 | hr = psl->SetPath(szPath); 671 | if (SUCCEEDED(hr)) 672 | { 673 | WCHAR szName[MAX_PATH]; 674 | StringCchCopy(szName, ARRAYSIZE(szName), PathFindFileName(szPath)); 675 | PathRenameExtension(szName, L".lnk"); 676 | 677 | hr = SHGetFolderPath(NULL, CSIDL_SENDTO, NULL, 0, szPath); 678 | if (SUCCEEDED(hr)) 679 | { 680 | hr = PathAppend(szPath, szName) ? S_OK : E_FAIL; 681 | if (SUCCEEDED(hr)) 682 | { 683 | IPersistFile *ppf; 684 | hr = psl->QueryInterface(IID_PPV_ARGS(&ppf)); 685 | if (SUCCEEDED(hr)) 686 | { 687 | hr = ppf->Save(szPath, TRUE); 688 | ppf->Release(); 689 | } 690 | } 691 | } 692 | } 693 | psl->Release(); 694 | } 695 | } 696 | return hr; 697 | } 698 | */ 699 | HRESULT CRegisterExtension::RegSetKeyValuePrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValueName, PCWSTR pszValue, ...) const 700 | { 701 | va_list argList; 702 | va_start(argList, pszValue); 703 | 704 | WCHAR szKeyName[512]; 705 | HRESULT hr = StringCchVPrintf(szKeyName, ARRAYSIZE(szKeyName), pszKeyFormatString, argList); 706 | if (SUCCEEDED(hr)) 707 | { 708 | hr = HRESULT_FROM_WIN32(RegSetKeyValueW(hkey, szKeyName, pszValueName, REG_SZ, pszValue, 709 | lstrlen(pszValue) * sizeof(*pszValue))); 710 | } 711 | 712 | va_end(argList); 713 | 714 | _UpdateAssocChanged(hr, pszKeyFormatString); 715 | return hr; 716 | } 717 | 718 | HRESULT CRegisterExtension::RegSetKeyValuePrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValueName, DWORD dwValue, ...) const 719 | { 720 | va_list argList; 721 | va_start(argList, dwValue); 722 | 723 | WCHAR szKeyName[512]; 724 | HRESULT hr = StringCchVPrintf(szKeyName, ARRAYSIZE(szKeyName), pszKeyFormatString, argList); 725 | if (SUCCEEDED(hr)) 726 | { 727 | hr = HRESULT_FROM_WIN32(RegSetKeyValueW(hkey, szKeyName, pszValueName, REG_DWORD, &dwValue, sizeof(dwValue))); 728 | } 729 | 730 | va_end(argList); 731 | 732 | _UpdateAssocChanged(hr, pszKeyFormatString); 733 | return hr; 734 | } 735 | 736 | HRESULT CRegisterExtension::RegSetKeyValuePrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValueName, const unsigned char pc[], DWORD dwSize, ...) const 737 | { 738 | va_list argList; 739 | va_start(argList, pc); 740 | 741 | WCHAR szKeyName[512]; 742 | HRESULT hr = StringCchVPrintf(szKeyName, ARRAYSIZE(szKeyName), pszKeyFormatString, argList); 743 | if (SUCCEEDED(hr)) 744 | { 745 | hr = HRESULT_FROM_WIN32(RegSetKeyValueW(hkey, szKeyName, pszValueName, REG_BINARY, pc, dwSize)); 746 | } 747 | 748 | va_end(argList); 749 | 750 | _UpdateAssocChanged(hr, pszKeyFormatString); 751 | return hr; 752 | } 753 | 754 | HRESULT CRegisterExtension::RegSetKeyValueBinaryPrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValueName, PCSTR pszBase64, ...) const 755 | { 756 | va_list argList; 757 | va_start(argList, pszBase64); 758 | 759 | WCHAR szKeyName[512]; 760 | HRESULT hr = StringCchVPrintf(szKeyName, ARRAYSIZE(szKeyName), pszKeyFormatString, argList); 761 | if (SUCCEEDED(hr)) 762 | { 763 | DWORD dwDecodedImageSize, dwSkipChars, dwActualFormat; 764 | hr = CryptStringToBinaryA(pszBase64, NULL, CRYPT_STRING_BASE64, NULL, 765 | &dwDecodedImageSize, &dwSkipChars, &dwActualFormat) ? S_OK : E_FAIL; 766 | if (SUCCEEDED(hr)) 767 | { 768 | BYTE *pbDecodedImage = (BYTE*)LocalAlloc(LPTR, dwDecodedImageSize); 769 | hr = pbDecodedImage ? S_OK : E_OUTOFMEMORY; 770 | if (SUCCEEDED(hr)) 771 | { 772 | hr = CryptStringToBinaryA(pszBase64, lstrlenA(pszBase64), CRYPT_STRING_BASE64, 773 | pbDecodedImage, &dwDecodedImageSize, &dwSkipChars, &dwActualFormat) ? S_OK : E_FAIL; 774 | if (SUCCEEDED(hr)) 775 | { 776 | hr = HRESULT_FROM_WIN32(RegSetKeyValueW(hkey, szKeyName, pszValueName, REG_BINARY, pbDecodedImage, dwDecodedImageSize)); 777 | } 778 | } 779 | } 780 | } 781 | 782 | va_end(argList); 783 | 784 | _UpdateAssocChanged(hr, pszKeyFormatString); 785 | return hr; 786 | } 787 | 788 | __inline HRESULT MapNotFoundToSuccess(HRESULT hr) 789 | { 790 | return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr ? S_OK : hr; 791 | } 792 | 793 | HRESULT CRegisterExtension::RegDeleteKeyPrintf(HKEY hkey, PCWSTR pszKeyFormatString, ...) const 794 | { 795 | va_list argList; 796 | va_start(argList, pszKeyFormatString); 797 | 798 | WCHAR szKeyName[512]; 799 | HRESULT hr = StringCchVPrintf(szKeyName, ARRAYSIZE(szKeyName), pszKeyFormatString, argList); 800 | if (SUCCEEDED(hr)) 801 | { 802 | hr = HRESULT_FROM_WIN32(RegDeleteTree(hkey, szKeyName)); 803 | } 804 | 805 | va_end(argList); 806 | 807 | _UpdateAssocChanged(hr, pszKeyFormatString); 808 | return MapNotFoundToSuccess(hr); 809 | } 810 | 811 | HRESULT CRegisterExtension::RegDeleteKeyValuePrintf(HKEY hkey, PCWSTR pszKeyFormatString, PCWSTR pszValue, ...) const 812 | { 813 | va_list argList; 814 | va_start(argList, pszKeyFormatString); 815 | 816 | WCHAR szKeyName[512]; 817 | HRESULT hr = StringCchVPrintf(szKeyName, ARRAYSIZE(szKeyName), pszKeyFormatString, argList); 818 | if (SUCCEEDED(hr)) 819 | { 820 | hr = HRESULT_FROM_WIN32(RegDeleteKeyValueW(hkey, szKeyName, pszValue)); 821 | } 822 | 823 | va_end(argList); 824 | 825 | _UpdateAssocChanged(hr, pszKeyFormatString); 826 | return MapNotFoundToSuccess(hr); 827 | } 828 | 829 | void CRegisterExtension::_UpdateAssocChanged(HRESULT hr, PCWSTR pszKeyFormatString) const 830 | { 831 | static const WCHAR c_szProgIDPrefix[] = L"Software\\Classes\\%s"; 832 | if (SUCCEEDED(hr) && !_fAssocChanged && 833 | (StrCmpNIC(pszKeyFormatString, c_szProgIDPrefix, ARRAYSIZE(c_szProgIDPrefix) - 1) == 0 || 834 | StrStrI(pszKeyFormatString, L"PropertyHandlers") || 835 | StrStrI(pszKeyFormatString, L"KindMap"))) 836 | { 837 | const_cast(this)->_fAssocChanged = true; 838 | } 839 | } 840 | --------------------------------------------------------------------------------