dllmain.cpp
  1  // dllmain.cpp : Defines the entry point for the DLL application.
  2  #include "pch.h"
  3  
  4  #include <atlfile.h>
  5  #include <atlstr.h>
  6  #include <Shlwapi.h>
  7  #include <shobjidl_core.h>
  8  #include <string>
  9  
 10  #include <common/telemetry/EtwTrace/EtwTrace.h>
 11  #include <common/utils/elevation.h>
 12  #include <common/utils/process_path.h>
 13  #include <common/utils/resources.h>
 14  #include <Settings.h>
 15  #include <trace.h>
 16  
 17  #include <wil/win32_helpers.h>
 18  #include <wrl/module.h>
 19  #include "Generated Files/resource.h"
 20  
 21  using namespace Microsoft::WRL;
 22  
 23  HINSTANCE g_hInst = 0;
 24  Shared::Trace::ETWTrace trace(L"ImageResizerContextMenu");
 25  
 26  #define BUFSIZE 4096 * 4
 27  
 28  BOOL APIENTRY DllMain( HMODULE hModule,
 29                         DWORD  ul_reason_for_call,
 30                         LPVOID lpReserved
 31                       )
 32  {
 33      switch (ul_reason_for_call)
 34      {
 35      case DLL_PROCESS_ATTACH:
 36          g_hInst = hModule;
 37          Trace::RegisterProvider();
 38          break;
 39      case DLL_PROCESS_DETACH:
 40          Trace::UnregisterProvider();
 41          break;
 42      }
 43      return TRUE;
 44  }
 45  
 46  class __declspec(uuid("8F491918-259F-451A-950F-8C3EBF4864AF")) ImageResizerContextMenuCommand final : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand, IObjectWithSite>
 47  {
 48  public:
 49      virtual const wchar_t* Title() { return L"Image Resizer"; }
 50      virtual const EXPCMDFLAGS Flags() { return ECF_DEFAULT; }
 51      virtual const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) { return ECS_ENABLED; }
 52  
 53      // IExplorerCommand
 54      IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name)
 55      {
 56          return SHStrDup(context_menu_caption.c_str(), name);
 57      }
 58  
 59      IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon)
 60      {
 61          std::wstring iconResourcePath = get_module_folderpath(g_hInst);
 62          iconResourcePath += L"\\Assets\\ImageResizer\\";
 63          iconResourcePath += L"ImageResizer.ico";
 64          return SHStrDup(iconResourcePath.c_str(), icon);
 65      }
 66  
 67      IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip)
 68      {
 69          *infoTip = nullptr;
 70          return E_NOTIMPL;
 71      }
 72  
 73      IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName)
 74      {
 75          *guidCommandName = __uuidof(this);
 76          return S_OK;
 77      }
 78  
 79      IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* cmdState)
 80      {
 81          if (nullptr == selection) {
 82              // We've observed that it's possible that a null gets passed instead of an empty array. Just don't show the context menu in this case.
 83              *cmdState = ECS_HIDDEN;
 84              return S_OK;
 85          }
 86  
 87          if (!CSettingsInstance().GetEnabled())
 88          {
 89              *cmdState = ECS_HIDDEN;
 90              return S_OK;
 91          }
 92          // Hide if the file is not an image
 93          *cmdState = ECS_HIDDEN;
 94          // Suppressing C26812 warning as the issue is in the shtypes.h library
 95  #pragma warning(suppress : 26812)
 96          PERCEIVED type;
 97          PERCEIVEDFLAG flag;
 98          IShellItem* shellItem=nullptr;
 99          //Check extension of first item in the list (the item which is right-clicked on)
100          HRESULT getItemResult  = selection->GetItemAt(0, &shellItem);
101          if (S_OK != getItemResult || nullptr == shellItem) {
102              // Some safeguards to avoid runtime errors.
103              *cmdState = ECS_HIDDEN;
104              return S_OK;
105          }
106          LPTSTR pszPath;
107          // Retrieves the entire file system path of the file from its shell item
108          HRESULT getDisplayResult = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
109          if (S_OK != getDisplayResult || nullptr == pszPath)
110          {
111              // Avoid crashes in the following code.
112              return E_FAIL;
113          }
114  
115          LPTSTR pszExt = PathFindExtension(pszPath);
116          if (nullptr == pszExt)
117          {
118              CoTaskMemFree(pszPath);
119              // Avoid crashes in the following code.
120              return E_FAIL;
121          }
122  
123          // TODO: Instead, detect whether there's a WIC codec installed that can handle this file
124          AssocGetPerceivedType(pszExt, &type, &flag, NULL);
125  
126          CoTaskMemFree(pszPath);
127          // If selected file is an image...
128  
129          if (type == PERCEIVED_TYPE_IMAGE)
130          {
131              *cmdState = ECS_ENABLED;
132          }
133          return S_OK;
134      }
135  
136      IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept
137      try
138      {
139          trace.UpdateState(true);
140  
141          Trace::Invoked();
142          HRESULT hr = S_OK;
143  
144          if (selection)
145          {
146              hr = ResizePictures(selection);
147          }
148  
149          Trace::InvokedRet(hr);
150  
151          trace.UpdateState(false);
152          trace.Flush();
153  
154          return hr;
155      }
156      CATCH_RETURN();
157  
158      IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* flags)
159      {
160          *flags = Flags();
161          return S_OK;
162      }
163      IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands)
164      {
165          *enumCommands = nullptr;
166          return E_NOTIMPL;
167      }
168  
169      // IObjectWithSite
170      IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept
171      {
172          m_site = site;
173          return S_OK;
174      }
175      IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept { return m_site.CopyTo(riid, site); }
176  
177  protected:
178      ComPtr<IUnknown> m_site;
179  
180  private:
181      HRESULT StartNamedPipeServerAndSendData(std::wstring pipe_name)
182      {
183          hPipe = CreateNamedPipe(
184              pipe_name.c_str(),
185              PIPE_ACCESS_DUPLEX |
186                  WRITE_DAC,
187              PIPE_TYPE_MESSAGE |
188                  PIPE_READMODE_MESSAGE |
189                  PIPE_WAIT,
190              PIPE_UNLIMITED_INSTANCES,
191              BUFSIZE,
192              BUFSIZE,
193              0,
194              NULL);
195  
196          if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)
197          {
198              return E_FAIL;
199          }
200  
201          // This call blocks until a client process connects to the pipe
202          BOOL connected = ConnectNamedPipe(hPipe, NULL);
203          if (!connected)
204          {
205              if (GetLastError() == ERROR_PIPE_CONNECTED)
206              {
207                  return S_OK;
208              }
209              else
210              {
211                  CloseHandle(hPipe);
212              }
213              return E_FAIL;
214          }
215  
216          return S_OK;
217      }
218  
219      HRESULT ResizePictures(IShellItemArray* psiItemArray)
220      {
221          // Set the application path based on the location of the dll
222          std::wstring path = get_module_folderpath(g_hInst);
223          path = path + L"\\PowerToys.ImageResizer.exe";
224  
225          std::wstring pipe_name(L"\\\\.\\pipe\\powertoys_imageresizerinput_");
226          UUID temp_uuid;
227          wchar_t* uuid_chars = nullptr;
228          if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
229          {
230              auto val = get_last_error_message(GetLastError());
231              Logger::warn(L"UuidCreate cannot create guid. {}", val.has_value() ? val.value() : L"");
232          }
233          else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(& uuid_chars)) != RPC_S_OK)
234          {
235              auto val = get_last_error_message(GetLastError());
236              Logger::warn(L"UuidToString cannot convert to string. {}", val.has_value() ? val.value() : L"");
237          }
238  
239          if (uuid_chars != nullptr)
240          {
241              pipe_name += std::wstring(uuid_chars);
242              RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars));
243              uuid_chars = nullptr;
244          }
245          create_pipe_thread = std::thread(&ImageResizerContextMenuCommand::StartNamedPipeServerAndSendData, this, pipe_name);
246          RunNonElevatedEx(path.c_str(), pipe_name, get_module_folderpath(g_hInst));
247          create_pipe_thread.join();
248  
249          if (hPipe != INVALID_HANDLE_VALUE)
250          {
251              CAtlFile writePipe(hPipe);
252  
253              //m_pdtobj will be NULL when invoked from the MSIX build as Initialize is never called (IShellExtInit functions aren't called in case of MSIX).
254              DWORD fileCount = 0;
255              // Gets the list of files currently selected using the IShellItemArray
256              psiItemArray->GetCount(&fileCount);
257              // Iterate over the list of files
258              for (DWORD i = 0; i < fileCount; i++)
259              {
260                  IShellItem* shellItem;
261                  HRESULT getItemAtResult = psiItemArray->GetItemAt(i, &shellItem);
262                  if (SUCCEEDED(getItemAtResult))
263                  {
264                      LPWSTR itemName;
265                      // Retrieves the entire file system path of the file from its shell item
266                      HRESULT getDisplayResult = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName);
267                      if (SUCCEEDED(getDisplayResult))
268                      {
269                          CString fileName(itemName);
270                          fileName.Append(_T("\r\n"));
271                          // Write the file path into the input stream for image resizer
272                          writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
273                      }
274                  }
275              }
276              writePipe.Close();
277          }
278  
279          return S_OK;
280      }
281  
282      std::thread create_pipe_thread;
283      HANDLE hPipe = INVALID_HANDLE_VALUE;
284      std::wstring context_menu_caption = GET_RESOURCE_STRING_FALLBACK(IDS_IMAGERESIZER_CONTEXT_MENU_ENTRY, L"Resize with Image Resizer");
285  };
286  
287  CoCreatableClass(ImageResizerContextMenuCommand)
288  CoCreatableClassWrlCreatorMapInclude(ImageResizerContextMenuCommand)
289  
290  
291  STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory)
292  {
293      return Module<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory);
294  }
295  
296  STDAPI DllCanUnloadNow()
297  {
298      return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
299  }
300  
301  STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ void** instance)
302  {
303      return Module<InProc>::GetModule().GetClassObject(rclsid, riid, instance);
304  }