Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

805 lines
23 KiB

  1. #include "precomp.h"
  2. IMPLEMENT_SHIM_BEGIN(TSPerUserFiles)
  3. #include "ShimHookMacro.h"
  4. #include <regapi.h>
  5. #include <stdio.h>
  6. #include "TSPerUserFiles_utils.h"
  7. HKEY HKLM = NULL;
  8. //Functions - helpers.
  9. DWORD RegKeyOpen(IN HKEY hKeyParent, IN LPCWSTR szKeyName, IN REGSAM samDesired, OUT HKEY *phKey );
  10. DWORD RegLoadDWORD(IN HKEY hKey, IN LPCWSTR szValueName, OUT DWORD *pdwValue);
  11. DWORD RegGetKeyInfo(IN HKEY hKey, OUT LPDWORD pcValues, OUT LPDWORD pcbMaxValueNameLen);
  12. DWORD RegKeyEnumValues(IN HKEY hKey, IN DWORD iValue, OUT LPWSTR *pwszValueName, OUT LPBYTE *ppbData);
  13. ///////////////////////////////////////////////////////////////////////////////
  14. //struct PER_USER_PATH
  15. ///////////////////////////////////////////////////////////////////////////////
  16. /******************************************************************************
  17. Routine Description:
  18. Loads wszFile and wszPerUserDir values from the registry;
  19. Translates them to ANSI and saves results in szFile and szPerUserDir
  20. If no wildcards used - constructs wszPerUserFile from
  21. wszPerUserDir, and wszFile and szPerUserFile from
  22. szPerUserDir and szFile
  23. Arguments:
  24. IN HKEY hKey - key to load values from.
  25. IN DWORD dwIndex - index of value
  26. Return Value:
  27. error code.
  28. Note:
  29. Name of a registry value is loaded into wszFile;
  30. Data is loaded into wszPerUserPath
  31. ******************************************************************************/
  32. DWORD
  33. PER_USER_PATH::Init(
  34. IN HKEY hKey,
  35. IN DWORD dwIndex)
  36. {
  37. DWORD err = RegKeyEnumValues(hKey, dwIndex, &wszFile,
  38. (LPBYTE *)&wszPerUserDir );
  39. if(err != ERROR_SUCCESS)
  40. {
  41. return err;
  42. }
  43. cFileLen = wcslen(wszFile);
  44. cPerUserDirLen = wcslen(wszPerUserDir);
  45. //Check if there is a '*' instead of file name.
  46. //'*' it stands for any file name not encluding extension
  47. long i;
  48. for(i=cFileLen-1; i>=0 && wszFile[i] !=L'\\'; i--)
  49. {
  50. if(wszFile[i] == L'*')
  51. {
  52. bWildCardUsed = TRUE;
  53. break;
  54. }
  55. }
  56. //there must be at least one '\\' in wszFile
  57. //and it cannot be a first character
  58. if(i<=0)
  59. {
  60. return ERROR_INVALID_PARAMETER;
  61. }
  62. InitANSI();
  63. if(!bWildCardUsed)
  64. {
  65. //now wszFile+i points to '\\' in wszFile
  66. //Let's construct wszPerUserFile from
  67. //wszPerUserDir and wszFile
  68. DWORD cPerUserFile = (wcslen(wszPerUserDir)+cFileLen-i)+1;
  69. wszPerUserFile=(LPWSTR) LocalAlloc(LPTR,cPerUserFile*sizeof(WCHAR));
  70. if(!wszPerUserFile)
  71. {
  72. return ERROR_NOT_ENOUGH_MEMORY;
  73. }
  74. wcscpy(wszPerUserFile,wszPerUserDir);
  75. wcscat(wszPerUserFile,wszFile+i);
  76. if(!bInitANSIFailed)
  77. {
  78. szPerUserFile=(LPSTR) LocalAlloc(LPTR,cPerUserFile);
  79. if(!szPerUserFile)
  80. {
  81. return ERROR_NOT_ENOUGH_MEMORY;
  82. }
  83. strcpy(szPerUserFile,szPerUserDir);
  84. strcat(szPerUserFile,szFile+i);
  85. }
  86. }
  87. return ERROR_SUCCESS;
  88. }
  89. /******************************************************************************
  90. Routine Description:
  91. creates szFile and szPerUserPathTemplate from
  92. wszFile and wszPerUserPathTemplate strings
  93. Arguments:
  94. NONE
  95. Return Value:
  96. TRUE if success
  97. ******************************************************************************/
  98. BOOL
  99. PER_USER_PATH::InitANSI()
  100. {
  101. //we already tried and failed
  102. //don't try again
  103. if(bInitANSIFailed)
  104. {
  105. return FALSE;
  106. }
  107. if(!szFile)
  108. {
  109. //allocate memory for ANSI strings
  110. DWORD cFile = cFileLen+1;
  111. szFile = (LPSTR) LocalAlloc(LPTR,cFile);
  112. if(!szFile)
  113. {
  114. bInitANSIFailed = TRUE;
  115. return FALSE;
  116. }
  117. DWORD cPerUserDir = cPerUserDirLen+1;
  118. szPerUserDir = (LPSTR) LocalAlloc(LPTR,cPerUserDir);
  119. if(!szPerUserDir)
  120. {
  121. bInitANSIFailed = TRUE;
  122. return FALSE;
  123. }
  124. //convert UNICODE wszFile and wszPerUserPath to ANSI
  125. if(!WideCharToMultiByte(
  126. CP_ACP, // code page
  127. 0, // performance and mapping flags
  128. wszFile, // wide-character string
  129. -1, // number of chars in string
  130. szFile, // buffer for new string
  131. cFile, // size of buffer
  132. NULL, // default for unmappable chars
  133. NULL // set when default char used
  134. ))
  135. {
  136. bInitANSIFailed = TRUE;
  137. return FALSE;
  138. }
  139. if(!WideCharToMultiByte(
  140. CP_ACP, // code page
  141. 0, // performance and mapping flags
  142. wszPerUserDir, // wide-character string
  143. -1, // number of chars in string
  144. szPerUserDir, // buffer for new string
  145. cPerUserDir, // size of buffer
  146. NULL, // default for unmappable chars
  147. NULL // set when default char used
  148. ))
  149. {
  150. bInitANSIFailed = TRUE;
  151. return FALSE;
  152. }
  153. }
  154. return TRUE;
  155. }
  156. /******************************************************************************
  157. Routine Description:
  158. returns szPerUserPath if szInFile is equal to szFile.
  159. Arguments:
  160. IN LPCSTR szInFile - original path
  161. IN DWORD cInLen - length of szInFile in characters
  162. Return Value:
  163. szPerUserPath or NULL if failes
  164. ******************************************************************************/
  165. LPCSTR
  166. PER_USER_PATH::PathForFileA(
  167. IN LPCSTR szInFile,
  168. IN DWORD cInLen)
  169. {
  170. if(bInitANSIFailed)
  171. {
  172. return NULL;
  173. }
  174. long j, i, k;
  175. if(bWildCardUsed)
  176. {
  177. //
  178. if(cInLen < cFileLen)
  179. {
  180. return NULL;
  181. }
  182. //if * is used, we need a special algorithm
  183. //the end of the path will more likely be different.
  184. //so start comparing from the end.
  185. for(j=cInLen-1, i=cFileLen-1; i>=0 && j>=0; i--, j--)
  186. {
  187. if(szFile[i] == '*')
  188. {
  189. i--;
  190. if(i<0 || szFile[i]!='\\')
  191. {
  192. //the string in the registry was garbage
  193. //the symbol previous to '*' must be '\\'
  194. return NULL;
  195. }
  196. //skip all symbols in szInFile till next '\\'
  197. while(j>=0 && szInFile[j]!='\\') j--;
  198. //At this point both strings must me of exactly the same size.
  199. //If not - they are not equal
  200. if(j!=i)
  201. {
  202. return NULL;
  203. }
  204. //no need to compare,
  205. //we already know that current symbols are equal
  206. break;
  207. }
  208. if(tolower(szFile[i])!=tolower(szInFile[j]))
  209. {
  210. return NULL;
  211. }
  212. }
  213. //i and j are equal now.
  214. //no more * allowed
  215. //j we will remember as a position of '\\'
  216. for(k=j-1; k>=0; k--)
  217. {
  218. if(tolower(szFile[k])!=tolower(szInFile[k]))
  219. {
  220. return NULL;
  221. }
  222. }
  223. //Okay, the strings are equal
  224. //Now construct the output string
  225. if(szPerUserFile)
  226. {
  227. LocalFree(szPerUserFile);
  228. szPerUserFile=NULL;
  229. }
  230. DWORD cPerUserFile = cPerUserDirLen + cInLen - j + 1;
  231. szPerUserFile = (LPSTR) LocalAlloc(LPTR,cPerUserFile);
  232. if(!szPerUserFile)
  233. {
  234. return NULL;
  235. }
  236. sprintf(szPerUserFile,"%s%s",szPerUserDir,szInFile+j);
  237. }
  238. else
  239. {
  240. //first find if the input string has right size
  241. if(cInLen != cFileLen)
  242. {
  243. return NULL;
  244. }
  245. //the end of the path will more likely be different.
  246. //so start comparing from the end.
  247. for(i=cFileLen-1; i>=0; i--)
  248. {
  249. if(tolower(szFile[i])!=tolower(szInFile[i]))
  250. {
  251. return NULL;
  252. }
  253. }
  254. }
  255. return szPerUserFile;
  256. }
  257. /******************************************************************************
  258. Routine Description:
  259. returns wszPerUserPath if wszInFile is equal to szFile.
  260. Arguments:
  261. IN LPCSTR wszInFile - original path
  262. IN DWORD cInLen - length of wszInFile in characters
  263. Return Value:
  264. wszPerUserPath or NULL if failes
  265. ******************************************************************************/
  266. LPCWSTR
  267. PER_USER_PATH::PathForFileW(
  268. IN LPCWSTR wszInFile,
  269. IN DWORD cInLen)
  270. {
  271. long j, i, k;
  272. if(bWildCardUsed)
  273. {
  274. //
  275. if(cInLen < cFileLen)
  276. {
  277. return NULL;
  278. }
  279. //if * is used, we need a special algorithm
  280. //the end of the path will more likely be different.
  281. //so start comparing from the end.
  282. for(j=cInLen-1, i=cFileLen-1; i>=0 && j>=0; i--, j--)
  283. {
  284. if(wszFile[i] == '*')
  285. {
  286. i--;
  287. if(i<0 || wszFile[i]!='\\')
  288. {
  289. //the string in the registry was garbage
  290. //the symbol previous to '*' must be '\\'
  291. return NULL;
  292. }
  293. //skip all symbols in szInFile till next '\\'
  294. while(j>=0 && wszInFile[j]!='\\') j--;
  295. //At this point both strings must me of exactly the same size.
  296. //If not - they are not equal
  297. if(j!=i)
  298. {
  299. return NULL;
  300. }
  301. //no need to compare,
  302. //we already know that current symbols are equal
  303. break;
  304. }
  305. if(towlower(wszFile[i])!=towlower(wszInFile[j]))
  306. {
  307. return NULL;
  308. }
  309. }
  310. //i and j are equal now.
  311. //no more * allowed
  312. //j we will remember as a position of '\\'
  313. for(k=j; k>=0; k--)
  314. {
  315. if(towlower(wszFile[k])!=towlower(wszInFile[k]))
  316. {
  317. return NULL;
  318. }
  319. }
  320. //Okay, the strings are equal
  321. //Now construct the output string
  322. if(wszPerUserFile)
  323. {
  324. LocalFree(wszPerUserFile);
  325. wszPerUserFile=NULL;
  326. }
  327. DWORD cPerUserFile = cPerUserDirLen + cInLen - j + 1;
  328. wszPerUserFile = (LPWSTR) LocalAlloc(LPTR,cPerUserFile*sizeof(WCHAR));
  329. if(!wszPerUserFile)
  330. {
  331. return NULL;
  332. }
  333. swprintf(wszPerUserFile,L"%s%s",wszPerUserDir,wszInFile+j);
  334. }
  335. else
  336. {
  337. //first find if the input string has right size
  338. if(cInLen != cFileLen)
  339. {
  340. return NULL;
  341. }
  342. //the end of the path will more likely be different.
  343. //so start comparing from the end.
  344. for(i=cFileLen-1; i>=0; i--)
  345. {
  346. if(towlower(wszFile[i])!=towlower(wszInFile[i]))
  347. {
  348. return NULL;
  349. }
  350. }
  351. }
  352. return wszPerUserFile;
  353. }
  354. ///////////////////////////////////////////////////////////////////////////////
  355. //class CPerUserPaths
  356. ///////////////////////////////////////////////////////////////////////////////
  357. CPerUserPaths::CPerUserPaths():
  358. m_pPaths(NULL), m_cPaths(0)
  359. {
  360. }
  361. CPerUserPaths::~CPerUserPaths()
  362. {
  363. if(m_pPaths)
  364. {
  365. delete[] m_pPaths;
  366. }
  367. if(HKLM)
  368. {
  369. NtClose(HKLM);
  370. HKLM = NULL;
  371. }
  372. }
  373. /******************************************************************************
  374. Routine Description:
  375. Loads from the registry (HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\
  376. Terminal Server\\Compatibility\\PerUserFiles\\<Executable name>) information of files
  377. that need to be redirected to per-user directory.
  378. Arguments:
  379. NONE
  380. Return Value:
  381. TRUE if success
  382. ******************************************************************************/
  383. BOOL
  384. CPerUserPaths::Init()
  385. {
  386. //Get the name of the current executable.
  387. LPCSTR szModule = COMMAND_LINE; //command line is CHAR[] so szModule needs to be CHAR too.
  388. DPF("TSPerUserFiles",eDbgLevelInfo," - App Name: %s\n",szModule);
  389. //Open HKLM for later use
  390. if(!HKLM)
  391. {
  392. if(RegKeyOpen(NULL, L"\\Registry\\Machine", KEY_READ, &HKLM )!=ERROR_SUCCESS)
  393. {
  394. DPF("TSPerUserFiles",eDbgLevelError," - FAILED: Cannot open HKLM!\n");
  395. return FALSE;
  396. }
  397. }
  398. //Check if TS App Compat is on.
  399. if(!IsAppCompatOn())
  400. {
  401. DPF("TSPerUserFiles",eDbgLevelError," - FAILED: TS App Compat is off!\n");
  402. return FALSE;
  403. }
  404. //Get files we need to redirect.
  405. DWORD err;
  406. HKEY hKey;
  407. WCHAR szKeyNameTemplate[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion"
  408. L"\\Terminal Server\\Compatibility\\PerUserFiles\\%S";
  409. LPWSTR szKeyName = (LPWSTR) LocalAlloc(LPTR,
  410. sizeof(szKeyNameTemplate)+strlen(szModule)*sizeof(WCHAR));
  411. if(!szKeyName)
  412. {
  413. DPF("TSPerUserFiles",eDbgLevelError," - FAILED: cannot allocate key name\n");
  414. return FALSE;
  415. }
  416. swprintf(szKeyName,szKeyNameTemplate,szModule);
  417. err = RegKeyOpen(HKLM, szKeyName, KEY_QUERY_VALUE, &hKey );
  418. LocalFree(szKeyName);
  419. if(err == ERROR_SUCCESS)
  420. {
  421. err = RegGetKeyInfo(hKey, &m_cPaths, NULL);
  422. if(err == ERROR_SUCCESS)
  423. {
  424. DPF("TSPerUserFiles",eDbgLevelInfo," - %d file(s) need to be redirected\n",m_cPaths);
  425. //Allocate array of PER_USER_PATH structs
  426. m_pPaths = new PER_USER_PATH[m_cPaths];
  427. if(!m_pPaths)
  428. {
  429. NtClose(hKey);
  430. return FALSE;
  431. }
  432. for(DWORD i=0;i<m_cPaths;i++)
  433. {
  434. err = m_pPaths[i].Init(hKey,i);
  435. if(err != ERROR_SUCCESS)
  436. {
  437. DPF("TSPerUserFiles",eDbgLevelError," - FAILED: cannot load filenames from registry\n");
  438. break;
  439. }
  440. }
  441. }
  442. NtClose(hKey);
  443. }
  444. if(err != ERROR_SUCCESS)
  445. {
  446. return FALSE;
  447. }
  448. else
  449. {
  450. return TRUE;
  451. }
  452. }
  453. /******************************************************************************
  454. Routine Description:
  455. redirects file path to per-user directory if necessary
  456. Arguments:
  457. IN LPCSTR lpFileName
  458. Return Value:
  459. full path to the per user file if redirected;
  460. the same as lpFileName if not.
  461. ******************************************************************************/
  462. LPCSTR
  463. CPerUserPaths::GetPerUserPathA(
  464. IN LPCSTR lpFileName)
  465. {
  466. LPCSTR szPerUserPath = NULL;
  467. DWORD cFileLen = strlen(lpFileName);
  468. for(DWORD i=0; i<m_cPaths; i++)
  469. {
  470. szPerUserPath = m_pPaths[i].PathForFileA(lpFileName, cFileLen);
  471. if(szPerUserPath)
  472. {
  473. DPF("TSPerUserFiles",eDbgLevelInfo," - redirecting %s\n to %s\n",
  474. lpFileName,szPerUserPath);
  475. return szPerUserPath;
  476. }
  477. }
  478. return lpFileName;
  479. }
  480. /******************************************************************************
  481. Routine Description:
  482. redirects file path to per-user directory if necessary
  483. Arguments:
  484. IN LPCWSTR lpFileName
  485. Return Value:
  486. full path to the per user file if redirected;
  487. the same as lpFileName if not.
  488. ******************************************************************************/
  489. LPCWSTR
  490. CPerUserPaths::GetPerUserPathW(
  491. IN LPCWSTR lpFileName)
  492. {
  493. LPCWSTR szPerUserPath = NULL;
  494. DWORD cFileLen = wcslen(lpFileName);
  495. for(DWORD i=0; i<m_cPaths; i++)
  496. {
  497. szPerUserPath = m_pPaths[i].PathForFileW(lpFileName, cFileLen);
  498. if(szPerUserPath)
  499. {
  500. DPF("TSPerUserFiles",eDbgLevelInfo," - redirecting %S\n to %S\n",
  501. lpFileName,szPerUserPath);
  502. return szPerUserPath;
  503. }
  504. }
  505. return lpFileName;
  506. }
  507. /******************************************************************************
  508. Routine Description:
  509. Checks if TS application compatibility on
  510. Arguments:
  511. NONE
  512. Return Value:
  513. In case of any error - returns FALSE
  514. ******************************************************************************/
  515. BOOL
  516. CPerUserPaths::IsAppCompatOn()
  517. {
  518. HKEY hKey;
  519. DWORD dwData = 0;
  520. BOOL fResult = FALSE;
  521. if( RegKeyOpen(HKLM,
  522. REG_CONTROL_TSERVER,
  523. KEY_QUERY_VALUE,
  524. &hKey) == ERROR_SUCCESS )
  525. {
  526. if(RegLoadDWORD(hKey, L"TSAppCompat", &dwData) == ERROR_SUCCESS )
  527. {
  528. DPF("TSPerUserFiles",eDbgLevelInfo," - IsAppCompatOn() - OK; Result=%d\n",dwData);
  529. fResult = (dwData!=0);
  530. }
  531. NtClose(hKey);
  532. }
  533. return fResult;
  534. }
  535. ///////////////////////////////////////////////////////////////////////////////
  536. //Functions - helpers.
  537. ///////////////////////////////////////////////////////////////////////////////
  538. /******************************************************************************
  539. Opens Registry key
  540. ******************************************************************************/
  541. DWORD
  542. RegKeyOpen(
  543. IN HKEY hKeyParent,
  544. IN LPCWSTR szKeyName,
  545. IN REGSAM samDesired,
  546. OUT HKEY *phKey )
  547. {
  548. NTSTATUS Status;
  549. UNICODE_STRING UnicodeString;
  550. OBJECT_ATTRIBUTES OA;
  551. RtlInitUnicodeString(&UnicodeString, szKeyName);
  552. InitializeObjectAttributes(&OA, &UnicodeString, OBJ_CASE_INSENSITIVE, hKeyParent, NULL);
  553. Status = NtOpenKey((PHANDLE)phKey, samDesired, &OA);
  554. return RtlNtStatusToDosError( Status );
  555. }
  556. /******************************************************************************
  557. Loads a REG_DWORD value from the registry
  558. ******************************************************************************/
  559. DWORD
  560. RegLoadDWORD(
  561. IN HKEY hKey,
  562. IN LPCWSTR szValueName,
  563. OUT LPDWORD pdwValue)
  564. {
  565. NTSTATUS Status;
  566. BYTE Buf[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(DWORD)];
  567. PKEY_VALUE_PARTIAL_INFORMATION pValInfo = (PKEY_VALUE_PARTIAL_INFORMATION)&Buf[0];
  568. DWORD cbData = sizeof(Buf);
  569. UNICODE_STRING ValueString;
  570. RtlInitUnicodeString(&ValueString, szValueName);
  571. Status = NtQueryValueKey(hKey,
  572. &ValueString,
  573. KeyValuePartialInformation,
  574. pValInfo,
  575. cbData,
  576. &cbData);
  577. if (NT_SUCCESS(Status))
  578. {
  579. *pdwValue = *((PDWORD)pValInfo->Data);
  580. }
  581. return RtlNtStatusToDosError( Status );
  582. }
  583. /******************************************************************************
  584. Get key's number of values and max svalue name length
  585. ******************************************************************************/
  586. DWORD
  587. RegGetKeyInfo(
  588. IN HKEY hKey,
  589. OUT LPDWORD pcValues,
  590. OUT LPDWORD pcbMaxValueNameLen)
  591. {
  592. NTSTATUS Status;
  593. KEY_CACHED_INFORMATION KeyInfo;
  594. DWORD dwRead;
  595. Status = NtQueryKey(
  596. hKey,
  597. KeyCachedInformation,
  598. &KeyInfo,
  599. sizeof(KeyInfo),
  600. &dwRead);
  601. if (NT_SUCCESS(Status))
  602. {
  603. if(pcValues)
  604. {
  605. *pcValues = KeyInfo.Values;
  606. }
  607. if(pcbMaxValueNameLen)
  608. {
  609. *pcbMaxValueNameLen = KeyInfo.MaxValueNameLen;
  610. }
  611. }
  612. return RtlNtStatusToDosError( Status );
  613. }
  614. /******************************************************************************
  615. Enumerates values of the registry key
  616. Returns name and data for one value at a time
  617. ******************************************************************************/
  618. DWORD
  619. RegKeyEnumValues(
  620. IN HKEY hKey,
  621. IN DWORD iValue,
  622. OUT LPWSTR *pwszValueName,
  623. OUT LPBYTE *ppbData)
  624. {
  625. KEY_VALUE_FULL_INFORMATION viValue, *pviValue;
  626. ULONG dwActualLength;
  627. NTSTATUS Status = STATUS_SUCCESS;
  628. DWORD err = ERROR_SUCCESS;
  629. *pwszValueName = NULL;
  630. *ppbData = NULL;
  631. pviValue = &viValue;
  632. Status = NtEnumerateValueKey(
  633. hKey,
  634. iValue,
  635. KeyValueFullInformation,
  636. pviValue,
  637. sizeof(KEY_VALUE_FULL_INFORMATION),
  638. &dwActualLength);
  639. if (Status == STATUS_BUFFER_OVERFLOW)
  640. {
  641. //
  642. // Our default buffer of KEY_VALUE_FULL_INFORMATION size didn't quite cut it
  643. // Forced to allocate from heap and make call again.
  644. //
  645. pviValue = (KEY_VALUE_FULL_INFORMATION *) LocalAlloc(LPTR, dwActualLength);
  646. if (!pviValue) {
  647. return GetLastError();
  648. }
  649. Status = NtEnumerateValueKey(
  650. hKey,
  651. iValue,
  652. KeyValueFullInformation,
  653. pviValue,
  654. dwActualLength,
  655. &dwActualLength);
  656. }
  657. if (NT_SUCCESS(Status))
  658. {
  659. *pwszValueName = (LPWSTR)LocalAlloc(LPTR,pviValue->NameLength+sizeof(WCHAR));
  660. if(*pwszValueName)
  661. {
  662. *ppbData = (LPBYTE)LocalAlloc(LPTR,pviValue->DataLength);
  663. if(*ppbData)
  664. {
  665. CopyMemory(*pwszValueName, pviValue->Name, pviValue->NameLength);
  666. (*pwszValueName)[pviValue->NameLength/sizeof(WCHAR)] = 0;
  667. CopyMemory(*ppbData, LPBYTE(pviValue)+pviValue->DataOffset, pviValue->DataLength);
  668. }
  669. else
  670. {
  671. err = GetLastError();
  672. LocalFree(*pwszValueName);
  673. *pwszValueName = NULL;
  674. }
  675. }
  676. else
  677. {
  678. err = GetLastError();
  679. }
  680. }
  681. if(pviValue != &viValue)
  682. {
  683. LocalFree(pviValue);
  684. }
  685. if(err != ERROR_SUCCESS)
  686. {
  687. return err;
  688. }
  689. else
  690. {
  691. return RtlNtStatusToDosError( Status );
  692. }
  693. }
  694. IMPLEMENT_SHIM_END