summaryrefslogtreecommitdiff
path: root/plugins/ShellExt/src/shlcom.cpp
blob: 1c48674315d5fcc0ad8f86ec281c26b0aa7f23b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
#include "stdafx.h"
#include "shlcom.h"
#include "shlicons.h"

#pragma comment(lib, "rpcrt4.lib")

int DllFactoryCount, DllObjectCount;

struct TCMInvokeCommandInfo
{
	int   cbSize;
	DWORD fMask;
	HWND  hwnd;
	char *lpVerb;  // maybe index, type cast as Integer
	char *lpParams;
	char *lpDir;
	int   nShow;
	DWORD dwHotkey;
	HICON hIcon;
};

/////////////////////////////////////////////////////////////////////////////////////////

int IsCOMRegistered()
{
	HKEY hRegKey;
	int  res = 0;

	// these arent the BEST checks in the world
	if (!RegOpenKeyEx(HKEY_CLASSES_ROOT, L"miranda.shlext", 0, KEY_READ, &hRegKey)) {
		res += COMREG_OK;
		RegCloseKey(hRegKey);
	}

	if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_READ, &hRegKey)) {
		DWORD lpType = REG_SZ;
		if (!RegQueryValueEx(hRegKey, L"{72013A26-A94C-11d6-8540-A5E62932711D}", nullptr, &lpType, nullptr, nullptr))
			res += COMREG_APPROVED;
		RegCloseKey(hRegKey);
	}

	return res;
}

/////////////////////////////////////////////////////////////////////////////////////////

char* CreateProcessUID(int pid, char *buf, size_t bufLen)
{
	sprintf_s(buf, bufLen, "mim.shlext.%d$", pid);
	return buf;
}

/////////////////////////////////////////////////////////////////////////////////////////

//
// IPC part
//

struct TAddArgList
{
	LPSTR szFile; // file being processed
	int cch; // it's length (with space for NULL char)
	int count; // number we have so far
	LPSTR *files;
	MCONTACT hContact;
	HANDLE hEvent;
};

BOOL AddToList(TAddArgList& args)
{
	char szBuf[MAX_PATH];
	LPSTR szThis;

	DWORD attr = GetFileAttributesA(args.szFile);
	if (attr != 0xFFFFFFFF && (attr & FILE_ATTRIBUTE_HIDDEN) == 0) {
		if ((args.count % 10) == 5)
			if (Miranda_IsTerminated() != 0)
				return true;

		if (attr & FILE_ATTRIBUTE_DIRECTORY) {
			// add the directory
			lstrcpyA(szBuf, args.szFile);
			args.files = (LPSTR*)mir_realloc(args.files, (args.count + 1) * sizeof(LPSTR));
			char *p = mir_strdup(szBuf);
			args.files[args.count++] = p;
			// tack on ending search token
			lstrcatA(szBuf, "\\*");

			WIN32_FIND_DATAA fd;
			HANDLE hFind = FindFirstFileA(szBuf, &fd);
			while (true) {
				if (fd.cFileName[0] != '.') {
					mir_snprintf(szBuf, "%s\\%s", args.szFile, fd.cFileName);
					// keep a copy of the current thing being processed
					szThis = args.szFile;
					args.szFile = szBuf;
					int cchThis = args.cch;
					args.cch = (int)strlen(szBuf) + 1;
					// recurse
					BOOL Result = AddToList(args);
					// restore
					args.szFile = szThis;
					args.cch = cchThis;
					if (Result) {
						FindClose(hFind);
						return true;
					}
				}
				if (!FindNextFileA(hFind, &fd))
					break;
			}
			FindClose(hFind);
		}
		else {
			// add the file
			args.files = (LPSTR*)mir_realloc(args.files, (args.count + 1) * sizeof(LPSTR));
			args.files[args.count++] = mir_strdup(args.szFile);
		}
	}
	return false;
}

void NTAPI MainThreadIssueTransfer(ULONG_PTR param)
{
	TAddArgList *p = (TAddArgList *)param;
	db_set_b(p->hContact, SHLExt_Name, SHLExt_MRU, 1);
	CallService(MS_FILE_SENDSPECIFICFILES, (WPARAM)p->hContact, LPARAM(p->files));
	SetEvent(p->hEvent);
}

