home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / dbmsg / mapi / remote.srv / support.cpp < prev    next >
C/C++ Source or Header  |  1996-04-11  |  20KB  |  669 lines

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. //  File Name 
  4. //      SUPPORT.CPP
  5. //
  6. //  Description
  7. //      Support functions for remote admin program.
  8. //
  9. //  Author
  10. //      Irving De la Cruz
  11. //      
  12. //  Revision: 1.7
  13. //
  14. // Written for Microsoft Windows Developer Support
  15. // Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
  16. //
  17. #include "_WINDS.H"
  18.  
  19. ///////////////////////////////////////////////////////////////////////////////
  20. //    ReleaseSyncObj()
  21. //
  22. //    Parameters
  23. //
  24. //    Purpose
  25. //
  26. //    Return Value
  27. //
  28. void WINAPI ReleaseSyncObj()
  29. {
  30.     DeleteCriticalSection (&g_csIOInfo);
  31.     DeleteCriticalSection (&g_csPipeID);
  32.     DeleteCriticalSection (&g_csNotifLinks);
  33.     DeleteCriticalSection (&g_csServiceState);
  34.     CloseHandle (g_IOInfo.hResumeEvent);
  35. }
  36.  
  37. ///////////////////////////////////////////////////////////////////////////////
  38. //    LoadSettingsFromRegistry()
  39. //
  40. //    Parameters
  41. //      None
  42. //
  43. //    Purpose
  44. //      This function is used to load the application setting structure values
  45. //      from the registry under a subkey for our application. The entire data
  46. //      structure is read from a single sub entry as an array of binary data,
  47. //      unreadable to humans.
  48. //      
  49. //    Return Value
  50. //      None
  51. //      
  52. void WINAPI LoadSettingsFromRegistry()
  53. {
  54.     HKEY hAppKey;
  55.     // Try to open the key in the registry, open it with READ rights,
  56.     // and read the data into our strcture
  57.     if (ERROR_SUCCESS == RegOpenKeyEx (HKEY_LOCAL_MACHINE,
  58.                                        WINDS_SERVICE_REGISTRY_KEY,
  59.                                        0,
  60.                                        KEY_READ,
  61.                                        &hAppKey))
  62.     {
  63.         ULONG cbSize = MAX_PATH*sizeof(TCHAR);
  64.         DWORD dwError = RegQueryValueEx (hAppKey,
  65.                                          DATA_FILE_SUB_KEY,
  66.                                          NULL,
  67.                                          NULL,
  68.                                          (LPBYTE)g_szDataFile,
  69.                                          &cbSize);
  70.         RegCloseKey (hAppKey);
  71.         // If we failed to get the data from the registry we can't return
  72.         // yet, we will proceed to initialize the options with default
  73.         // values
  74.         if (ERROR_SUCCESS == dwError)
  75.         {
  76.             return;
  77.         }
  78.     }
  79.     // If we couldn't read the file name, then initialize to default values
  80.     lstrcpy (g_szDataFile, TEXT(".\\WINDS.DAT"));
  81. }
  82.  
  83. ///////////////////////////////////////////////////////////////////////////////
  84. //    SaveSettingsToRegistry()
  85. //
  86. //    Parameters
  87. //      None
  88. //
  89. //    Purpose
  90. //      This function is used to save the settings structure values to the
  91. //      registry under a subkey for our application. The key where we write
  92. //      the values is created if it doesn't exist. The entire structure data
  93. //      is written to a single sub entry as an array of binary data,
  94. //      unreadable to humans.
  95. //      
  96. //    Return Value
  97. //      None
  98. //      
  99. void WINAPI SaveSettingsToRegistry()
  100. {
  101.     HKEY hAppKey;
  102.     DWORD dwDisposition = MAX_PATH*sizeof(TCHAR);
  103.     // Try to open the key in the registry, open it with READ/WRITE rights
  104.     if (ERROR_SUCCESS != RegCreateKeyEx (HKEY_LOCAL_MACHINE,
  105.                                          WINDS_SERVICE_REGISTRY_KEY,
  106.                                          0,
  107.                                          NULL,
  108.                                          REG_OPTION_NON_VOLATILE,
  109.                                          KEY_WRITE,
  110.                                          NULL,
  111.                                          &hAppKey,
  112.                                          &dwDisposition))
  113.     {
  114.         // If we fail to access the registry, we need to get out, we don't
  115.         // have a key opened where to write
  116.         TraceResult ("SaveSettingsToRegistry: Failed to open app key", HRESULT_FROM_WIN32(GetLastError()));
  117.     }
  118.     else
  119.     {
  120.         // With the opened key handle, create a sub entry where we save our
  121.         // structure values. For this sample we just write the entire
  122.         // structure as a big blob of data, which we read the save way.
  123.         if (ERROR_SUCCESS != RegSetValueEx (hAppKey,
  124.                                             DATA_FILE_SUB_KEY,
  125.                                             0,
  126.                                             REG_SZ,
  127.                                             (LPBYTE)g_szDataFile,
  128.                                             (lstrlen (g_szDataFile)+1)*sizeof(TCHAR)))
  129.         {
  130.             TraceResult ("SaveSettingsToRegistry: Failed to save registry values", HRESULT_FROM_WIN32(GetLastError()));
  131.         }
  132.         // Close the handle we have open and return the appropiate flag
  133.         RegCloseKey (hAppKey);
  134.     }
  135. }
  136.  
  137. ///////////////////////////////////////////////////////////////////////////////
  138. //    GetLocalTempFileName()
  139. //
  140. //    Parameters
  141. //      pszFileName     Pointer to a buffer allocated by the caller where the
  142. //                      function returns a fully qualified patha and file for
  143. //                      a uniquely named temporary file name.
  144. //
  145. //    Purpose
  146. //      This function creates a temporary file name. The file name will be
  147. //      returned in the pszFileName buffer which must have been allocated by
  148. //      the caller. The file will have a fully qualified path to its location.
  149. //      The location of the file is on the TEMP directory as set in the system.
  150. //
  151. //    Return Value
  152. //      0 if the function is successful at creating a temporary
  153. //      unique file name. A system error code otherwise.
  154. //
  155. HRESULT WINAPI GetLocalTempFileName(LPTSTR pszFileName)
  156. {
  157.     HRESULT hResult = S_OK;
  158.     TCHAR szTmpPath[_MAX_PATH];
  159.  
  160.     // Ask the system for the TEMP directory
  161.     if (!GetTempPath (_MAX_PATH, szTmpPath))
  162.     {
  163.         hResult = HRESULT_FROM_WIN32(GetLastError());
  164.         TraceResult ("GetLocalTempFileName: Failed to get temp path", hResult);
  165.         return hResult;
  166.     }
  167.  
  168.     if (!GetTempFileName (szTmpPath,        // Call the Win32 API
  169.                           TEXT("WDS"),      // Our fixed prefix for temp files
  170.                           0,                // Use a pseudo-unique number
  171.                           pszFileName))     // Destination buffer
  172.     {
  173.         hResult = HRESULT_FROM_WIN32(GetLastError());
  174.         TraceResult ("GetLocalTempFileName: Failed to get temp file name", hResult);
  175.     }
  176.     return hResult;
  177. }
  178.  
  179. ///////////////////////////////////////////////////////////////////////////////
  180. //    FileCopy()
  181. //
  182. //    Parameters                        
  183. //      hDest           handle to destination file/stream
  184. //      hSrc            handle to source file/stream
  185. //      dwMsgLen        number of bytes to copy
  186. //
  187. //    Purpose
  188. //      Copies bytes from one open file or stream object to another file
  189. //      stream object.
  190. //      
  191. //    Return Value
  192. //      NO_ERROR on success, ERROR_READ/WRITE_FAULT otherwise
  193. //      
  194. long WINAPI FileCopy(HANDLE hDest, HANDLE hSrc, DWORD dwMsgLen)
  195. {
  196.     BYTE  buf[IO_BUFFERSIZE]; 
  197.     DWORD nRead, nRemaining = dwMsgLen;
  198.     BOOL  bRes;
  199.     for (DWORD nWritten=0; nRemaining>0; nRemaining -= nWritten)
  200.     {
  201.         bRes = ReadFile (hSrc, buf, min(nRemaining, (DWORD)IO_BUFFERSIZE), &nRead, NULL);
  202.         if (!nRead || !bRes)
  203.         {
  204.             return HRESULT_FROM_WIN32(ERROR_READ_FAULT);
  205.         }
  206.         bRes = WriteFile(hDest, buf, nRead, &nWritten, NULL);
  207.         if (!nWritten || !bRes)
  208.         {
  209.             return HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
  210.         }
  211.     }
  212.     return NO_ERROR;
  213. }
  214.  
  215. ///////////////////////////////////////////////////////////////////////////////
  216. //    WaitForClientConnection()
  217. //
  218. //    Parameters
  219. //
  220. //    Purpose
  221. //
  222. //    Return Value
  223. //
  224. long WINAPI WaitForClientConnection (HANDLE hPipe, DWORD dwTimeOut)
  225. {
  226.     dwTimeOut *= 1000; // The timeout value comes is seconds so we must convert them to milliseconds.
  227.  
  228.     long lResult = 0;
  229.     BOOL bResult;
  230.     OVERLAPPED ovWait = { 0 };
  231.     ovWait.hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
  232.     if (NULL == ovWait.hEvent)
  233.     {
  234.         lResult = HRESULT_FROM_WIN32(GetLastError());
  235.         TraceResult ("WaitForClientConnection: Failed to create wait event", lResult);
  236.     }
  237.     else
  238.     {
  239.         bResult = ConnectNamedPipe (hPipe, &ovWait);
  240.         if (FALSE == bResult)
  241.         {
  242.             lResult = HRESULT_FROM_WIN32(GetLastError());
  243.             if (HRESULT_FROM_WIN32(lResult) == HRESULT_FROM_WIN32(ERROR_IO_PENDING) ||
  244.                 HRESULT_FROM_WIN32(lResult) == HRESULT_FROM_WIN32(ERROR_PIPE_CONNECTED))
  245.             {
  246.                 lResult = S_OK;
  247.             }
  248.         }
  249.         if (!lResult)
  250.         {
  251.             lResult = WaitForSingleObject (ovWait.hEvent, dwTimeOut);
  252.             if (WAIT_OBJECT_0 == lResult)
  253.             {
  254.                 lResult = 0; // No error;
  255.             }
  256.             else
  257.             {
  258.                 TraceMessage ("WaitForClientConnection: Client did not connect. Timeout");
  259.                 lResult = HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED);
  260.             }
  261.         }
  262.         else
  263.         {
  264.             lResult = HRESULT_FROM_WIN32(GetLastError());
  265.             TraceResult ("WaitForClientConnection: Failed to wait for connection", lResult);
  266.         }
  267.         CloseHandle (ovWait.hEvent);
  268.     }
  269.     return lResult;
  270. }
  271.  
  272. ///////////////////////////////////////////////////////////////////////////////
  273. //    AnsiToUnicode()
  274. //
  275. //    Parameters
  276. //      
  277. //    Purpose
  278. //
  279. //    Return Value
  280. //      None.
  281. //
  282. HRESULT WINAPI AnsiToUnicode (LPSTR   szStr,
  283.                               LPWSTR  wcStr,
  284.                               ULONG   cchUnicode)
  285. {
  286.     HRESULT hResult = S_OK;
  287.     // Do we have a string to duplicate?
  288.     if (!szStr || !wcStr)
  289.     {
  290.         TraceMessage ("AnsiToUnicode: NULL string pointers");
  291.         return E_INVALIDARG;
  292.     }
  293.     int iChars = MultiByteToWideChar (CP_ACP,
  294.                                       MB_PRECOMPOSED,
  295.                                       szStr,
  296.                                       -1,
  297.                                       wcStr,
  298.                                       cchUnicode);
  299.     if (0 == iChars)
  300.     {
  301.         hResult = HRESULT_FROM_WIN32(GetLastError());
  302.         TraceResult ("AnsiToUnicode: Failed tp convert to UNICODE", hResult);
  303.     }
  304.     return hResult;
  305. }
  306.  
  307. ///////////////////////////////////////////////////////////////////////////////
  308. //    UnicodeToAnsi()
  309. //
  310. //    Parameters
  311. //      
  312. //    Purpose
  313. //
  314. //    Return Value
  315. //      None.
  316. //
  317. HRESULT WINAPI UnicodeToAnsi (LPWSTR  wcStr,
  318.                               LPSTR   szStr,
  319.                               ULONG   cchAnsi)
  320. {
  321.     HRESULT hResult = S_OK;
  322.     // Do we have a string to duplicate?
  323.     if (!szStr || !wcStr)
  324.     {
  325.         TraceMessage ("UnicodeToAnsi: NULL string pointers");
  326.         return E_INVALIDARG;
  327.     }
  328.     int iChars = WideCharToMultiByte (CP_ACP,
  329.                                       0,
  330.                                       wcStr,
  331.                                       -1,
  332.                                       szStr,
  333.                                       cchAnsi,
  334.                                       NULL,
  335.                                       NULL);
  336.     if (0 == iChars)
  337.     {
  338.         hResult = HRESULT_FROM_WIN32(GetLastError());
  339.         TraceResult ("UnicodeToAnsi: Failed tp convert to UNICODE", hResult);
  340.     }
  341.     return hResult;
  342. }
  343.  
  344. ///////////////////////////////////////////////////////////////////////////////
  345. //    GetNextPipeID()
  346. //
  347. //    Parameters
  348. //
  349. //    Purpose
  350. //
  351. //    Return Value
  352. //
  353. DWORD WINAPI GetNextPipeID()
  354. {
  355.     DWORD dwPipeID;
  356.     EnterCriticalSection (&g_csPipeID);
  357.     if (g_dwNextPipeID > MAX_PIPE_NUMBER)
  358.     {
  359.         g_dwNextPipeID = MIN_PIPE_NUMBER;
  360.     }
  361.     dwPipeID = g_dwNextPipeID;
  362.     g_dwNextPipeID++;
  363.     LeaveCriticalSection (&g_csPipeID);
  364.     return dwPipeID;
  365. }
  366.  
  367. ///////////////////////////////////////////////////////////////////////////////
  368. //    CObjectList::CObjectList()
  369. //
  370. //    Parameters
  371. //      
  372. //    Purpose
  373. //
  374. //    Return Value
  375. //      None.
  376. //
  377. CObjectList::CObjectList()
  378. {
  379.     TraceMessage ("CObjectList: Constructor called");
  380.     m_hHeap = GetProcessHeap();
  381.     m_pHead = NULL;
  382.     InitializeCriticalSection (&m_csObj);
  383. }
  384.  
  385. ///////////////////////////////////////////////////////////////////////////////
  386. //    CObjectList::~CObjectList()
  387. //
  388. //    Parameters
  389. //      
  390. //    Purpose
  391. //
  392. //    Return Value
  393. //      None.
  394. //
  395. CObjectList::~CObjectList()
  396. {
  397.     TraceMessage ("CObjectList: Destructor called");
  398.     // Free the entire list
  399.     POBJLIST_NODE pNode = m_pHead;
  400.     while (m_pHead)
  401.     {
  402.         pNode = m_pHead->pNext;
  403.         HeapFree (m_hHeap, 0, m_pHead);
  404.         m_pHead = pNode;
  405.     }
  406.     DeleteCriticalSection (&m_csObj);
  407. }
  408.  
  409. ///////////////////////////////////////////////////////////////////////////////
  410. //    CObjectList::Insert()
  411. //
  412. //    Parameters
  413. //      
  414. //    Purpose
  415. //
  416. //    Return Value
  417. //      An HRESULT
  418. //
  419. STDMETHODIMP CObjectList::Insert (DWORD             dwObjID,
  420.                                   LPTSTR            pObjName,
  421.                                   WINDS_AB_OBJTYPE  ObjType)
  422. {
  423.     // Allocate a new node
  424.     POBJLIST_NODE pNode, pNewNode = (POBJLIST_NODE)HeapAlloc (m_hHeap,
  425.                                                               HEAP_ZERO_MEMORY,
  426.                                                               sizeof(OBJLIST_NODE));
  427.     if (NULL == pNewNode)
  428.     {
  429.         TraceResult ("CObjectList::Insert: Failed to allocate new node", E_OUTOFMEMORY);
  430.         return E_OUTOFMEMORY;
  431.     }
  432.     // Copy the data of the node
  433.     // pNewNode->pNext = NULL; <- implied in the allocation
  434.     pNewNode->dwObjID = dwObjID;
  435.     pNewNode->ObjType = ObjType;
  436.     lstrcpy (pNewNode->szObjAlias, pObjName);
  437.     
  438.     EnterCriticalSection (&m_csObj);
  439.     // Insert the node at the end of the list
  440.     if (m_pHead)
  441.     {
  442.         pNode = m_pHead;
  443.         while (m_pHead)
  444.         {
  445.             if (NULL == pNode->pNext)
  446.             {
  447.                 pNode->pNext = pNewNode;
  448.                 break; // Out of the WHILE() loop
  449.             }
  450.             pNode = pNode->pNext;
  451.         }
  452.     }
  453.     else
  454.     {
  455.         m_pHead = pNewNode;
  456.     }
  457.     LeaveCriticalSection (&m_csObj);
  458.     return S_OK;
  459. }
  460.  
  461. ///////////////////////////////////////////////////////////////////////////////
  462. //    CObjectList::Delete()
  463. //
  464. //    Parameters
  465. //      
  466. //    Purpose
  467. //
  468. //    Return Value
  469. //      An HRESULT
  470. //
  471. STDMETHODIMP CObjectList::Delete (DWORD dwObjID)
  472. {
  473.     HRESULT hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  474.     EnterCriticalSection (&m_csObj);
  475.     POBJLIST_NODE pNext, pPrev = NULL, pNode = m_pHead;
  476.     while (pNode)
  477.     {
  478.         if (pNode->dwObjID == dwObjID)
  479.         {
  480.             // If the node is the head of the list, make the head point to the next node
  481.             if (m_pHead == pNode)
  482.             {
  483.                 m_pHead = m_pHead->pNext;
  484.             }
  485.             // Save the next pointer so that we may delete the current node
  486.             pNext = pNode->pNext;
  487.             HeapFree (m_hHeap, 0, pNode);
  488.             if (pPrev)
  489.             {
  490.                 pPrev->pNext = pNext;
  491.             }
  492.             hResult = S_OK;
  493.             break; // Out of the WHILE() loop
  494.         }
  495.         else
  496.         {
  497.             pPrev = pNode;
  498.             pNode = pNode->pNext;
  499.         }
  500.     }
  501.     LeaveCriticalSection (&m_csObj);
  502.     return hResult;
  503. }
  504.  
  505. ///////////////////////////////////////////////////////////////////////////////
  506. //    CObjectList::FindObjFromID()
  507. //
  508. //    Parameters
  509. //      
  510. //    Purpose
  511. //
  512. //    Return Value
  513. //      An HRESULT
  514. //
  515. STDMETHODIMP CObjectList::FindObjFromID (DWORD              dwObjID,
  516.                                          LPTSTR             pObjName,
  517.                                          WINDS_AB_OBJTYPE * pObjType)
  518. {
  519.     HRESULT hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  520.     EnterCriticalSection (&m_csObj);
  521.     POBJLIST_NODE pNode = m_pHead;
  522.     while (pNode)
  523.     {
  524.         if (pNode->dwObjID == dwObjID)
  525.         {
  526.             *pObjType = pNode->ObjType;
  527.             lstrcpy (pObjName, pNode->szObjAlias);
  528.             hResult = S_OK; 
  529.             break; // Out of the WHILE() loop
  530.         }
  531.         pNode = pNode->pNext;
  532.     }
  533.     LeaveCriticalSection (&m_csObj);
  534.     return hResult;
  535. }
  536.  
  537. ///////////////////////////////////////////////////////////////////////////////
  538. //    CObjectList::FindObjFromName()
  539. //
  540. //    Parameters
  541. //
  542. //    Purpose
  543. //
  544. //    Return Value
  545. //      An HRESULT
  546. //
  547. STDMETHODIMP CObjectList::FindObjFromName (WINDS_AB_OBJTYPE     ObjType,
  548.                                            LPTSTR               pObjName,
  549.                                            DWORD *              pdwObjID)
  550. {
  551.     HRESULT hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  552.     EnterCriticalSection (&m_csObj);
  553.     POBJLIST_NODE pNode = m_pHead;
  554.     while (pNode)
  555.     {
  556.         if (pNode->ObjType == ObjType && 0 == lstrcmpi (pNode->szObjAlias, pObjName))
  557.         {
  558.             *pdwObjID = pNode->dwObjID;
  559.             hResult = S_OK;
  560.             break; // Out of the WHILE() loop
  561.         }
  562.         pNode = pNode->pNext;
  563.     }
  564.     LeaveCriticalSection (&m_csObj);
  565.     return hResult;
  566. }
  567.  
  568. ///////////////////////////////////////////////////////////////////////////////
  569. //    CObjectList::FindObjAndTypeFromName()
  570. //
  571. //    Parameters
  572. //
  573. //    Purpose
  574. //
  575. //    Return Value
  576. //      An HRESULT
  577. //
  578. STDMETHODIMP CObjectList::FindObjAndTypeFromName (LPTSTR               pObjName,
  579.                                                   WINDS_AB_OBJTYPE *   pObjType,
  580.                                                   DWORD *              pdwObjID)
  581. {
  582.     HRESULT hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  583.     EnterCriticalSection (&m_csObj);
  584.     POBJLIST_NODE pNode = m_pHead;
  585.     while (pNode)
  586.     {
  587.         if (0 == lstrcmpi (pNode->szObjAlias, pObjName))
  588.         {
  589.             *pObjType = pNode->ObjType;
  590.             *pdwObjID = pNode->dwObjID;
  591.             hResult = S_OK;
  592.             break; // Out of the WHILE() loop
  593.         }
  594.         pNode = pNode->pNext;
  595.     }
  596.     LeaveCriticalSection (&m_csObj);
  597.     return hResult;
  598. }
  599.  
  600. ///////////////////////////////////////////////////////////////////////////////
  601. //    CObjectList::IsAliasNameAvailable()
  602. //
  603. //    Parameters
  604. //
  605. //    Purpose
  606. //
  607. //    Return Value
  608. //      An HRESULT
  609. //
  610. BOOL WINAPI CObjectList::IsAliasNameAvailable (LPTSTR pObjName)
  611. {
  612.     BOOL bResult = TRUE;
  613.     EnterCriticalSection (&m_csObj);
  614.     POBJLIST_NODE pNode = m_pHead;
  615.     while (pNode)
  616.     {
  617.         if (0 == lstrcmpi (pNode->szObjAlias, pObjName))
  618.         {
  619.             bResult = FALSE;
  620.             break; // Out of the WHILE() loop
  621.         }
  622.         pNode = pNode->pNext;
  623.     }
  624.     LeaveCriticalSection (&m_csObj);
  625.     return bResult;
  626. }
  627.  
  628. ///////////////////////////////////////////////////////////////////////////////
  629. //    CreateDLsDirectory()
  630. //
  631. //    Parameters
  632. //
  633. //    Purpose
  634. //
  635. //    Return Value
  636. //
  637. HRESULT WINAPI CreateDLsDirectory (LPSTORAGE pStorage)
  638. {
  639.     LPSTORAGE pPFStg;
  640.     HRESULT hResult = pStorage->CreateStorage (DISTRIBUTION_LISTS, CREATE_FLAGS, 0, 0, &pPFStg);
  641.     if (!hResult)
  642.     {
  643.         pPFStg->Release();
  644.     }
  645.     return hResult;
  646. }
  647.  
  648. ///////////////////////////////////////////////////////////////////////////////
  649. //    CreatePublicFoldersDirectory()
  650. //
  651. //    Parameters
  652. //
  653. //    Purpose
  654. //
  655. //    Return Value
  656. //
  657. HRESULT WINAPI CreatePublicFoldersDirectory (LPSTORAGE pStorage)
  658. {
  659.     LPSTORAGE pPFStg;
  660.     HRESULT hResult = pStorage->CreateStorage (PUBLIC_FOLDERS_ROOT, CREATE_FLAGS, 0, 0, &pPFStg);
  661.     if (!hResult)
  662.     {
  663.         pPFStg->Release();
  664.     }
  665.     return hResult;
  666. }
  667.  
  668. // End of file for SUPPORT.CPP
  669.