Clone
ixen <ixen@copyhandler.com>
committed
on 03 Nov 16
Improvements in shell extension registration process (CH-286). Updated Polish translation.
ParallelizeReaderWriter + 3 more
src/ch/TShellExtensionClient.cpp (+149 -94)
2 2 *   Copyright (C) 2001-2008 by J�zef Starosczyk                           *
3 3 *   ixen@copyhandler.com                                                  *
4 4 *                                                                         *
5 5 *   This program is free software; you can redistribute it and/or modify  *
6 6 *   it under the terms of the GNU Library General Public License          *
7 7 *   (version 2) as published by the Free Software Foundation;             *
8 8 *                                                                         *
9 9 *   This program is distributed in the hope that it will be useful,       *
10 10 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11 11 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12 12 *   GNU General Public License for more details.                          *
13 13 *                                                                         *
14 14 *   You should have received a copy of the GNU Library General Public     *
15 15 *   License along with this program; if not, write to the                 *
16 16 *   Free Software Foundation, Inc.,                                       *
17 17 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18 18 ***************************************************************************/
19 19 #include "stdafx.h"
20 20 #include "TShellExtensionClient.h"
21 21 #include "objbase.h"
  22 #include "../chext/Logger.h"
22 23
23 24 #ifdef _DEBUG
24 25 #define new DEBUG_NEW
25 26 #endif
26 27
27 28 TShellExtensionClient::TShellExtensionClient() :
28 29         m_piShellExtControl(nullptr),
29 30         m_bInitialized(false)
30 31 {
31 32 }
32 33
33 34 TShellExtensionClient::~TShellExtensionClient()
34 35 {
35 36         FreeControlInterface();
36 37         UninitializeCOM();
37 38 }
38 39
39 40 HRESULT TShellExtensionClient::InitializeCOM()
40 41 {
41 42         if(m_bInitialized)
42 43                 return S_FALSE;
43 44
44 45         HRESULT hResult = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
45 46         if(SUCCEEDED(hResult))
46 47                 m_bInitialized = true;
47 48         else if(hResult == RPC_E_CHANGED_MODE)
48 49                 return S_FALSE;
49 50
50 51         return hResult;
51 52 }
52 53
53 54 void TShellExtensionClient::UninitializeCOM()
54 55 {
55 56         if(m_bInitialized)
56 57         {
57 58                 CoUninitialize();
58 59                 m_bInitialized = false;
59 60         }
60 61 }
61 62
62   HRESULT TShellExtensionClient::RegisterShellExtDll(const CString& strPath, long lClientVersion, long& rlExtensionVersion, CString& rstrExtensionStringVersion)
  63 bool TShellExtensionClient::DetectRegExe()
63 64 {
64           if(strPath.IsEmpty())
65                   return E_INVALIDARG;
  65         const DWORD dwSize = 32768;
  66         wchar_t szData[dwSize];
  67         DWORD dwResult = ::GetModuleFileName(nullptr, szData, dwSize);
  68         if (dwResult == 0)
  69                 return false;
  70         szData[dwResult] = L'\0';
66 71
67           HRESULT hResult = S_OK;
  72         std::wstring wstrDir = szData;
68 73
69           if(SUCCEEDED(hResult))
70                   hResult = InitializeCOM();
  74         size_t stPos = wstrDir.find_last_of(L'\\');
  75         if (stPos != std::wstring::npos)
  76                 wstrDir.erase(wstrDir.begin() + stPos + 1, wstrDir.end());
71 77
72           // get rid of the interface, so we can at least try to re-register
73           if(SUCCEEDED(hResult))
74                   FreeControlInterface();
  78 #ifdef _WIN64
  79         wstrDir += _T("regchext64.exe");
  80 #else
  81         wstrDir += _T("regchext.exe");
  82 #endif
75 83
76           // first try - load dll and register it manually.
77           // if failed - try by loading extension manually (would fail on vista when running as user)
78           if(SUCCEEDED(hResult))
  84         m_strRegExe = wstrDir;
  85
  86         return true;
  87 }
  88
  89 logger::TLoggerPtr& TShellExtensionClient::GetLogger()