void __cdecl IssueTransferThread(void *param)
{
	THeaderIPC *pipch = (THeaderIPC *)param;
	HANDLE hMainThread = HANDLE(pipch->Param);

	char szBuf[MAX_PATH];
	GetCurrentDirectoryA(sizeof(szBuf), szBuf);

	TAddArgList args;
	args.count = 0;
	args.files = nullptr;
	TSlotIPC *pct = pipch->DataPtr;
	BOOL bQuit = false;
	while (pct != nullptr) {
		if (pct->cbSize != sizeof(TSlotIPC))
			break;
		args.szFile = LPSTR(UINT_PTR(pct) + sizeof(TSlotIPC));
		args.hContact = pct->hContact;
		args.cch = pct->cbStrSection + 1;
		bQuit = AddToList(args);
		if (bQuit)
			break;
		pct = pct->Next;
	} // while

	if (args.files != nullptr) {
		args.files = (LPSTR*)mir_realloc(args.files, (args.count + 1) * sizeof(LPSTR));
		args.files[args.count++] = nullptr;
		if (!bQuit) {
			args.hEvent = CreateEvent(nullptr, true, false, nullptr);
			QueueUserAPC(MainThreadIssueTransfer, hMainThread, UINT_PTR(&args));
			while (true) {
				if (WaitForSingleObjectEx(args.hEvent, INFINITE, true) != WAIT_IO_COMPLETION)
					break;
			}
			CloseHandle(args.hEvent);
		}
		for (int j = 0; j < args.count; j++)
			mir_free(args.files[j]);
		mir_free(args.files);
	}
	SetCurrentDirectoryA(szBuf);
	mir_free(pipch);
	CloseHandle(hMainThread);
}

struct TSlotInfo
{
	MCONTACT hContact;
	int    hProto;
	int    dwStatus; // will be aligned anyway
};

int __cdecl SortContact(const void *Item1, const void *Item2)
{
	return Clist_ContactCompare(((TSlotInfo*)Item1)->hContact, ((TSlotInfo*)Item2)->hContact);
}

void ipcGetSkinIcons(THeaderIPC *ipch)
{
	TSlotProtoIcons spi;
	char szTmp[64];

	int protoCount;
	PROTOACCOUNT **pp;
	Proto_EnumAccounts(&protoCount, &pp);
	if (protoCount != 0) {
		spi.pid = GetCurrentProcessId();
		while (protoCount > 0) {
			PROTOACCOUNT *pa = *pp;
			lstrcpyA(szTmp, pa->szModuleName);
			lstrcatA(szTmp, PS_GETCAPS);
			DWORD dwCaps = CallService(szTmp, PFLAGNUM_1, 0);
			if (dwCaps & PF1_FILESEND) {
				TSlotIPC *pct = ipcAlloc(ipch, sizeof(TSlotProtoIcons));
				if (pct != nullptr) {
					// capture all the icons!
					spi.hProto = murmur_hash(pa->szModuleName);
					for (int j = 0; j <= 10; j++)
						spi.hIcons[j] = Skin_LoadProtoIcon(pa->szModuleName, ID_STATUS_OFFLINE + j);

					pct->fType = REQUEST_NEWICONS;
					memcpy(LPSTR(pct) + sizeof(TSlotIPC), &spi, sizeof(TSlotProtoIcons));
					if (ipch->NewIconsBegin == nullptr)
						ipch->NewIconsBegin = pct;
				}
			}
			pp++;
			protoCount--;
		}
	}

	// add Miranda icon
	TSlotIPC *pct = ipcAlloc(ipch, sizeof(TSlotProtoIcons));
	if (pct != nullptr) {
		memset(&spi.hIcons, 0, sizeof(spi.hIcons));
		spi.hProto = 0; // no protocol
		spi.hIcons[0] = Skin_LoadIcon(SKINICON_OTHER_MIRANDA);
		pct->fType = REQUEST_NEWICONS;
		memcpy(LPSTR(pct) + sizeof(TSlotIPC), &spi, sizeof(TSlotProtoIcons));
		if (ipch->NewIconsBegin == nullptr)
			ipch->NewIconsBegin = pct;
	}
}

