1 /*
2  * regexpl - Console Registry Explorer
3  *
4  * Copyright (C) 2000-2005 Nedko Arnaudov <nedko@users.sourceforge.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; see the file COPYING.  If not, write to
18  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21 
22 // RegistryTree.cpp: implementation of the CRegistryTree class.
23 
24 #include "ph.h"
25 #include "RegistryTree.h"
26 #include "Pattern.h"
27 #include "RegistryExplorer.h"
28 
CRegistryTree()29 CRegistryTree::CRegistryTree()
30 {
31 	m_pszMachineName = NULL;
32   VERIFY(SUCCEEDED(m_Root.m_Key.InitRoot()));
33   m_Root.m_pUp = NULL;
34 	m_pCurrentKey = &m_Root;
35   ASSERT(m_pCurrentKey->m_Key.IsRoot());
36 	m_ErrorMsg[ERROR_MSG_BUFFER_SIZE] = 0;
37 }
38 
CRegistryTree(const CRegistryTree & Tree)39 CRegistryTree::CRegistryTree(const CRegistryTree& Tree)
40 {
41 	m_pszMachineName = NULL;
42   VERIFY(SUCCEEDED(m_Root.m_Key.InitRoot()));
43   m_Root.m_pUp = NULL;
44 	m_pCurrentKey = &m_Root;
45   ASSERT(m_pCurrentKey->m_Key.IsRoot());
46 
47   const TCHAR *pszPath = Tree.GetCurrentPath();
48   if ((pszPath[0] == _T('\\')) && (pszPath[1] == _T('\\')))
49   { // path has machine name
50     pszPath += 2;
51     while (*pszPath && (*pszPath != _T('\\')))
52       pszPath++;
53 
54     ASSERT(*pszPath == _T('\\')); // if path begins with \\ it must be followed by machine name
55   }
56 
57   if (Tree.m_pszMachineName)
58     SetMachineName(Tree.m_pszMachineName);
59 
60   VERIFY(ChangeCurrentKey(pszPath));
61 }
62 
~CRegistryTree()63 CRegistryTree::~CRegistryTree()
64 {
65 	if (m_pszMachineName)
66 		delete[] m_pszMachineName;
67 
68 	CNode *pNode;
69 	while(m_pCurrentKey->m_pUp)
70 	{
71 		pNode = m_pCurrentKey;
72 		m_pCurrentKey = m_pCurrentKey->m_pUp;
73 		delete pNode;
74 	}
75 
76   // We are on root
77   ASSERT(m_pCurrentKey->m_Key.IsRoot());
78   ASSERT(m_pCurrentKey == &m_Root);
79 }
80 
GetCurrentPath() const81 const TCHAR * CRegistryTree::GetCurrentPath() const
82 {
83   return m_pCurrentKey->m_Key.GetKeyName();
84 }
85 
IsCurrentRoot()86 BOOL CRegistryTree::IsCurrentRoot()
87 {
88 	return m_pCurrentKey->m_Key.IsRoot();
89 }
90 
ChangeCurrentKey(const TCHAR * pszRelativePath)91 BOOL CRegistryTree::ChangeCurrentKey(const TCHAR *pszRelativePath)
92 {
93   if (*pszRelativePath == _T('\\'))
94     GotoRoot();  // This is full absolute path.
95 
96   // split path to key names.
97 	const TCHAR *pszSeps = _T("\\");
98 
99   // Make buffer and copy relative path into it.
100   TCHAR *pszBuffer = new (std::nothrow) TCHAR[_tcslen(pszRelativePath)+1];
101   if (!pszBuffer)
102   {
103     SetError(ERROR_OUTOFMEMORY);
104     return FALSE;
105   }
106 
107 	_tcscpy(pszBuffer,pszRelativePath);
108 
109   // We accept names in form "\"blablabla\\blab  labla\"\\"
110   size_t size = _tcslen(pszBuffer);
111   if (size)
112   {
113     if (pszBuffer[size-1] == _T('\\'))
114       pszBuffer[--size] = 0;
115 
116     TCHAR *psz;
117     if (*pszBuffer == _T('\"') && (psz = _tcschr(pszBuffer+1,_T('\"'))) && size_t(psz-pszBuffer) == size-1)
118     {
119       size--;
120       pszBuffer[size] = 0;
121 
122       pszBuffer++;
123     }
124   }
125 
126 	const TCHAR *pszNewKey = _tcstok(pszBuffer,pszSeps);
127 
128 	if ((!pszNewKey)&&((*pszRelativePath != _T('\\'))||(*(pszRelativePath+1) != 0)))
129 	{
130 		SetError(_T("Invalid key name"));
131 		goto Abort;
132 	};
133 
134   // change keys
135 	while (pszNewKey)
136 	{
137     if (!InternalChangeCurrentKey(pszNewKey,KEY_READ))
138       goto Abort;  // InternalChangeCurrentKey sets last error description
139 
140 		// Get next key name
141 		pszNewKey = _tcstok(NULL,pszSeps);
142 	}
143 
144 	return TRUE;
145 
146 Abort:
147   delete[] pszBuffer;
148   return FALSE;
149 }
150 
GetLastErrorDescription()151 const TCHAR * CRegistryTree::GetLastErrorDescription()
152 {
153 	return m_ErrorMsg;
154 }
155 
GotoRoot()156 void CRegistryTree::GotoRoot()
157 {
158   // Delete current tree
159   CNode *pNode;
160   while(m_pCurrentKey->m_pUp)
161   {
162     pNode = m_pCurrentKey;
163     m_pCurrentKey = m_pCurrentKey->m_pUp;
164     delete pNode;
165   }
166 
167   // We are on root
168   ASSERT(m_pCurrentKey->m_Key.IsRoot());
169   ASSERT(m_pCurrentKey == &m_Root);
170 }
171 
SetMachineName(LPCTSTR pszMachineName)172 BOOL CRegistryTree::SetMachineName(LPCTSTR pszMachineName)
173 {
174   GotoRoot();
175 
176   // If we are going to local machine...
177   if (pszMachineName == NULL)
178   {
179     // Delete previous machine name buffer if allocated.
180     if (m_pszMachineName)
181       delete[] m_pszMachineName;
182 
183     m_pszMachineName = NULL;
184     m_Root.m_Key.InitRoot();
185     return TRUE;
186   }
187 
188   // Skip leading backslashes if any.
189   while ((*pszMachineName)&&(*pszMachineName == _T('\\')))
190     pszMachineName++;
191 
192   ASSERT(*pszMachineName);      // No machine name.
193 
194   TCHAR *pszNewMachineName = new (std::nothrow) TCHAR[_tcslen(pszMachineName)+3]; // two leading backslashes + terminating null
195 
196   if (!pszNewMachineName)
197   {
198     SetError(ERROR_OUTOFMEMORY);
199     return FALSE;
200   }
201 
202   // Delete previous machine name buffer if allocated.
203   if (m_pszMachineName)
204     delete[] m_pszMachineName;
205 
206   m_pszMachineName = pszNewMachineName;
207 
208   _tcscpy(m_pszMachineName,_T("\\\\")); // leading backslashes
209   _tcscpy(m_pszMachineName+2,pszMachineName); // machine name itself
210   _tcsupr(m_pszMachineName+2);  // upercase it
211 
212   VERIFY(SUCCEEDED(m_Root.m_Key.InitRoot(m_pszMachineName)));
213   return TRUE;
214 }
215 
NewKey(const TCHAR * pszKeyName,const TCHAR * pszPath,BOOL blnVolatile)216 BOOL CRegistryTree::NewKey(const TCHAR *pszKeyName, const TCHAR *pszPath, BOOL blnVolatile)
217 {
218   if (!m_pCurrentKey)
219   {
220     SetErrorCommandNAOnRoot(_T("Creating new key "));
221     return FALSE;
222   }
223 
224   CRegistryTree Tree(*this);
225 	if (!Tree.ChangeCurrentKey(pszPath))
226   {
227     SetError(Tree.GetLastErrorDescription());
228     return FALSE;
229   }
230 
231 	BOOL blnOpened;
232   HKEY hKey;
233 
234 	LONG nError = Tree.m_pCurrentKey->m_Key.CreateSubkey(KEY_READ,
235                                                        pszKeyName,
236                                                        hKey,
237                                                        &blnOpened,
238                                                        blnVolatile);
239   if (nError == ERROR_SUCCESS)
240   {
241     LONG nError = RegCloseKey(hKey);
242     ASSERT(nError == ERROR_SUCCESS);
243   }
244 
245 	if ((nError == ERROR_SUCCESS) && blnOpened)
246 	{
247 		SetError(_T("A key \"%s\" already exists."),pszKeyName);
248     return FALSE;
249 	}
250 
251 	if (nError != ERROR_SUCCESS)
252 	{
253 		SetError(_T("Cannot create key : %s%s\nError %d (%s)\n"),
254              GetCurrentPath(),pszKeyName,nError,GetErrorDescription(nError));
255 
256     return FALSE;
257 	}
258 
259 	return TRUE;
260 }
261 
DeleteSubkeys(const TCHAR * pszKeyPattern,const TCHAR * pszPath,BOOL blnRecursive)262 BOOL CRegistryTree::DeleteSubkeys(const TCHAR *pszKeyPattern, const TCHAR *pszPath, BOOL blnRecursive)
263 {
264   CRegistryKey Key;
265   if (!GetKey(pszPath,KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS|DELETE,Key))
266     return FALSE;
267 
268   return DeleteSubkeys(Key, pszKeyPattern, blnRecursive);
269 }
270 
DeleteSubkeys(CRegistryKey & rKey,const TCHAR * pszKeyPattern,BOOL blnRecursive)271 BOOL CRegistryTree::DeleteSubkeys(CRegistryKey& rKey, const TCHAR *pszKeyPattern, BOOL blnRecursive)
272 {
273   LONG nError;
274 
275   // enumerate subkeys
276   DWORD dwMaxSubkeyNameLength;
277   nError = rKey.GetSubkeyNameMaxLength(dwMaxSubkeyNameLength);
278   if (nError != ERROR_SUCCESS)
279   {
280     SetError(_T("Cannot delete subkeys(s) of key %s.\nRequesting info about key failed.\nError %d (%s)\n"),
281              rKey.GetKeyName(),nError,GetErrorDescription(nError));
282     return FALSE;
283   }
284 
285   TCHAR *pszSubkeyName = new (std::nothrow) TCHAR [dwMaxSubkeyNameLength];
286   rKey.InitSubkeyEnumeration(pszSubkeyName, dwMaxSubkeyNameLength);
287   BOOL blnKeyDeleted = FALSE;
288   while ((nError = rKey.GetNextSubkeyName()) == ERROR_SUCCESS)
289   {
290     if (PatternMatch(pszKeyPattern,pszSubkeyName))
291     {
292       if (blnRecursive)
293       { // deltion is recursive, delete subkey subkeys
294         CRegistryKey Subkey;
295         // open subkey
296         nError = rKey.OpenSubkey(DELETE,pszSubkeyName,Subkey);
297         // delete subkey subkeys
298         if (DeleteSubkeys(Subkey, PATTERN_MATCH_ALL, TRUE))
299         {
300           AddErrorDescription(_T("Cannot delete subkey(s) of key %s. Subkey deletion failed.\n"),Subkey.GetKeyName());
301           return FALSE;
302         }
303       }
304 
305       nError = rKey.DeleteSubkey(pszSubkeyName);
306       if (nError != ERROR_SUCCESS)
307       {
308         SetError(_T("Cannot delete the %s subkey of key %s.\nError %d (%s)\n"),
309                  pszSubkeyName,rKey.GetKeyName(),nError,GetErrorDescription(nError));
310 
311         return FALSE;
312       }
313       blnKeyDeleted = TRUE;
314       rKey.InitSubkeyEnumeration(pszSubkeyName, dwMaxSubkeyNameLength); // reset iteration
315     }
316   }
317 
318   ASSERT(nError != ERROR_SUCCESS);
319   if (nError != ERROR_NO_MORE_ITEMS)
320   {
321     SetError(_T("Cannot delete subkeys(s) of key %s.\nSubkey enumeration failed.\nError %d (%s)\n"),
322              rKey.GetKeyName(),nError,GetErrorDescription(nError));
323     return FALSE;
324   }
325 
326   if (!blnKeyDeleted)
327     SetError(_T("The key %s has no subkeys that match %s pattern.\n"),rKey.GetKeyName(),pszKeyPattern);
328 
329   return blnKeyDeleted;
330 }
331 
GetErrorDescription(LONG nError)332 const TCHAR * CRegistryTree::GetErrorDescription(LONG nError)
333 {
334   switch(nError)
335   {
336   case ERROR_ACCESS_DENIED:
337     return _T("Access denied");
338   case ERROR_FILE_NOT_FOUND:
339     return _T("The system cannot find the key specified");
340   case ERROR_INTERNAL_ERROR:
341     return _T("Internal error");
342   case ERROR_OUTOFMEMORY:
343     return _T("Out of memory");
344   default:
345     return _T("Unknown error");
346   }
347 }
348 
SetError(LONG nError)349 void CRegistryTree::SetError(LONG nError)
350 {
351   SetError(_T("Error %u (%s)"),nError,GetErrorDescription(nError));
352 }
353 
SetError(const TCHAR * pszFormat,...)354 void CRegistryTree::SetError(const TCHAR *pszFormat, ...)
355 {
356   va_list args;
357   va_start(args,pszFormat);
358   if (_vsntprintf(m_ErrorMsg,ERROR_MSG_BUFFER_SIZE,pszFormat,args) < 0)
359     m_ErrorMsg[ERROR_MSG_BUFFER_SIZE] = 0;
360   va_end(args);
361 }
362 
SetInternalError()363 void CRegistryTree::SetInternalError()
364 {
365   SetError(_T("Internal Error"));
366 }
367 
AddErrorDescription(const TCHAR * pszFormat,...)368 void CRegistryTree::AddErrorDescription(const TCHAR *pszFormat, ...)
369 {
370   size_t size = _tcslen(m_ErrorMsg);
371   if (size < ERROR_MSG_BUFFER_SIZE)
372   {
373     TCHAR *pszAdd = m_ErrorMsg+size;
374     va_list args;
375     va_start(args,pszFormat);
376     size = ERROR_MSG_BUFFER_SIZE-size;
377     if (_vsntprintf(pszAdd,size,pszFormat,args) < 0)
378       m_ErrorMsg[size] = 0;
379     va_end(args);
380   }
381 }
382 
SetErrorCommandNAOnRoot(const TCHAR * pszCommand)383 void CRegistryTree::SetErrorCommandNAOnRoot(const TCHAR *pszCommand)
384 {
385   ASSERT(pszCommand);
386   if (pszCommand)
387     SetError(_T("%s") COMMAND_NA_ON_ROOT,pszCommand);
388   else
389     SetInternalError();
390 }
391 
InternalChangeCurrentKey(const TCHAR * pszSubkeyName,REGSAM DesiredAccess)392 BOOL CRegistryTree::InternalChangeCurrentKey(const TCHAR *pszSubkeyName, REGSAM DesiredAccess)
393 {
394   size_t size = _tcslen(pszSubkeyName);
395   TCHAR *pszSubkeyNameBuffer = new (std::nothrow) TCHAR[size+3];
396   if (!pszSubkeyNameBuffer)
397   {
398     SetError(_T("Cannot open key : %s%s\nError %d (%s)\n"),
399              GetCurrentPath(),pszSubkeyName,ERROR_OUTOFMEMORY,GetErrorDescription(ERROR_OUTOFMEMORY));
400   }
401 
402   _tcscpy(pszSubkeyNameBuffer,pszSubkeyName);
403   if (size && (pszSubkeyName[0] == _T('\"')) && (pszSubkeyName[size-1] == _T('\"')))
404   {
405     pszSubkeyNameBuffer[size-1] = 0;
406     pszSubkeyName = pszSubkeyNameBuffer+1;
407   }
408 
409   if (_tcscmp(pszSubkeyName,_T(".")) == 0)
410   {
411     delete[] pszSubkeyNameBuffer;
412     return TRUE;
413   }
414 
415   if (_tcscmp(pszSubkeyName,_T("..")) == 0)
416   {
417     // Up level abstraction
418     if (m_pCurrentKey->m_Key.IsRoot())
419     {
420       // We are on root
421       ASSERT(m_pCurrentKey->m_pUp == NULL);
422       SetError(_T("Cannot open key. The root is not child.\n"));
423       delete[] pszSubkeyNameBuffer;
424       return FALSE;
425     }
426 
427     ASSERT(m_pCurrentKey->m_pUp);
428     if (!m_pCurrentKey->m_pUp)
429     {
430       SetInternalError();
431       delete[] pszSubkeyNameBuffer;
432       return FALSE;
433     }
434     CNode *pNode = m_pCurrentKey;
435     m_pCurrentKey = m_pCurrentKey->m_pUp;
436     delete pNode;
437     delete[] pszSubkeyNameBuffer;
438     return TRUE;
439   }
440 
441   CNode *pNewKey = new CNode;
442   if (!pNewKey)
443   {
444     SetError(_T("Cannot open key : %s%s\nError %d (%s)\n"),
445              GetCurrentPath(),pszSubkeyName,ERROR_OUTOFMEMORY,GetErrorDescription(ERROR_OUTOFMEMORY));
446     delete[] pszSubkeyNameBuffer;
447     return FALSE;
448   }
449 
450   if (!InternalGetSubkey(pszSubkeyName,DesiredAccess,pNewKey->m_Key))
451   {
452     delete pNewKey;
453     delete[] pszSubkeyNameBuffer;
454     return FALSE;
455   }
456   pNewKey->m_pUp = m_pCurrentKey;
457   m_pCurrentKey = pNewKey;
458 
459   delete[] pszSubkeyNameBuffer;
460   return TRUE;
461 }
462 
InternalGetSubkey(const TCHAR * pszSubkeyName,REGSAM DesiredAccess,CRegistryKey & rKey)463 BOOL CRegistryTree::InternalGetSubkey(const TCHAR *pszSubkeyName, REGSAM DesiredAccess, CRegistryKey& rKey)
464 {
465   LONG nError;
466   HKEY hNewKey = NULL;
467   TCHAR *pszSubkeyNameCaseUpdated = NULL;
468 
469   nError = m_pCurrentKey->m_Key.OpenSubkey(DesiredAccess,pszSubkeyName,hNewKey);
470 
471   if (nError != ERROR_SUCCESS)
472   {
473 		SetError(_T("Cannot open key : %s%s\nError %u (%s)\n"),
474              GetCurrentPath(),pszSubkeyName,nError,GetErrorDescription(nError));
475 
476     return FALSE;
477   }
478 
479   // enum subkeys to find the subkey and get its name in stored case.
480   DWORD dwMaxSubkeyNameLength;
481 	nError = m_pCurrentKey->m_Key.GetSubkeyNameMaxLength(dwMaxSubkeyNameLength);
482   if (nError != ERROR_SUCCESS)
483     goto SkipCaseUpdate;
484 
485   pszSubkeyNameCaseUpdated = new (std::nothrow) TCHAR [dwMaxSubkeyNameLength];
486   m_pCurrentKey->m_Key.InitSubkeyEnumeration(pszSubkeyNameCaseUpdated, dwMaxSubkeyNameLength);
487   while ((nError = m_pCurrentKey->m_Key.GetNextSubkeyName()) == ERROR_SUCCESS)
488     if (_tcsicmp(pszSubkeyNameCaseUpdated, pszSubkeyName) == 0)
489       break;
490 
491   if (nError != ERROR_SUCCESS)
492   {
493     delete[] pszSubkeyNameCaseUpdated;
494     pszSubkeyNameCaseUpdated = NULL;
495   }
496 
497 SkipCaseUpdate:
498 
499   HRESULT hr;
500   ASSERT(hNewKey);
501   if (pszSubkeyNameCaseUpdated)
502   {
503     hr = rKey.Init(hNewKey,GetCurrentPath(),pszSubkeyNameCaseUpdated,DesiredAccess);
504     if (FAILED(hr))
505     {
506       if (hr == (HRESULT)E_OUTOFMEMORY)
507         SetError(_T("Cannot open key : %s%s\nError %d (%s)\n"),
508                  GetCurrentPath(),pszSubkeyName,ERROR_OUTOFMEMORY,GetErrorDescription(ERROR_OUTOFMEMORY));
509       else
510         SetError(_T("Cannot open key : %s%s\nUnknown error\n"), GetCurrentPath(), pszSubkeyName);
511 
512       goto Abort;
513     }
514 
515     delete[] pszSubkeyNameCaseUpdated;
516   }
517   else
518   {
519     hr = rKey.Init(hNewKey,GetCurrentPath(),pszSubkeyName,DesiredAccess);
520     if (FAILED(hr))
521     {
522       if (hr == (HRESULT)E_OUTOFMEMORY)
523         SetError(_T("Cannot open key : %s%s\nError %d (%s)\n"),
524                  GetCurrentPath(),pszSubkeyName,ERROR_OUTOFMEMORY,GetErrorDescription(ERROR_OUTOFMEMORY));
525       else
526         SetError(_T("Cannot open key : %s%s\nUnknown error \n"),
527                  GetCurrentPath(),
528                  pszSubkeyName);
529 
530       goto Abort;
531     }
532   }
533 
534   return TRUE;
535 Abort:
536   if (pszSubkeyNameCaseUpdated)
537     delete[] pszSubkeyNameCaseUpdated;
538 
539   if (hNewKey)
540   {
541     LONG nError = RegCloseKey(hNewKey);
542     ASSERT(nError == ERROR_SUCCESS);
543   }
544 
545   return FALSE;
546 }
547 
GetKey(const TCHAR * pszRelativePath,REGSAM DesiredAccess,CRegistryKey & rKey)548 BOOL CRegistryTree::GetKey(const TCHAR *pszRelativePath, REGSAM DesiredAccess, CRegistryKey& rKey)
549 {
550   CRegistryTree Tree(*this);
551 
552   if (!Tree.ChangeCurrentKey(pszRelativePath))
553   {
554     SetError(Tree.GetLastErrorDescription());
555     return FALSE;
556   }
557 
558   if (Tree.m_pCurrentKey->m_Key.IsRoot())
559   {
560     HRESULT hr = rKey.InitRoot(m_pszMachineName);
561     if (FAILED(hr))
562     {
563       if (hr == (HRESULT)E_OUTOFMEMORY)
564         SetError(_T("\nError %d (%s)\n"),
565                  ERROR_OUTOFMEMORY,GetErrorDescription(ERROR_OUTOFMEMORY));
566       else
567         SetInternalError();
568       return FALSE;
569     }
570 
571     return TRUE;
572   }
573 
574   // open key with desired access
575 
576   // may be call to DuplicateHandle() is better.
577   // registry key handles returned by the RegConnectRegistry function cannot be used in a call to DuplicateHandle.
578 
579   // Get short key name now...
580   const TCHAR *pszKeyName = Tree.m_pCurrentKey->m_Key.GetKeyName();
581   if (pszKeyName == NULL)
582   {
583     SetInternalError();
584     return FALSE;
585   }
586 
587   size_t size = _tcslen(pszKeyName);
588   ASSERT(size);
589   if (!size)
590   {
591     SetInternalError();
592     return FALSE;
593   }
594 
595   const TCHAR *pszShortKeyName_ = pszKeyName + size-1;
596   pszShortKeyName_--; // skip ending backslash
597   size = 0;
598   while (pszShortKeyName_ >= pszKeyName)
599   {
600     if (*pszShortKeyName_ == _T('\\'))
601       break;
602     pszShortKeyName_--;
603     size++;
604   }
605 
606   if (!size || (*pszShortKeyName_ != _T('\\')))
607   {
608     ASSERT(FALSE);
609     SetInternalError();
610     return FALSE;
611   }
612 
613   TCHAR *pszShortKeyName = new (std::nothrow) TCHAR [size+1];
614   if (!pszShortKeyName)
615   {
616     SetError(ERROR_OUTOFMEMORY);
617     return FALSE;
618   }
619 
620   memcpy(pszShortKeyName,pszShortKeyName_+1,size*sizeof(TCHAR));
621   pszShortKeyName[size] = 0;
622 
623   // change to parent key
624 	if (!Tree.InternalChangeCurrentKey(_T(".."),READ_CONTROL))
625   {
626     delete[] pszShortKeyName;
627     ASSERT(FALSE);
628     SetInternalError();
629     return FALSE;
630   }
631 
632   // change back to target key
633 	if (!Tree.InternalGetSubkey(pszShortKeyName,DesiredAccess,rKey))
634   {
635     delete[] pszShortKeyName;
636     SetError(Tree.GetLastErrorDescription());
637     return FALSE;
638   }
639 
640   delete[] pszShortKeyName;
641   return TRUE;
642 }
643 
644