79 90 {
80                   HRESULT (STDAPICALLTYPE *pfn)(void) = nullptr;
81                   HINSTANCE hMod = LoadLibrary(strPath);  // load the dll
82                   if(hMod == nullptr)
83                           hResult = HRESULT_FROM_WIN32(GetLastError());
84                   if(SUCCEEDED(hResult) && !hMod)
85                           hResult = E_FAIL;
86                   if(SUCCEEDED(hResult))
  91         if(!m_spLog)
  92                 m_spLog = logger::MakeLogger(GetLogFileData(), L"ShellExtClient");
  93
  94         return m_spLog;
  95 }
  96
  97 ERegistrationResult TShellExtensionClient::RegisterShellExtDll(long lClientVersion, long& rlExtensionVersion, CString& rstrExtensionStringVersion)
87 98 {
88                           (FARPROC&)pfn = GetProcAddress(hMod, "DllRegisterServer");
89                           if(pfn == nullptr)
90                                   hResult = E_FAIL;
91                           if(SUCCEEDED(hResult))
92                                   hResult = (*pfn)();
  99         LOG_INFO(GetLogger()) << L"Registering shell extension";
93 100
94                           FreeLibrary(hMod);
  101         HRESULT hResult = InitializeCOM();
  102         if(FAILED(hResult))
  103         {
  104                 LOG_ERROR(GetLogger()) << L"Failed to initialize COM. Error: " << hResult;
  105                 return eFailure;
95 106         }
  107
  108         // get rid of the interface, so we can at least try to re-register
  109         LOG_DEBUG(GetLogger()) << L"Freeing control interface";
  110         FreeControlInterface();
  111
  112         LOG_DEBUG(GetLogger()) << L"Detecting regchext binary";
  113         if(!DetectRegExe())
  114         {
  115                 LOG_ERROR(GetLogger()) << L"Failed to detect regchext binary";
  116                 return eFailure;
96 117         }
97 118
  119         LOG_DEBUG(GetLogger()) << L"Executing regchext binary";
98 120         // if previous operation failed (ie. vista system) - try running regsvr32 with elevated privileges
99           if(SCODE_CODE(hResult) == ERROR_ACCESS_DENIED)
100           {
101 121         // try with regsvr32
102 122         SHELLEXECUTEINFO sei;
103 123         memset(&sei, 0, sizeof(sei));
104 124         sei.cbSize = sizeof(sei);
105                   sei.fMask = SEE_MASK_UNICODE;
  125         sei.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS;
106 126         sei.lpVerb = _T("runas");
107                   sei.lpFile = _T("regsvr32.exe");
108                   CString strParams = CString(_T("/s \"")) + strPath + CString(_T("\""));
109                   sei.lpParameters = strParams;
  127         sei.lpFile = m_strRegExe.c_str();
  128         sei.lpParameters = _T("");
110 129         sei.nShow = SW_SHOW;
111 130
112 131         if(!ShellExecuteEx(&sei))
113                           hResult = E_FAIL;
114                   else
115                           hResult = S_OK;
  132         {
  133                 DWORD dwLastError = GetLastError();
  134                 LOG_ERROR(GetLogger()) << L"Failed to execute regchext binary. Error: " << dwLastError;
  135                 return eFailure;
116 136         }
117 137
118           if(SUCCEEDED(hResult))
  138         LOG_DEBUG(GetLogger()) << L"Waiting for registration process to finish";
  139         if(SUCCEEDED(hResult) && WaitForSingleObject(sei.hProcess, 10000) != WAIT_OBJECT_0)
119 140         {
120                   SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
  141                 DWORD dwLastError = GetLastError();
  142                 LOG_ERROR(GetLogger()) << L"Waiting failed. Last error: " << dwLastError;
  143                 CloseHandle(sei.hProcess);
121 144
  145                 return eFailure;
  146         }
  147
  148         DWORD dwExitCode = 0;
  149         if (!GetExitCodeProcess(sei.hProcess, &dwExitCode))
  150         {
  151                 DWORD dwLastError = GetLastError();
  152                 LOG_ERROR(GetLogger()) << L"Failed to retrieve process exit code. Last error: " << dwLastError;
  153                 CloseHandle(sei.hProcess);
  154
  155                 return eFailure;
  156         }
  157         CloseHandle(sei.hProcess);
  158
  159         LOG_INFO(GetLogger()) << L"Registration result: " << dwExitCode;
  160
  161         ERegistrationResult eResult = (ERegistrationResult)dwExitCode;
  162
  163         if(eResult == eSuccess || eResult == eSuccessNative)
  164         {
122 165                 // NOTE: we are re-trying to enable the shell extension through our notification interface
123 166                 // in case of class-not-registered error because (it seems) system needs some time to process
124 167                 // DLL's self registration and usually the first call fails.
125 168                 int iTries = 3;
126 169                 do
127 170                 {
  171                         LOG_DEBUG(GetLogger()) << L"Trying to enable native shell extension";;
128 172                         hResult = EnableExtensionIfCompatible(lClientVersion, rlExtensionVersion, rstrExtensionStringVersion);
129 173                         if(hResult == REGDB_E_CLASSNOTREG)
130 174                         {
131                                   ATLTRACE(_T("Class CLSID_CShellExtControl still not registered...\r\n"));
  175                                 LOG_ERROR(GetLogger()) << L"Class CLSID_CShellExtControl still not registered";
132 176                                 Sleep(500);
133 177                         }
134 178                 }
135 179                 while(--iTries && hResult == REGDB_E_CLASSNOTREG);
  180
  181                 if(FAILED(hResult))
  182                 {
  183                         LOG_INFO(GetLogger()) << L"Shell Extension requires system restart";;
  184                         eResult = eSuccessNeedRestart;
136 185                 }
  186         }
137 187
138           return hResult;
  188         return eResult;
139 189 }
140 190
141   HRESULT TShellExtensionClient::UnRegisterShellExtDll(const CString& strPath)
  191 ERegistrationResult TShellExtensionClient::UnRegisterShellExtDll()
142 192 {
143           if(strPath.IsEmpty())
144                   return E_INVALIDARG;
  193         LOG_INFO(GetLogger()) << L"Unregistering shell extension";
145 194
146           HRESULT hResult = S_OK;
  195         HRESULT hResult = InitializeCOM();
  196         if(FAILED(hResult))
  197         {
  198                 LOG_ERROR(GetLogger()) << L"Failed to initialize COM. Error: " << hResult;
  199                 return eFailure;
  200         }
147 201
148           if(SUCCEEDED(hResult))
149                   hResult = InitializeCOM();
150  
151           // get rid of the interface if unregistering
152           if(SUCCEEDED(hResult))
  202         // get rid of the interface, so we can at least try to re-register
  203         LOG_DEBUG(GetLogger()) << L"Freeing control interface";
153 204         FreeControlInterface();
154 205
155           // first try - load dll and register it manually.
156           // if failed - try by loading extension manually (would fail on vista when running as user)
157           if(SUCCEEDED(hResult))
  206         LOG_DEBUG(GetLogger()) << L"Detecting regchext binary";
  207         if(!DetectRegExe())
158 208         {
159                   HRESULT (STDAPICALLTYPE *pfn)(void) = nullptr;
160                   HINSTANCE hMod = LoadLibrary(strPath);  // load the dll
161                   if(hMod == nullptr)
162                           hResult = HRESULT_FROM_WIN32(GetLastError());
163                   if(SUCCEEDED(hResult) && !hMod)
164                           hResult = E_FAIL;
165                   if(SUCCEEDED(hResult))
166                   {
167                           (FARPROC&)pfn = GetProcAddress(hMod, "DllUnregisterServer");
168                           if(pfn == nullptr)
169                                   hResult = E_FAIL;
170                           if(SUCCEEDED(hResult))
171                                   hResult = (*pfn)();
172  
173                           FreeLibrary(hMod);
  209                 LOG_ERROR(GetLogger()) << L"Failed to detect regchext binary";
  210                 return eFailure;
174 211         }
175           }
176 212
  213         LOG_DEBUG(GetLogger()) << L"Executing regchext binary";
177 214         // if previous operation failed (ie. vista system) - try running regsvr32 with elevated privileges
178           if(SCODE_CODE(hResult) == ERROR_ACCESS_DENIED)
179           {
180 215         // try with regsvr32
181 216         SHELLEXECUTEINFO sei;
182 217         memset(&sei, 0, sizeof(sei));
183 218         sei.cbSize = sizeof(sei);
184                   sei.fMask = SEE_MASK_UNICODE;
  219         sei.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS;
185 220         sei.lpVerb = _T("runas");
186                   sei.lpFile = _T("regsvr32.exe");
187                   CString strParams = CString(_T("/u /s \"")) + strPath + CString(_T("\""));
188                   sei.lpParameters = strParams;
  221         sei.lpFile = m_strRegExe.c_str();
  222         sei.lpParameters = _T("/u");
189 223         sei.nShow = SW_SHOW;
190 224
191 225         if(!ShellExecuteEx(&sei))
192                           hResult = E_FAIL;
193                   else
194                           hResult = S_OK;
  226         {
  227                 DWORD dwLastError = GetLastError();
  228                 LOG_ERROR(GetLogger()) << L"Failed to execute regchext binary. Error: " << dwLastError;
  229                 return eFailure;
195 230         }
196 231
197           if(SUCCEEDED(hResult))
198                   SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
  232         LOG_DEBUG(GetLogger()) << L"Waiting for deregistration process to finish";
  233         if(SUCCEEDED(hResult) && WaitForSingleObject(sei.hProcess, 10000) != WAIT_OBJECT_0)
  234         {
  235                 DWORD dwLastError = GetLastError();
  236                 LOG_ERROR(GetLogger()) << L"Waiting failed. Last error: " << dwLastError;
  237                 CloseHandle(sei.hProcess);
199 238
200           return hResult;
  239                 return eFailure;
201 240         }
202 241
  242         DWORD dwExitCode = 0;
  243         if(!GetExitCodeProcess(sei.hProcess, &dwExitCode))
  244         {
  245                 DWORD dwLastError = GetLastError();
  246                 LOG_ERROR(GetLogger()) << L"Failed to retrieve process exit code. Last error: " << dwLastError;
  247                 CloseHandle(sei.hProcess);
  248
  249                 return eFailure;
  250         }
  251         CloseHandle(sei.hProcess);
  252
  253         LOG_INFO(GetLogger()) << L"Deregistration result: " << dwExitCode;
  254
  255         return (ERegistrationResult)dwExitCode;
  256 }
  257
203 258 HRESULT TShellExtensionClient::EnableExtensionIfCompatible(long lClientVersion, long& rlExtensionVersion, CString& rstrExtensionStringVersion)
204 259 {
205 260         rlExtensionVersion = 0;
206 261         rstrExtensionStringVersion.Empty();
207 262
208 263         BSTR bstrVersion = nullptr;
209 264
210 265         HRESULT hResult = RetrieveControlInterface();
211 266         if(SUCCEEDED(hResult) && !m_piShellExtControl)
212 267                 hResult = E_FAIL;
213 268         if(SUCCEEDED(hResult))
214 269                 hResult = m_piShellExtControl->GetVersion(&rlExtensionVersion, &bstrVersion);
215 270         if(SUCCEEDED(hResult))
216 271         {
217 272                 // enable or disable extension - currently we only support extension from strictly the same version as CH
218 273                 bool bVersionMatches = (lClientVersion == rlExtensionVersion);
219 274                 hResult = m_piShellExtControl->SetFlags(bVersionMatches ? eShellExt_Enabled : 0, eShellExt_Enabled);
220 275                 if(SUCCEEDED(hResult))
221 276                         hResult = bVersionMatches ? S_OK : S_FALSE;
222 277         }