bool ipcGetSortedContacts(THeaderIPC *ipch, int *pSlot, bool bGroupMode)
{
	// hide offliners?
	bool bHideOffline = db_get_b(0, "CList", "HideOffline", 0) == 1;
	// do they wanna hide the offline people anyway?
	if (db_get_b(0, SHLExt_Name, SHLExt_ShowNoOffline, 0) == 1)
		// hide offline people
		bHideOffline = true;

	// get the number of contacts
	int dwContacts = db_get_contact_count();
	if (dwContacts == 0)
		return false;

	// get the contacts in the array to be sorted by status, trim out anyone
	// who doesn't wanna be seen.
	TSlotInfo *pContacts = (TSlotInfo*)mir_alloc((dwContacts + 2) * sizeof(TSlotInfo));
	int i = 0;
	int dwOnline = 0;
	for (MCONTACT hContact = db_find_first(); hContact != 0; hContact = db_find_next(hContact)) {
		if (i >= dwContacts)
			break;

		// do they have a running protocol? 
		char *szProto = GetContactProto(hContact);
		if (szProto != nullptr) {
			// does it support file sends?
			DWORD dwCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0);
			if ((dwCaps & PF1_FILESEND) == 0)
				continue;

			int dwStatus = db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE);
			if (dwStatus != ID_STATUS_OFFLINE)
				dwOnline++;
			else if (bHideOffline)
				continue;

			// is HIT on?
			if (BST_UNCHECKED == db_get_b(0, SHLExt_Name, SHLExt_UseHITContacts, BST_UNCHECKED)) {
				// don't show people who are "Hidden" "NotOnList" or Ignored
				if (db_get_b(hContact, "CList", "Hidden", 0) == 1 ||
					db_get_b(hContact, "CList", "NotOnList", 0) == 1 ||
					CallService(MS_IGNORE_ISIGNORED, hContact, IGNOREEVENT_MESSAGE | IGNOREEVENT_URL | IGNOREEVENT_FILE) != 0)
					continue;
			}
			// is HIT2 off?
			if (BST_UNCHECKED == db_get_b(0, SHLExt_Name, SHLExt_UseHIT2Contacts, BST_UNCHECKED))
				if (db_get_w(hContact, szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE)
					continue;

			// store
			pContacts[i].hContact = hContact;
			pContacts[i].dwStatus = dwStatus;
			pContacts[i++].hProto = murmur_hash(szProto);
		}
	}

	// if no one is online and the CList isn't showing offliners, quit
	if (dwOnline == 0 && bHideOffline) {
		mir_free(pContacts);
		return false;
	}

	dwContacts = i;
	qsort(pContacts, dwContacts, sizeof(TSlotInfo), SortContact);

	// create an IPC slot for each contact and store display name, etc
	for (i = 0; i < dwContacts; i++) {
		ptrA szContact(mir_u2a(pcli->pfnGetContactDisplayName(pContacts[i].hContact, 0)));
		if (szContact != NULL) {
			ptrA szGroup;
			if (bGroupMode)
				szGroup = db_get_sa(pContacts[i].hContact, "CList", "Group");

			int cch = lstrlenA(szContact) + 1;
			TSlotIPC *pct = ipcAlloc(ipch, cch + 1 + lstrlenA(szGroup) + 1);
			if (pct == nullptr)
				break;

			// lie about the actual size of the TSlotIPC
			pct->cbStrSection = cch;
			LPSTR szSlot = LPSTR(pct) + sizeof(TSlotIPC);
			lstrcpyA(szSlot, szContact);
			pct->fType = REQUEST_CONTACTS;
			pct->hContact = pContacts[i].hContact;
			pct->Status = pContacts[i].dwStatus;
			pct->hProto = pContacts[i].hProto;
			pct->MRU = db_get_b(pct->hContact, SHLExt_Name, SHLExt_MRU, 0);
			if (ipch->ContactsBegin == nullptr)
				ipch->ContactsBegin = pct;
			szSlot += cch + 1;
			if (szGroup != 0) {
				pct->hGroup = murmur_hash(szGroup);
				lstrcpyA(szSlot, szGroup);
			}
			else {
				pct->hGroup = 0;
				*szSlot = 0;
			}
			pSlot[0]++;
		}
	}
	mir_free(pContacts);
	return true;
}

