1 /* 2 * PROJECT: ReactOS Compatibility Layer Shell Extension 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: CLayerUIPropPage implementation 5 * COPYRIGHT: Copyright 2015-2017 Mark Jansen (mark.jansen@reactos.org) 6 */ 7 8 #include "precomp.h" 9 10 #include <shlwapi.h> 11 #include <shellapi.h> 12 #include <shellutils.h> 13 #include <strsafe.h> 14 #include <apphelp.h> 15 #include <windowsx.h> 16 #include <sfc.h> 17 18 const GUID CLSID_CLayerUIPropPage = { 0x513D916F, 0x2A8E, 0x4F51, { 0xAE, 0xAB, 0x0C, 0xBC, 0x76, 0xFB, 0x1A, 0xF8 } }; 19 #define ACP_WNDPROP L"{513D916F-2A8E-4F51-AEAB-0CBC76FB1AF8}.Prop" 20 21 #define GPLK_USER 1 22 #define GPLK_MACHINE 2 23 #define MAX_LAYER_LENGTH 256 24 25 static struct { 26 const PCWSTR Display; 27 const PCWSTR Name; 28 } g_CompatModes[] = { 29 { L"Windows 95", L"WIN95" }, 30 { L"Windows 98/ME", L"WIN98" }, 31 { L"Windows NT 4.0 (SP5)", L"NT4SP5" }, 32 { L"Windows 2000", L"WIN2000" }, 33 { L"Windows XP (SP2)", L"WINXPSP2" }, 34 { L"Windows XP (SP3)", L"WINXPSP3" }, 35 { L"Windows Server 2003 (SP1)", L"WINSRV03SP1" }, 36 #if 0 37 { L"Windows Server 2008 (SP1)", L"WINSRV08SP1" }, 38 { L"Windows Vista", L"VISTARTM" }, 39 { L"Windows Vista (SP1)", L"VISTASP1" }, 40 { L"Windows Vista (SP2)", L"VISTASP2" }, 41 { L"Windows 7", L"WIN7RTM" }, 42 #endif 43 { NULL, NULL } 44 }; 45 46 static struct { 47 const PCWSTR Name; 48 DWORD Id; 49 } g_Layers[] = { 50 { L"256COLOR", IDC_CHKRUNIN256COLORS }, 51 { L"640X480", IDC_CHKRUNIN640480RES }, 52 { L"DISABLETHEMES", IDC_CHKDISABLEVISUALTHEMES }, 53 #if 0 54 { L"DISABLEDWM", IDC_??, TRUE }, 55 { L"HIGHDPIAWARE", IDC_??, TRUE }, 56 { L"RUNASADMIN", IDC_??, TRUE }, 57 #endif 58 { NULL, 0 } 59 }; 60 61 static const WCHAR* g_AllowedExtensions[] = { 62 L".exe", 63 L".msi", 64 L".pif", 65 L".bat", 66 L".cmd", 67 0 68 }; 69 70 71 void ACDBG_FN(PCSTR FunctionName, PCWSTR Format, ...) 72 { 73 WCHAR Buffer[512]; 74 WCHAR* Current = Buffer; 75 size_t Length = _countof(Buffer); 76 77 StringCchPrintfExW(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, L"[%-20S] ", FunctionName); 78 va_list ArgList; 79 va_start(ArgList, Format); 80 StringCchVPrintfExW(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, Format, ArgList); 81 va_end(ArgList); 82 OutputDebugStringW(Buffer); 83 } 84 85 #define ACDBG(fmt, ...) ACDBG_FN(__FUNCTION__, fmt, ##__VA_ARGS__ ) 86 87 88 89 CLayerUIPropPage::CLayerUIPropPage() 90 : m_IsSfcProtected(FALSE) 91 , m_AllowPermLayer(FALSE) 92 , m_LayerQueryFlags(GPLK_USER) /* TODO: When do we read from HKLM? */ 93 , m_RegistryOSMode(0) 94 , m_OSMode(0) 95 , m_RegistryEnabledLayers(0) 96 , m_EnabledLayers(0) 97 { 98 } 99 100 CLayerUIPropPage::~CLayerUIPropPage() 101 { 102 } 103 104 HRESULT CLayerUIPropPage::InitFile(PCWSTR Filename) 105 { 106 CString ExpandedFilename; 107 DWORD dwRequired = ExpandEnvironmentStringsW(Filename, NULL, 0); 108 if (dwRequired > 0) 109 { 110 LPWSTR Buffer = ExpandedFilename.GetBuffer(dwRequired); 111 DWORD dwReturned = ExpandEnvironmentStringsW(Filename, Buffer, dwRequired); 112 if (dwRequired == dwReturned) 113 { 114 ExpandedFilename.ReleaseBufferSetLength(dwReturned - 1); 115 ACDBG(L"Expanded '%s' => '%s'\r\n", Filename, (PCWSTR)ExpandedFilename); 116 } 117 else 118 { 119 ExpandedFilename.ReleaseBufferSetLength(0); 120 ExpandedFilename = Filename; 121 ACDBG(L"Failed during expansion '%s'\r\n", Filename); 122 } 123 } 124 else 125 { 126 ACDBG(L"Failed to expand '%s'\r\n", Filename); 127 ExpandedFilename = Filename; 128 } 129 PCWSTR pwszExt = PathFindExtensionW(ExpandedFilename); 130 if (!pwszExt) 131 { 132 ACDBG(L"Failed to find an extension: '%s'\r\n", (PCWSTR)ExpandedFilename); 133 return E_FAIL; 134 } 135 if (!wcsicmp(pwszExt, L".lnk")) 136 { 137 WCHAR Buffer[MAX_PATH]; 138 if (!GetExeFromLnk(ExpandedFilename, Buffer, _countof(Buffer))) 139 { 140 ACDBG(L"Failed to read link target from: '%s'\r\n", (PCWSTR)ExpandedFilename); 141 return E_FAIL; 142 } 143 if (!wcsicmp(Buffer, ExpandedFilename)) 144 { 145 ACDBG(L"Link redirects to itself: '%s'\r\n", (PCWSTR)ExpandedFilename); 146 return E_FAIL; 147 } 148 return InitFile(Buffer); 149 } 150 151 CString tmp; 152 if (tmp.GetEnvironmentVariable(L"SystemRoot")) 153 { 154 tmp += L"\\System32"; 155 if (ExpandedFilename.GetLength() >= tmp.GetLength() && 156 ExpandedFilename.Left(tmp.GetLength()).MakeLower() == tmp.MakeLower()) 157 { 158 ACDBG(L"Ignoring System32: %s\r\n", (PCWSTR)ExpandedFilename); 159 return E_FAIL; 160 } 161 tmp.GetEnvironmentVariable(L"SystemRoot"); 162 tmp += L"\\WinSxs"; 163 if (ExpandedFilename.GetLength() >= tmp.GetLength() && 164 ExpandedFilename.Left(tmp.GetLength()).MakeLower() == tmp.MakeLower()) 165 { 166 ACDBG(L"Ignoring WinSxs: %s\r\n", (PCWSTR)ExpandedFilename); 167 return E_FAIL; 168 } 169 } 170 171 for (size_t n = 0; g_AllowedExtensions[n]; ++n) 172 { 173 if (!wcsicmp(g_AllowedExtensions[n], pwszExt)) 174 { 175 m_Filename = ExpandedFilename; 176 ACDBG(L"Got: %s\r\n", (PCWSTR)ExpandedFilename); 177 m_IsSfcProtected = SfcIsFileProtected(NULL, m_Filename); 178 m_AllowPermLayer = AllowPermLayer(ExpandedFilename); 179 return S_OK; 180 } 181 } 182 ACDBG(L"Extension not included: '%s'\r\n", pwszExt); 183 return E_FAIL; 184 } 185 186 static BOOL GetLayerInfo(PCWSTR Filename, DWORD QueryFlags, PDWORD OSMode, PDWORD Enabledlayers, CSimpleArray<CString>& customLayers) 187 { 188 WCHAR wszLayers[MAX_LAYER_LENGTH] = { 0 }; 189 DWORD dwBytes = sizeof(wszLayers); 190 191 *OSMode = *Enabledlayers = 0; 192 customLayers.RemoveAll(); 193 if (!SdbGetPermLayerKeys(Filename, wszLayers, &dwBytes, QueryFlags)) 194 return FALSE; 195 196 for (PWCHAR Layer = wcstok(wszLayers, L" "); Layer; Layer = wcstok(NULL, L" ")) 197 { 198 size_t n; 199 for (n = 0; g_Layers[n].Name; ++n) 200 { 201 if (!wcsicmp(g_Layers[n].Name, Layer)) 202 { 203 *Enabledlayers |= (1<<n); 204 break; 205 } 206 } 207 /* Did we find it? */ 208 if (g_Layers[n].Name) 209 continue; 210 211 for (n = 0; g_CompatModes[n].Name; ++n) 212 { 213 if (!wcsicmp(g_CompatModes[n].Name, Layer)) 214 { 215 *OSMode = n+1; 216 break; 217 } 218 } 219 /* Did we find it? */ 220 if (g_CompatModes[n].Name) 221 continue; 222 223 /* Must be a 'custom' layer */ 224 customLayers.Add(Layer); 225 } 226 return TRUE; 227 } 228 229 int CLayerUIPropPage::OnSetActive() 230 { 231 if (!GetLayerInfo(m_Filename, m_LayerQueryFlags, &m_RegistryOSMode, &m_RegistryEnabledLayers, m_RegistryCustomLayers)) 232 m_RegistryOSMode = m_RegistryEnabledLayers = 0; 233 234 for (size_t n = 0; g_Layers[n].Name; ++n) 235 CheckDlgButton(g_Layers[n].Id, (m_RegistryEnabledLayers & (1<<n)) ? BST_CHECKED : BST_UNCHECKED); 236 237 CheckDlgButton(IDC_CHKRUNCOMPATIBILITY, m_RegistryOSMode ? BST_CHECKED : BST_UNCHECKED); 238 239 if (m_RegistryOSMode) 240 ComboBox_SetCurSel(GetDlgItem(IDC_COMPATIBILITYMODE), m_RegistryOSMode-1); 241 242 m_CustomLayers = m_RegistryCustomLayers; 243 244 /* TODO: visualize 'custom' layers! */ 245 246 UpdateControls(); 247 248 return 0; 249 } 250 251 252 static BOOL ArrayEquals(const CSimpleArray<CString>& lhs, const CSimpleArray<CString>& rhs) 253 { 254 if (lhs.GetSize() != rhs.GetSize()) 255 return FALSE; 256 257 for (int n = 0; n < lhs.GetSize(); ++n) 258 { 259 if (lhs[n] != rhs[n]) 260 return FALSE; 261 } 262 return TRUE; 263 } 264 265 BOOL CLayerUIPropPage::HasChanges() const 266 { 267 if (m_RegistryEnabledLayers != m_EnabledLayers) 268 return TRUE; 269 270 if (m_RegistryOSMode != m_OSMode) 271 return TRUE; 272 273 if (!ArrayEquals(m_RegistryCustomLayers, m_CustomLayers)) 274 return TRUE; 275 276 return FALSE; 277 } 278 279 int CLayerUIPropPage::OnApply() 280 { 281 if (HasChanges()) 282 { 283 BOOL bMachine = m_LayerQueryFlags == GPLK_MACHINE; 284 285 for (size_t n = 0; g_CompatModes[n].Name; ++n) 286 SetPermLayerState(m_Filename, g_CompatModes[n].Name, 0, bMachine, (n+1) == m_OSMode); 287 288 for (size_t n = 0; g_Layers[n].Name; ++n) 289 { 290 SetPermLayerState(m_Filename, g_Layers[n].Name, 0, bMachine, ((1<<n) & m_EnabledLayers) != 0); 291 } 292 293 /* Disable all old values */ 294 for (int j = 0; j < m_RegistryCustomLayers.GetSize(); j++) 295 { 296 SetPermLayerState(m_Filename, m_RegistryCustomLayers[j].GetString(), 0, bMachine, FALSE); 297 } 298 299 /* Enable all new values */ 300 for (int j = 0; j < m_CustomLayers.GetSize(); j++) 301 { 302 SetPermLayerState(m_Filename, m_CustomLayers[j].GetString(), 0, bMachine, TRUE); 303 } 304 305 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, (PCWSTR)m_Filename, NULL); 306 } 307 308 return PSNRET_NOERROR; 309 } 310 311 LRESULT CLayerUIPropPage::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 312 { 313 HWND cboMode = GetDlgItem(IDC_COMPATIBILITYMODE); 314 for (size_t n = 0; g_CompatModes[n].Display; ++n) 315 ComboBox_AddString(cboMode, g_CompatModes[n].Display); 316 ComboBox_SetCurSel(cboMode, 5); 317 318 CComBSTR explanation; 319 if (!m_AllowPermLayer) 320 { 321 explanation.LoadString(g_hModule, IDS_FAILED_NETWORK); 322 DisableControls(); 323 ACDBG(L"AllowPermLayer returned FALSE\r\n"); 324 } 325 else if (m_IsSfcProtected) 326 { 327 explanation.LoadString(g_hModule, IDS_FAILED_PROTECTED); 328 DisableControls(); 329 ACDBG(L"Protected OS file\r\n"); 330 } 331 else 332 { 333 return TRUE; 334 } 335 SetDlgItemTextW(IDC_EXPLANATION, explanation); 336 return TRUE; 337 } 338 339 INT_PTR CLayerUIPropPage::DisableControls() 340 { 341 ::EnableWindow(GetDlgItem(IDC_COMPATIBILITYMODE), 0); 342 ::EnableWindow(GetDlgItem(IDC_CHKRUNCOMPATIBILITY), 0); 343 for (size_t n = 0; g_Layers[n].Name; ++n) 344 ::EnableWindow(GetDlgItem(g_Layers[n].Id), 0); 345 ::EnableWindow(GetDlgItem(IDC_EDITCOMPATIBILITYMODES), 0); 346 return TRUE; 347 } 348 349 void CLayerUIPropPage::UpdateControls() 350 { 351 m_OSMode = 0, m_EnabledLayers = 0; 352 BOOL ModeEnabled = IsDlgButtonChecked(IDC_CHKRUNCOMPATIBILITY); 353 if (ModeEnabled) 354 m_OSMode = ComboBox_GetCurSel(GetDlgItem(IDC_COMPATIBILITYMODE))+1; 355 ::EnableWindow(GetDlgItem(IDC_COMPATIBILITYMODE), ModeEnabled); 356 357 for (size_t n = 0; g_Layers[n].Name; ++n) 358 { 359 m_EnabledLayers |= IsDlgButtonChecked(g_Layers[n].Id) ? (1<<n) : 0; 360 ::ShowWindow(GetDlgItem(g_Layers[n].Id), SW_SHOW); 361 } 362 363 SetModified(HasChanges()); 364 } 365 366 LRESULT CLayerUIPropPage::OnCtrlCommand(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled) 367 { 368 UpdateControls(); 369 return 0; 370 } 371 372 LRESULT CLayerUIPropPage::OnEditModes(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled) 373 { 374 if (DialogBoxParam(g_hModule, MAKEINTRESOURCE(IDD_EDITCOMPATIBILITYMODES), m_hWnd, EditModesProc, (LPARAM)this) == IDOK) 375 UpdateControls(); 376 return 0; 377 } 378 379 LRESULT CLayerUIPropPage::OnClickNotify(INT uCode, LPNMHDR hdr, BOOL& bHandled) 380 { 381 if (hdr->idFrom == IDC_INFOLINK) 382 ShellExecute(NULL, L"open", L"https://www.reactos.org/forum/viewforum.php?f=4", NULL, NULL, SW_SHOW); 383 return 0; 384 } 385 386 static void ListboxChanged(HWND hWnd) 387 { 388 int Sel = ListBox_GetCurSel(GetDlgItem(hWnd, IDC_COMPATIBILITYMODE)); 389 EnableWindow(GetDlgItem(hWnd, IDC_EDIT), Sel >= 0); 390 EnableWindow(GetDlgItem(hWnd, IDC_DELETE), Sel >= 0); 391 } 392 393 static void OnAdd(HWND hWnd) 394 { 395 HWND Combo = GetDlgItem(hWnd, IDC_NEWCOMPATIBILITYMODE); 396 397 int Length = ComboBox_GetTextLength(Combo); 398 CComBSTR Str(Length); 399 ComboBox_GetText(Combo, Str, Length+1); 400 HWND List = GetDlgItem(hWnd, IDC_COMPATIBILITYMODE); 401 int Index = ListBox_FindStringExact(List, -1, Str); 402 if (Index == LB_ERR) 403 Index = ListBox_AddString(List, Str); 404 ListBox_SetCurSel(List, Index); 405 ListboxChanged(hWnd); 406 ComboBox_SetCurSel(Combo, -1); 407 SetFocus(Combo); 408 } 409 410 static BOOL ComboHasData(HWND hWnd) 411 { 412 HWND Combo = GetDlgItem(hWnd, IDC_NEWCOMPATIBILITYMODE); 413 if (ComboBox_GetCurSel(Combo) >= 0) 414 return TRUE; 415 ULONG Len = ComboBox_GetTextLength(Combo); 416 return Len > 0; 417 } 418 419 INT_PTR CALLBACK CLayerUIPropPage::EditModesProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 420 { 421 CLayerUIPropPage* page = NULL; 422 423 switch (uMsg) 424 { 425 case WM_INITDIALOG: 426 page = (CLayerUIPropPage*)lParam; 427 page->AddRef(); 428 ::SetProp(hWnd, ACP_WNDPROP, page); 429 { 430 HWND Combo = ::GetDlgItem(hWnd, IDC_NEWCOMPATIBILITYMODE); 431 CComObject<CLayerStringList> pList; 432 433 while (TRUE) 434 { 435 CComHeapPtr<OLECHAR> str; 436 HRESULT hr = pList.Next(1, &str, NULL); 437 if (hr != S_OK) 438 break; 439 ComboBox_AddString(Combo, str); 440 } 441 442 HWND List = ::GetDlgItem(hWnd, IDC_COMPATIBILITYMODE); 443 for (int n = 0; n < page->m_CustomLayers.GetSize(); ++n) 444 { 445 const WCHAR* Str = page->m_CustomLayers[n].GetString(); 446 int Index = ListBox_FindStringExact(List, -1, Str); 447 if (Index == LB_ERR) 448 Index = ListBox_AddString(List, Str); 449 } 450 } 451 break; 452 case WM_ENDSESSION: 453 case WM_DESTROY: 454 page = (CLayerUIPropPage*)::GetProp(hWnd, ACP_WNDPROP); 455 ::RemoveProp(hWnd, ACP_WNDPROP); 456 page->Release(); 457 break; 458 459 case WM_COMMAND: 460 switch(LOWORD(wParam)) 461 { 462 case IDC_ADD: 463 OnAdd(hWnd); 464 break; 465 case IDC_EDIT: 466 { 467 HWND List = ::GetDlgItem(hWnd, IDC_COMPATIBILITYMODE); 468 int Cur = ListBox_GetCurSel(List); 469 int Length = ListBox_GetTextLen(List, Cur); 470 CComBSTR Str(Length); 471 ListBox_GetText(List, Cur, Str); 472 ListBox_DeleteString(List, Cur); 473 HWND Combo = ::GetDlgItem(hWnd, IDC_NEWCOMPATIBILITYMODE); 474 ComboBox_SetCurSel(Combo, -1); 475 ::SetWindowText(Combo,Str); 476 ListboxChanged(hWnd); 477 ComboBox_SetEditSel(Combo, 30000, 30000); 478 ::SetFocus(Combo); 479 } 480 break; 481 case IDC_DELETE: 482 { 483 HWND List = ::GetDlgItem(hWnd, IDC_COMPATIBILITYMODE); 484 ListBox_DeleteString(List, ListBox_GetCurSel(List)); 485 ListboxChanged(hWnd); 486 } 487 break; 488 case IDC_COMPATIBILITYMODE: 489 ListboxChanged(hWnd); 490 break; 491 case IDC_NEWCOMPATIBILITYMODE: 492 { 493 ::EnableWindow(::GetDlgItem(hWnd, IDC_ADD), ComboHasData(hWnd)); 494 } 495 break; 496 case IDOK: 497 /* Copy from list! */ 498 { 499 if (ComboHasData(hWnd)) 500 { 501 CComBSTR question, title; 502 title.LoadString(g_hModule, IDS_TABTITLE); 503 question.LoadString(g_hModule, IDS_YOU_DID_NOT_ADD); 504 int result = ::MessageBoxW(hWnd, question, title, MB_YESNOCANCEL | MB_ICONQUESTION); 505 switch (result) 506 { 507 case IDYES: 508 OnAdd(hWnd); 509 break; 510 case IDNO: 511 break; 512 case IDCANCEL: 513 return FALSE; 514 } 515 } 516 517 page = (CLayerUIPropPage*)::GetProp(hWnd, ACP_WNDPROP); 518 519 HWND List = ::GetDlgItem(hWnd, IDC_COMPATIBILITYMODE); 520 int Count = ListBox_GetCount(List); 521 page->m_CustomLayers.RemoveAll(); 522 for (int Cur = 0; Cur < Count; ++Cur) 523 { 524 int Length = ListBox_GetTextLen(List, Cur); 525 CString Str; 526 LPWSTR Buffer = Str.GetBuffer(Length + 1); 527 ListBox_GetText(List, Cur, Buffer); 528 Str.ReleaseBuffer(Length); 529 page->m_CustomLayers.Add(Str); 530 } 531 } 532 /* Fall trough */ 533 case IDCANCEL: 534 ::EndDialog(hWnd, LOWORD(wParam)); 535 break; 536 } 537 break; 538 case WM_CLOSE: 539 ::EndDialog(hWnd, IDCANCEL); 540 break; 541 } 542 return FALSE; 543 } 544 545 static BOOL DisableShellext() 546 { 547 HKEY hkey; 548 LSTATUS ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Policies\\Microsoft\\Windows\\AppCompat", 0, KEY_QUERY_VALUE, &hkey); 549 BOOL Disable = FALSE; 550 if (ret == ERROR_SUCCESS) 551 { 552 DWORD dwValue = 0; 553 DWORD type, size = sizeof(dwValue); 554 ret = RegQueryValueExW(hkey, L"DisableEngine", NULL, &type, (PBYTE)&dwValue, &size); 555 if (ret == ERROR_SUCCESS && type == REG_DWORD) 556 { 557 Disable = !!dwValue; 558 } 559 if (!Disable) 560 { 561 size = sizeof(dwValue); 562 ret = RegQueryValueExW(hkey, L"DisablePropPage", NULL, &type, (PBYTE)&dwValue, &size); 563 if (ret == ERROR_SUCCESS && type == REG_DWORD) 564 { 565 Disable = !!dwValue; 566 } 567 } 568 569 RegCloseKey(hkey); 570 } 571 return Disable; 572 } 573 574 STDMETHODIMP CLayerUIPropPage::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hkeyProgID) 575 { 576 FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; 577 STGMEDIUM stg; 578 579 if (DisableShellext()) 580 return E_ACCESSDENIED; 581 582 HRESULT hr = pDataObj->GetData(&etc, &stg); 583 if (FAILED(hr)) 584 { 585 ACDBG(L"Failed to retrieve Data from pDataObj.\r\n"); 586 return E_INVALIDARG; 587 } 588 hr = E_FAIL; 589 HDROP hdrop = (HDROP)GlobalLock(stg.hGlobal); 590 if (hdrop) 591 { 592 UINT uNumFiles = DragQueryFileW(hdrop, 0xFFFFFFFF, NULL, 0); 593 if (uNumFiles == 1) 594 { 595 WCHAR szFile[MAX_PATH * 2]; 596 if (DragQueryFileW(hdrop, 0, szFile, _countof(szFile))) 597 { 598 this->AddRef(); 599 hr = InitFile(szFile); 600 } 601 else 602 { 603 ACDBG(L"Failed to query the file.\r\n"); 604 } 605 } 606 else 607 { 608 ACDBG(L"Invalid number of files: %d\r\n", uNumFiles); 609 } 610 GlobalUnlock(stg.hGlobal); 611 } 612 else 613 { 614 ACDBG(L"Could not lock stg.hGlobal\r\n"); 615 } 616 ReleaseStgMedium(&stg); 617 return hr; 618 } 619