// worker thread to clear MRU, called by the IPC bridge
void __cdecl ClearMRUThread(void*)
{
	for (MCONTACT hContact = db_find_first(); hContact != 0; hContact = db_find_next(hContact))
		if (db_get_b(hContact, SHLExt_Name, SHLExt_MRU, 0) > 0)
			db_set_b(hContact, SHLExt_Name, SHLExt_MRU, 0);
}

// this function is called from an APC into the main thread
void __stdcall ipcService(ULONG_PTR)
{
	HANDLE hSignal;
	LPSTR szBuf;
	char szGroupStr[32];
	DBVARIANT dbv;
	LPSTR szMiranda;

	// try to open the file mapping object the caller must make sure no other
	// running instance is using this file
	HANDLE hMap = OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, IPC_PACKET_NAME);
	if (hMap == nullptr)
		return;

	// map the file to this process
	THeaderIPC *pMMT = (THeaderIPC*)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
	// if it fails the caller should of had some timeout in wait
	if (pMMT != nullptr && pMMT->cbSize == sizeof(THeaderIPC) && pMMT->dwVersion == PLUGIN_MAKE_VERSION(2, 0, 1, 2)) {
		// toggle the right bits
		int *bits = &pMMT->fRequests;
		// jump right to a worker thread for file processing?
		if (*bits & REQUEST_XFRFILES) {
			THeaderIPC *cloned = (THeaderIPC*)mir_alloc(IPC_PACKET_SIZE);
			// translate from client space to cloned heap memory
			pMMT->pServerBaseAddress = pMMT->pClientBaseAddress;
			pMMT->pClientBaseAddress = cloned;
			memcpy(cloned, pMMT, IPC_PACKET_SIZE);
			ipcFixupAddresses(cloned);
			DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &cloned->Param, THREAD_SET_CONTEXT, false, 0);
			mir_forkthread(&IssueTransferThread, cloned);
			goto Reply;
		}
		// the request was to clear the MRU entries, we have no return data
		if (*bits & REQUEST_CLEARMRU) {
			mir_forkthread(&ClearMRUThread, nullptr);
			goto Reply;
		}
		// the IPC header may have pointers that need to be translated
		// in either case the supplied data area pointers has to be
		// translated to this address space.
		// the server base address is always removed to get an offset
		// to which the client base is added, this is what ipcFixupAddresses() does
		pMMT->pServerBaseAddress = pMMT->pClientBaseAddress;
		pMMT->pClientBaseAddress = pMMT;
		// translate to the server space map
		ipcFixupAddresses(pMMT);
		// store the address map offset so the caller can retranslate
		pMMT->pServerBaseAddress = pMMT;
		// return some options to the client
		if (db_get_b(0, SHLExt_Name, SHLExt_ShowNoIcons, 0) != 0)
			pMMT->dwFlags = HIPC_NOICONS;

		// see if we have a custom string for 'Miranda'
		szMiranda = "Miranda";
		lstrcpynA(pMMT->MirandaName, szMiranda, sizeof(pMMT->MirandaName) - 1);

		// for the MRU menu
		szBuf = Translate("Recently");
		lstrcpynA(pMMT->MRUMenuName, szBuf, sizeof(pMMT->MRUMenuName) - 1);

		// and a custom string for "clear entries"
		szBuf = Translate("Clear entries");
		lstrcpynA(pMMT->ClearEntries, szBuf, sizeof(pMMT->ClearEntries) - 1);

		// if the group mode is on, check if they want the CList setting
		bool bGroupMode = (BST_CHECKED == db_get_b(0, SHLExt_Name, SHLExt_UseGroups, BST_UNCHECKED));
		if (bGroupMode && BST_CHECKED == db_get_b(0, SHLExt_Name, SHLExt_UseCListSetting, BST_UNCHECKED))
			bGroupMode = db_get_b(0, "CList", "UseGroups", true) != 0;

		TSlotIPC *pct = nullptr;
		int iSlot = 0;
		// return profile if set
		if (BST_UNCHECKED == db_get_b(0, SHLExt_Name, SHLExt_ShowNoProfile, BST_UNCHECKED)) {
			pct = ipcAlloc(pMMT, 50);
			if (pct != nullptr) {
				// will actually return with .dat if there's space for it, not what the docs say
				pct->Status = STATUS_PROFILENAME;
				Profile_GetNameA(49, (char*)pct + sizeof(TSlotIPC));
			}
		}
		if (*bits & REQUEST_NEWICONS)
			ipcGetSkinIcons(pMMT);

		if (*bits & REQUEST_GROUPS) {
			// return contact's grouping if it's present
			while (bGroupMode) {
				_itoa(iSlot, szGroupStr, 10);
				if (db_get_s(0, "CListGroups", szGroupStr, &dbv) != 0)
					break;
				pct = ipcAlloc(pMMT, lstrlenA(dbv.pszVal + 1) + 1);
				// first byte has flags, need null term
				if (pct != nullptr) {
					if (pMMT->GroupsBegin == nullptr)
						pMMT->GroupsBegin = pct;
					pct->fType = REQUEST_GROUPS;
					pct->hContact = 0;
					szBuf = LPSTR(pct) + sizeof(TSlotIPC); // get the end of the slot
					lstrcpyA(szBuf, dbv.pszVal + 1);
					pct->hGroup = 0;
					db_free(&dbv); // free the string
				}
				else {
					// outta space
					db_free(&dbv);
					break;
				}
				iSlot++;
			}
			// if there was no space left, it'll } on null
			if (pct == nullptr)
				*bits = (*bits | GROUPS_NOTIMPL) & ~REQUEST_GROUPS;
		}
		// SHOULD check slot space.
		if (*bits & REQUEST_CONTACTS) {
			if (!ipcGetSortedContacts(pMMT, &iSlot, bGroupMode))
				// fail if there were no contacts AT ALL
				*bits = (*bits | CONTACTS_NOTIMPL) & ~REQUEST_CONTACTS;
		}
		// store the number of slots allocated
		pMMT->Slots = iSlot;
Reply:
		// get the handle the caller wants to be signalled on 
		hSignal = OpenEventA(EVENT_ALL_ACCESS, false, pMMT->SignalEventName);
		if (hSignal != nullptr) {
			SetEvent(hSignal);
			CloseHandle(hSignal);
		}

		UnmapViewOfFile(pMMT);
	}
	CloseHandle(hMap);
}

void __cdecl ThreadServer(HANDLE hMainThread)
{
	char szBuf[100];
	HANDLE hEvent = CreateEventA(nullptr, false, false, CreateProcessUID(GetCurrentProcessId(), szBuf, sizeof(szBuf)));
	while (true) {
		int retVal = WaitForSingleObjectEx(hEvent, INFINITE, true);
		if (retVal == WAIT_OBJECT_0)
			QueueUserAPC(ipcService, hMainThread, 0);

		if (Miranda_IsTerminated() == 1)
			break;
	}
	CloseHandle(hEvent);
	CloseHandle(hMainThread);
}

void InvokeThreadServer()
{
	HANDLE hMainThread = nullptr;
	DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hMainThread, THREAD_SET_CONTEXT, false, 0);
	if (hMainThread != nullptr)
		mir_forkthread(&ThreadServer, hMainThread);
}

// helper functions
HRESULT RemoveCOMRegistryEntries()
{
	HKEY hRootKey;
	if (!RegOpenKeyEx(HKEY_CLASSES_ROOT, L"miranda.shlext", 0, KEY_READ, &hRootKey)) {
		// need to delete the subkey before the parent key is deleted under NT/2000/XP
		RegDeleteKey(hRootKey, L"CLSID");
		// close the key
		RegCloseKey(hRootKey);
		// delete it
		if (RegDeleteKey(HKEY_CLASSES_ROOT, L"miranda.shlext") != ERROR_SUCCESS)
			MessageBox(nullptr,
				TranslateT("Unable to delete registry key for 'shlext COM', this key may already be deleted or you may need admin rights."),
				TranslateT("Problem"), MB_ICONERROR);
	}
	if (!RegOpenKeyEx(HKEY_CLASSES_ROOT, L"\\*\\shellex\\ContextMenuHandlers", 0, KEY_ALL_ACCESS, &hRootKey)) {
		if (RegDeleteKey(hRootKey, L"miranda.shlext") != ERROR_SUCCESS)
			MessageBox(nullptr,
				TranslateT("Unable to delete registry key for 'File context menu handlers', this key may already be deleted or you may need admin rights."),
				TranslateT("Problem"), MB_ICONERROR);
		RegCloseKey(hRootKey);
	}
	if (!RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Directory\\shellex\\ContextMenuHandlers", 0, KEY_ALL_ACCESS, &hRootKey)) {
		if (RegDeleteKey(hRootKey, L"miranda.shlext") != ERROR_SUCCESS)
			MessageBox(nullptr,
				TranslateT("Unable to delete registry key for 'Directory context menu handlers', this key may already be deleted or you may need admin rights."),
				TranslateT("Problem"), MB_ICONERROR);
		RegCloseKey(hRootKey);
	}
	if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_ALL_ACCESS, &hRootKey)) {
		if (RegDeleteValue(hRootKey, L"{72013A26-A94C-11d6-8540-A5E62932711D}") != ERROR_SUCCESS) {
			MessageBox(nullptr,
				TranslateT("Unable to delete registry entry for 'Approved context menu handlers', this key may already be deleted or you may need admin rights."),
				TranslateT("Problem"), MB_ICONERROR);
		}
		RegCloseKey(hRootKey);
	}
	return S_OK;
}

// called by the options code to remove COM entries, and before that, get permission, if required.
void CheckUnregisterServer()
{
	if (bIsVistaPlus) {
		// launches regsvr to remove the dll under admin.
		wchar_t szFileName[MAX_PATH], szBuf[MAX_PATH * 2];
		GetModuleFileName(hInst, szFileName, _countof(szFileName));
		mir_snwprintf(szBuf, L"/s /u \"%s\"", szFileName);

		SHELLEXECUTEINFO sei = { sizeof(sei) };
		sei.lpVerb = L"runas";
		sei.lpFile = L"regsvr32";
		sei.lpParameters = szBuf;
		if (ShellExecuteEx(&sei) == TRUE)
			return;

		Sleep(1000);
	}
	RemoveCOMRegistryEntries();
}

// Wow, I can't believe there isn't a direct API for this - 'runas' will invoke the UAC and ask
// for permission before installing the shell extension.  note the filepath arg has to be quoted }
void CheckRegisterServer()
{
	wchar_t szFileName[MAX_PATH], szBuf[MAX_PATH * 2];

	HKEY hRegKey;
	if (!RegOpenKeyEx(HKEY_CLASSES_ROOT, L"miranda.shlext", 0, KEY_READ, &hRegKey))
		RegCloseKey(hRegKey);
	else if (bIsVistaPlus) {
		MessageBox(nullptr,
			TranslateT("Shell context menus requires your permission to register with Windows Explorer (one time only)."),
			TranslateT("Miranda NG - Shell context menus (shellext.dll)"), MB_OK | MB_ICONINFORMATION);
		// /s = silent
		GetModuleFileName(hInst, szFileName, _countof(szFileName));
		mir_snwprintf(szBuf, L"/s \"%s\"", szFileName);

		SHELLEXECUTEINFO sei = { sizeof(sei) };
		sei.lpVerb = L"runas";
		sei.lpFile = L"regsvr32";
		sei.lpParameters = szBuf;
		ShellExecuteEx(&sei);
	}
}