/* * CSV.C * * Migrate CSV <-> WAB * * Copyright 1997 Microsoft Corporation. All Rights Reserved. */ #include "_comctl.h" #include #include #include #include #include #include #include #include #include #include "wabimp.h" #include "..\..\wab32res\resrc2.h" #include "dbgutil.h" BOOL HandleImportError(HWND hwnd, ULONG ids, HRESULT hResult, LPTSTR lpDisplayName, LPTSTR lpEmailAddress, LPWAB_IMPORT_OPTIONS lpImportOptions); BOOL HandleExportError(HWND hwnd, ULONG ids, HRESULT hResult, LPTSTR lpDisplayName, LPTSTR lpEmailAddress, LPWAB_EXPORT_OPTIONS lpExportOptions); /*************************************************************************** Name : IsDomainName Purpose : Is this domain correctly formatted for an Internet address? Parameters: lpDomain -> Domain name to check Returns : TRUE if the domain is a correct format for an Internet address. Comment : Valid domain names have this form: bar[.bar]* where bar must have non-empty contents no high bits are allowed on any characters no '@' allowed ***************************************************************************/ BOOL IsDomainName(LPTSTR lpDomain) { BOOL fBar = FALSE; if (lpDomain) { if (*lpDomain == '\0' || *lpDomain == '.') { // domain name must have contents and can't start with '.' return(FALSE); } while (*lpDomain) { // Internet addresses only allow pure ASCII. No high bits! if (*lpDomain & 0x80 || *lpDomain == '@') { return(FALSE); } if (*lpDomain == '.') { // Recursively check this part of the domain name return(IsDomainName(CharNext(lpDomain))); } lpDomain = CharNext(lpDomain); } return(TRUE); } return(FALSE); } /*************************************************************************** Name : IsInternetAddress Purpose : Is this address correctly formatted for an Internet address Parameters: lpAddress -> Address to check Returns : TRUE if the address is a correct format for an Internet address. Comment : Valid addresses have this form: foo@bar[.bar]* where foo and bar must have non-empty contents ***************************************************************************/ BOOL IsInternetAddress(LPTSTR lpAddress) { BOOL fDomain = FALSE; // Step through the address looking for '@'. If there's an at sign in the middle // of a string, this is close enough to being an internet address for me. if (lpAddress) { // Can't start with '@' if (*lpAddress == '@') { return(FALSE); } while (*lpAddress) { // Internet addresses only allow pure ASCII. No high bits! if (*lpAddress & 0x80) { return(FALSE); } if (*lpAddress == '@') { // Found the at sign. Is there anything following? // (Must NOT be another '@') return(IsDomainName(CharNext(lpAddress))); } lpAddress = CharNext(lpAddress); } } return(FALSE); } /*************************************************************************** Name : OpenCSVFile Purpose : Opens a CSV file for import Parameters: hwnd = main dialog window lpFileName = filename to create lphFile -> returned file handle Returns : HRESULT Comment : ***************************************************************************/ HRESULT OpenCSVFile(HWND hwnd, LPTSTR lpFileName, LPHANDLE lphFile) { LPTSTR lpFilter; TCHAR szFileName[MAX_PATH + 1] = ""; OPENFILENAME ofn; HANDLE hFile = INVALID_HANDLE_VALUE; HRESULT hResult = hrSuccess; DWORD ec; if (INVALID_HANDLE_VALUE == (hFile = CreateFile(lpFileName, GENERIC_READ, 0, // sharing NULL, CREATE_NEW, FILE_FLAG_SEQUENTIAL_SCAN, NULL))) { ec = GetLastError(); DebugTrace("CreateFile(%s) -> %u\n", lpFileName, ec); switch (ec) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: default: ShowMessageBoxParam(hwnd, IDE_CSV_EXPORT_FILE_ERROR, MB_ICONERROR, lpFileName); hResult = ResultFromScode(MAPI_E_NOT_FOUND); break; } } if (! hResult) { *lphFile = hFile; } return(hResult); } /*************************************************************************** Name : CountCSVRows Purpose : Counts the rows in the CSV file Parameters: hFile = open CSV file szSep = list separator lpulcEntries -> returned count of rows Returns : HRESULT Comment : File pointer should be positioned past the header row prior to calling this function. This function leaves the file pointer where it found it. ***************************************************************************/ HRESULT CountCSVRows(HANDLE hFile, LPTSTR szSep, LPULONG lpulcEntries) { HRESULT hResult = hrSuccess; PUCHAR * rgItems = NULL; ULONG ulStart; ULONG cProps, i; *lpulcEntries = 0; Assert(hFile != INVALID_HANDLE_VALUE); if (0xFFFFFFFF == (ulStart = SetFilePointer(hFile, 0, NULL, FILE_CURRENT))) { DebugTrace("CountCSVRows SetFilePointer -> %u\n", GetLastError()); return(ResultFromScode(MAPI_E_CALL_FAILED)); } while (hResult == hrSuccess) { // Read the line if (ReadCSVLine(hFile, szSep, &cProps, &rgItems)) { // End of file break; } (*lpulcEntries)++; if (rgItems) { for (i = 0; i < cProps; i++) { if (rgItems[i]) { LocalFree(rgItems[i]); } } LocalFree(rgItems); rgItems = NULL; } } if (0xFFFFFFFF == SetFilePointer(hFile, ulStart, NULL, FILE_BEGIN)) { DebugTrace("CountCSVRows SetFilePointer -> %u\n", GetLastError()); } return(hResult); } BOOL TestCSVName(ULONG index, LPPROP_NAME lpImportMapping, ULONG ulcFields, PUCHAR * rgItems, ULONG cProps, BOOL fTryUnchosen) { return((index != NOT_FOUND) && index < ulcFields && index < cProps && (fTryUnchosen || lpImportMapping[index].fChosen) && rgItems[index] && rgItems[index][0]); } /*************************************************************************** Name : MakeDisplayName Purpose : Forms a display name based on the values of various props. Parameters: lppDisplayName -> Returned display name. This should only be used for certain purposes. It can be used for error dialogs, but if it was generated from first/middle/last, it should not be used for PR_DISPLAY_NAME! lpImportMapping = import mapping table ulcFields = size of import mapping table rgItems = fields for this CSV item cProps = number of fields in rgItems iDisplayName = indicies of name related props iNickname iSurname iGivenName iMiddleName iEmailAddress iCompanyName Returns : index of attribute forming the display name, or if FML, return INDEX_FIRST_MIDDLE_LAST. Comment : Form the display name based on these rules: 1. If there's already a display name and it's chosen, use it. 2. if there's a chosen first, middle or last name, add them together and use them. 3. if there's a chosen nickname, use it 4. if there's a chosen email-address, use it. 5. if there's a chosen company name, use it. 6. look again without regard to whether it was chosen or not. ***************************************************************************/ ULONG MakeDisplayName(LPTSTR * lppDisplayName, LPPROP_NAME lpImportMapping, ULONG ulcFields, PUCHAR * rgItems, ULONG cProps, ULONG iDisplayName, ULONG iNickname, ULONG iSurname, ULONG iGivenName, ULONG iMiddleName, ULONG iEmailAddress, ULONG iCompanyName) { BOOL fTryUnchosen = FALSE; BOOL fSurname = FALSE; BOOL fGivenName = FALSE; BOOL fMiddleName = FALSE; ULONG index = NOT_FOUND; ULONG ulSize = 0; LPTSTR lpDisplayName = NULL; try_again: if (TestCSVName(iDisplayName, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen)) { index = iDisplayName; goto found; } if (TestCSVName(iSurname, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen) || TestCSVName(iGivenName, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen) || TestCSVName(iMiddleName, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen)) { index = INDEX_FIRST_MIDDLE_LAST; goto found; } if (TestCSVName(iNickname, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen)) { index = iNickname; goto found; } if (TestCSVName(iEmailAddress, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen)) { index = iEmailAddress; goto found; } if (TestCSVName(iCompanyName, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen)) { index = iCompanyName; goto found; } if (! fTryUnchosen) { fTryUnchosen = TRUE; goto try_again; } found: *lppDisplayName = NULL; switch (index) { case NOT_FOUND: break; case INDEX_FIRST_MIDDLE_LAST: if (fSurname = TestCSVName(iSurname, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen)) { ulSize += (lstrlen(rgItems[iSurname]) + 1); } if (fGivenName = TestCSVName(iGivenName, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen)) { ulSize += (lstrlen(rgItems[iGivenName]) + 1); } if (fMiddleName = TestCSVName(iMiddleName, lpImportMapping, ulcFields, rgItems, cProps, fTryUnchosen)) { ulSize += (lstrlen(rgItems[iMiddleName]) + 1); } Assert(ulSize); if (lpDisplayName = *lppDisplayName = LocalAlloc(LPTR, ulSize)) { // BUGBUG: This does not localize. The effect is that in the collision/error // dialogs, we will get the order of names wrong. It should not effect the properties // actually stored on the object since we won't set PR_DISPLAY_NAME if it was // generated by First/Middle/Last. I can live with this, but we'll see if the // testers find it. BruceK if (fGivenName) { StrCatBuff(lpDisplayName, rgItems[iGivenName], ulSize); } if (fMiddleName) { if (*lpDisplayName) { StrCatBuff(lpDisplayName, " ", ulSize); } StrCatBuff(lpDisplayName, rgItems[iMiddleName], ulSize); } if (fSurname) { if (*lpDisplayName) { StrCatBuff(lpDisplayName, " ", ulSize); } StrCatBuff(lpDisplayName, rgItems[iSurname], ulSize); } } break; default: ulSize = lstrlen(rgItems[index]) + 1; if (*lppDisplayName = LocalAlloc(LPTR, ulSize)) { StrCpyN(*lppDisplayName, rgItems[index], ulSize); } break; } return(index); } #define MAX_SEP 20 void GetListSeparator(LPTSTR szBuf) { // Buffer is assumed to be MAX_SEP chars long if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SLIST, szBuf, MAX_SEP)) { szBuf[0] = TEXT(','); szBuf[1] = 0; } } HRESULT CSVImport(HWND hWnd, LPADRBOOK lpAdrBook, LPWABOBJECT lpWABObject, LPWAB_PROGRESS_CALLBACK lpProgressCB, LPWAB_EXPORT_OPTIONS lpOptions) { HRESULT hResult = hrSuccess; register ULONG i; ULONG cbWABEID, ulObjType; ULONG index; ULONG ulLastChosenProp = 0; ULONG ulcFields = 0; ULONG cProps; ULONG ulCreateFlags = CREATE_CHECK_DUP_STRICT; TCHAR szBuffer[MAX_RESOURCE_STRING + 1]; WAB_PROGRESS Progress; LPABCONT lpContainer = NULL; HANDLE hFile = INVALID_HANDLE_VALUE; TCHAR rgFileName[MAX_PATH + 1] = ""; PUCHAR * rgItems = NULL; REPLACE_INFO RI; LPMAPIPROP lpMailUserWAB = NULL; SPropValue sPropVal; BOOL fSkipSetProps; LPTSTR lpDisplayName = NULL, lpEmailAddress = NULL; ULONG iEmailAddress = NOT_FOUND, iDisplayName = NOT_FOUND, iSurname = NOT_FOUND, iGivenName = NOT_FOUND, iCompanyName = NOT_FOUND, iMiddleName = NOT_FOUND, iNickname = NOT_FOUND, iDisplay = NOT_FOUND; TCHAR szSep[MAX_SEP]; SetGlobalBufferFunctions(lpWABObject); // Read in the Property Name strings to the PropNames array for (i = 0; i < NUM_EXPORT_PROPS; i++) { rgPropNames[i].lpszName = LoadAllocString(rgPropNames[i].ids); Assert(rgPropNames[i].lpszName); DebugTrace("Property 0x%08x name: %s\n", rgPropNames[i].ulPropTag, rgPropNames[i].lpszName); } GetListSeparator(szSep); // Present UI Wizard if (hResult = ImportWizard(hWnd, rgFileName, ARRAYSIZE(rgFileName), rgPropNames, szSep, &lpImportMapping, &ulcFields, &hFile)) { goto exit; } Assert(hFile != INVALID_HANDLE_VALUE); // Find name props and last chosen property for (i = 0; i < ulcFields; i++) { if (lpImportMapping[i].fChosen) { ulLastChosenProp = i; } switch (lpImportMapping[i].ulPropTag) { case PR_EMAIL_ADDRESS: iEmailAddress = i; break; case PR_DISPLAY_NAME: iDisplayName = i; break; case PR_SURNAME: iSurname = i; break; case PR_GIVEN_NAME: iGivenName = i; break; case PR_COMPANY_NAME: iCompanyName = i; break; case PR_MIDDLE_NAME: iMiddleName = i; break; case PR_NICKNAME: iNickname = i; break; } } // // Open the WAB's PAB container: fills global lpCreateEIDsWAB // if (hResult = LoadWABEIDs(lpAdrBook, &lpContainer)) { goto exit; } // // All set... now loop through the file lines, adding each to the WAB // // How many lines are there? if (hResult = CountCSVRows(hFile, szSep, &ulcEntries)) { goto exit; } DebugTrace("CSV file contains %u entries\n", ulcEntries); // Initialize the Progress Bar Progress.denominator = max(ulcEntries, 1); Progress.numerator = 0; if (LoadString(hInst, IDS_STATE_IMPORT_MU, szBuffer, sizeof(szBuffer))) { DebugTrace("Status Message: %s\n", szBuffer); Progress.lpText = szBuffer; } else { DebugTrace("Cannot load resource string %u\n", IDS_STATE_IMPORT_MU); Progress.lpText = NULL; } lpProgressCB(hWnd, &Progress); while (hResult == hrSuccess) { // Read the CSV attributes if (hResult = ReadCSVLine(hFile, szSep, &cProps, &rgItems)) { DebugTrace("ReadCSVLine -> %x\n", GetScode(hResult)); if (GetScode(hResult) == MAPI_E_NOT_FOUND) { // EOF hResult = hrSuccess; } break; // nothing more to read } iDisplay = iDisplayName; if (TestCSVName(iEmailAddress, lpImportMapping, ulcFields, rgItems, cProps, TRUE)) { lpEmailAddress = rgItems[iEmailAddress]; } switch (index = MakeDisplayName(&lpDisplayName, lpImportMapping, ulcFields, rgItems, cProps, iDisplayName, iNickname, iSurname, iGivenName, iMiddleName, iEmailAddress, iCompanyName)) { case NOT_FOUND: // No name props // BUGBUG: Should give special error? break; case INDEX_FIRST_MIDDLE_LAST: break; default: iDisplay = index; break; } // Should be the same number of fields in every entry, but if not, // we'll handle it below. // Assert(cProps == ulcFields); // Outlook does this! ulCreateFlags = CREATE_CHECK_DUP_STRICT; if (lpOptions->ReplaceOption == WAB_REPLACE_ALWAYS) { ulCreateFlags |= CREATE_REPLACE; } retry: // Create a new wab mailuser if (HR_FAILED(hResult = lpContainer->lpVtbl->CreateEntry(lpContainer, lpCreateEIDsWAB[iconPR_DEF_CREATE_MAILUSER].Value.bin.cb, (LPENTRYID)lpCreateEIDsWAB[iconPR_DEF_CREATE_MAILUSER].Value.bin.lpb, ulCreateFlags, &lpMailUserWAB))) { DebugTrace("CreateEntry(WAB MailUser) -> %x\n", GetScode(hResult)); goto exit; } for (i = 0; i <= min(ulLastChosenProp, cProps); i++) { if (lpImportMapping[i].fChosen && lpImportMapping[i].lpszName) { if (rgItems[i] && *rgItems[i]) { // Look it up in the WAB property names table DebugTrace("Prop %u: <%s> %s\n", i, lpImportMapping[i].lpszName, rgItems[i]); sPropVal.ulPropTag = lpImportMapping[i].ulPropTag; Assert(PROP_TYPE(lpImportMapping[i].ulPropTag) == PT_TSTRING); sPropVal.Value.LPSZ = rgItems[i]; fSkipSetProps = FALSE; if (sPropVal.ulPropTag == PR_EMAIL_ADDRESS) { if (! IsInternetAddress(sPropVal.Value.LPSZ)) { DebugTrace("Found non-SMTP address %s\n", sPropVal.Value.LPSZ); if (HandleImportError(hWnd, 0, WAB_W_BAD_EMAIL, lpDisplayName, lpEmailAddress, lpOptions)) { hResult = ResultFromScode(MAPI_E_USER_CANCEL); goto exit; } lpEmailAddress = NULL; fSkipSetProps = TRUE; } } if (! fSkipSetProps) { // Set the property on the WAB entry if (HR_FAILED(hResult = lpMailUserWAB->lpVtbl->SetProps(lpMailUserWAB, 1, // cValues &sPropVal, // property array NULL))) { // problems array DebugTrace("ImportEntry:SetProps(WAB) -> %x\n", GetScode(hResult)); goto exit; } // [PaulHi] 3/4/99 Raid 73637 // If we have a valid email address then we need to also add the // PR_ADDRTYPE property set to "SMTP". if (sPropVal.ulPropTag == PR_EMAIL_ADDRESS) { sPropVal.ulPropTag = PR_ADDRTYPE; sPropVal.Value.LPSZ = (LPTSTR)szSMTP; hResult = lpMailUserWAB->lpVtbl->SetProps( lpMailUserWAB, 1, &sPropVal, NULL); if (HR_FAILED(hResult)) { DebugTrace("CSV ImportEntry:SetProps(WAB) for PR_ADDRTYPE -> %x\n", GetScode(hResult)); goto exit; } } } } } } if (index != iDisplayName && index != NOT_FOUND && index != INDEX_FIRST_MIDDLE_LAST) { // Set the PR_DISPLAY_NAME sPropVal.ulPropTag = PR_DISPLAY_NAME; sPropVal.Value.LPSZ = rgItems[index]; // Set the property on the WAB entry if (HR_FAILED(hResult = lpMailUserWAB->lpVtbl->SetProps(lpMailUserWAB, 1, // cValues &sPropVal, // property array NULL))) { // problems array DebugTrace("ImportEntry:SetProps(WAB) -> %x\n", GetScode(hResult)); goto exit; } } // Save the new wab mailuser or distlist if (HR_FAILED(hResult = lpMailUserWAB->lpVtbl->SaveChanges(lpMailUserWAB, KEEP_OPEN_READONLY | FORCE_SAVE))) { if (GetScode(hResult) == MAPI_E_COLLISION) { /* // Find the display name Assert(lpDisplayName); if (! lpDisplayName) { DebugTrace("Collision, but can't find PR_DISPLAY_NAME in entry\n"); goto exit; } */ // WAB replaces no Display Names with Unknown // Do we need to prompt? if (lpOptions->ReplaceOption == WAB_REPLACE_PROMPT) { // Prompt user with dialog. If they say YES, we should try again RI.lpszDisplayName = lpDisplayName ? lpDisplayName : ""; RI.lpszEmailAddress = lpEmailAddress; RI.ConfirmResult = CONFIRM_ERROR; RI.lpImportOptions = lpOptions; DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_ImportReplace), hWnd, ReplaceDialogProc, (LPARAM)&RI); switch (RI.ConfirmResult) { case CONFIRM_YES: case CONFIRM_YES_TO_ALL: // YES // NOTE: recursive Migrate will fill in the SeenList entry // go try again! lpMailUserWAB->lpVtbl->Release(lpMailUserWAB); lpMailUserWAB = NULL; ulCreateFlags |= CREATE_REPLACE; goto retry; break; case CONFIRM_ABORT: hResult = ResultFromScode(MAPI_E_USER_CANCEL); goto exit; default: // NO break; } } hResult = hrSuccess; } else { DebugTrace("SaveChanges(WAB MailUser) -> %x\n", GetScode(hResult)); } } // Clean up if (rgItems) { for (i = 0; i < cProps; i++) { if (rgItems[i]) { LocalFree(rgItems[i]); } } LocalFree(rgItems); rgItems = NULL; } // Update progress bar Progress.numerator++; // TEST CODE! if (Progress.numerator == Progress.denominator) { // Done? Do I need to do anything? } lpProgressCB(hWnd, &Progress); if (lpMailUserWAB) { lpMailUserWAB->lpVtbl->Release(lpMailUserWAB); lpMailUserWAB = NULL; } if (lpDisplayName) { LocalFree(lpDisplayName); lpDisplayName = NULL; } // if (hResult) { // if (HandleExportError(hWnd, 0, hResult, lpRow->aRow[0].lpProps[iptaColumnsPR_DISPLAY_NAME].Value.LPSZ)) { // hResult = ResultFromScode(MAPI_E_USER_CANCEL); // } else { // hResult = hrSuccess; // } // } } exit: if (hFile) { CloseHandle(hFile); } if (lpDisplayName) { LocalFree(lpDisplayName); } // Don't free lpEmailAddress! It's part of the rgItems below. // Free the WAB objects if (lpMailUserWAB) { lpMailUserWAB->lpVtbl->Release(lpMailUserWAB); } if (lpContainer) { lpContainer->lpVtbl->Release(lpContainer); } // Free the prop name strings for (i = 0; i < NUM_EXPORT_PROPS; i++) { if (rgPropNames[i].lpszName) { LocalFree(rgPropNames[i].lpszName); } } // Free any CSV attributes left if (rgItems) { for (i = 0; i < cProps; i++) { if (rgItems[i]) { LocalFree(rgItems[i]); } } LocalFree(rgItems); } if (lpCreateEIDsWAB) { WABFreeBuffer(lpCreateEIDsWAB); lpCreateEIDsWAB = NULL; } return(hResult); } /*************************************************************************** Name : CreateCSVFile Purpose : Creates a CSV file for export Parameters: hwnd = main dialog window lpFileName = filename to create lphFile -> returned file handle Returns : HRESULT Comment : ***************************************************************************/ HRESULT CreateCSVFile(HWND hwnd, LPTSTR lpFileName, LPHANDLE lphFile) { LPTSTR lpFilter; TCHAR szFileName[MAX_PATH + 1] = ""; OPENFILENAME ofn; HANDLE hFile = INVALID_HANDLE_VALUE; HRESULT hResult = hrSuccess; if (INVALID_HANDLE_VALUE == (hFile = CreateFile(lpFileName, GENERIC_WRITE, 0, // sharing NULL, CREATE_NEW, FILE_FLAG_SEQUENTIAL_SCAN, NULL))) { if (GetLastError() == ERROR_FILE_EXISTS) { // Ask user if they want to overwrite switch (ShowMessageBoxParam(hwnd, IDE_CSV_EXPORT_FILE_EXISTS, MB_ICONEXCLAMATION | MB_YESNO | MB_SETFOREGROUND, lpFileName)) { case IDYES: if (INVALID_HANDLE_VALUE == (hFile = CreateFile(lpFileName, GENERIC_WRITE, 0, // sharing NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL))) { ShowMessageBoxParam(hwnd, IDE_CSV_EXPORT_FILE_ERROR, MB_ICONERROR, lpFileName); hResult = ResultFromScode(MAPI_E_NOT_FOUND); } break; default: DebugTrace("ShowMessageBoxParam gave unknown return\n"); case IDNO: // nothing to do here hResult = ResultFromScode(MAPI_E_USER_CANCEL); break; } } else { ShowMessageBoxParam(hwnd, IDE_CSV_EXPORT_FILE_ERROR, MB_ICONERROR, lpFileName); hResult = ResultFromScode(MAPI_E_NOT_FOUND); } } if (! hResult) { *lphFile = hFile; } return(hResult); } /*************************************************************************** Name : WriteCSV Purpose : Writes a string to a CSV file with fixups for special characters Parameters: hFile = file handle fFixup = TRUE if we should check for special characters lpString = nul-terminated string to write szSep = list separator (only needed if fFixup is TRUE) Returns : HRESULT Comment : CSV special characters are szSep, CR and LF. If they occur in the string, we should wrap the entire string in quotes. ***************************************************************************/ HRESULT WriteCSV(HANDLE hFile, BOOL fFixup, const UCHAR * lpString, LPTSTR szSep) { HRESULT hResult = hrSuccess; ULONG cWrite = lstrlen((LPTSTR)lpString); ULONG cbWritten; BOOL fQuote = FALSE; register ULONG i; ULONG ec; LPTSTR szSepT; // Is there a szSep, a CR or a LF in the string? // If so, enclose the string in quotes. if (fFixup) { szSepT = szSep; for (i = 0; i < cWrite && ! fQuote; i++) { if (lpString[i] == (UCHAR)(*szSepT)) { szSepT++; if (*szSepT == '\0') fQuote = TRUE; } else { szSepT = szSep; if ((lpString[i] == '\n') || (lpString[i] == '\r')) fQuote = TRUE; } } } if (fQuote) { if (! WriteFile(hFile, szQuote, 1, &cbWritten, NULL)) { ec = GetLastError(); DebugTrace("WriteCSV:WriteFile -> %u\n", ec); if (ec == ERROR_HANDLE_DISK_FULL || ec == ERROR_DISK_FULL) { hResult = ResultFromScode(MAPI_E_NOT_ENOUGH_DISK); } else { hResult = ResultFromScode(MAPI_E_DISK_ERROR); } goto exit; } } if (! WriteFile(hFile, lpString, cWrite, &cbWritten, NULL)) { ec = GetLastError(); DebugTrace("WriteCSV:WriteFile -> %u\n", ec); if (ec == ERROR_HANDLE_DISK_FULL || ec == ERROR_DISK_FULL) { hResult = ResultFromScode(MAPI_E_NOT_ENOUGH_DISK); } else { hResult = ResultFromScode(MAPI_E_DISK_ERROR); } goto exit; } if (fQuote) { if (! WriteFile(hFile, szQuote, 1, &cbWritten, NULL)) { ec = GetLastError(); DebugTrace("WriteCSV:WriteFile -> %u\n", ec); if (ec == ERROR_HANDLE_DISK_FULL || ec == ERROR_DISK_FULL) { hResult = ResultFromScode(MAPI_E_NOT_ENOUGH_DISK); } else { hResult = ResultFromScode(MAPI_E_DISK_ERROR); } goto exit; } } exit: return(hResult); } HRESULT ExportCSVMailUser(HANDLE hFile, ULONG ulPropNames, ULONG ulLastProp, LPPROP_NAME lpPropNames, LPSPropTagArray lppta, LPTSTR szSep, LPADRBOOK lpAdrBook, ULONG cbEntryID, LPENTRYID lpEntryID) { HRESULT hResult = hrSuccess; LPMAILUSER lpMailUser = NULL; ULONG ulObjType; ULONG cProps; LPSPropValue lpspv = NULL; ULONG i; const UCHAR szCRLF[] = "\r\n"; UCHAR szBuffer[11] = ""; if (hResult = lpAdrBook->lpVtbl->OpenEntry(lpAdrBook, cbEntryID, lpEntryID, NULL, 0, &ulObjType, (LPUNKNOWN *)&lpMailUser)) { DebugTrace("WAB OpenEntry(mailuser) -> %x\n", GetScode(hResult)); goto exit; } if ((HR_FAILED(hResult = lpMailUser->lpVtbl->GetProps(lpMailUser, lppta, 0, &cProps, &lpspv)))) { DebugTrace("ExportCSVMailUser: GetProps() -> %x\n", GetScode(hResult)); goto exit; } for (i = 0; i < ulPropNames; i++) { if (rgPropNames[i].fChosen) { // Output the value switch (PROP_TYPE(lpspv[i].ulPropTag)) { case PT_TSTRING: if (hResult = WriteCSV(hFile, TRUE, lpspv[i].Value.LPSZ, szSep)) { goto exit; } break; case PT_LONG: wnsprintf(szBuffer, ARRAYSIZE(szBuffer), "%u", lpspv[i].Value.l); if (hResult = WriteCSV(hFile, TRUE, szBuffer, szSep)) { goto exit; } break; default: DebugTrace("CSV export: unsupported property 0x%08x\n", lpspv[i].ulPropTag); Assert(FALSE); // fall through to skip case PT_ERROR: // skip it. break; } if (i != ulLastProp) { // Output the seperator if (hResult = WriteCSV(hFile, FALSE, szSep, NULL)) { goto exit; } } } } if (hResult = WriteCSV(hFile, FALSE, szCRLF, NULL)) { goto exit; } exit: if (lpspv) { WABFreeBuffer(lpspv); } if (lpMailUser) { lpMailUser->lpVtbl->Release(lpMailUser); } return(hResult); } HRESULT CSVExport(HWND hWnd, LPADRBOOK lpAdrBook, LPWABOBJECT lpWABObject, LPWAB_PROGRESS_CALLBACK lpProgressCB, LPWAB_EXPORT_OPTIONS lpOptions) { HRESULT hResult = hrSuccess; register ULONG i; ULONG cbWABEID, ulObjType; ULONG ulLastChosenProp = 0; WAB_PROGRESS Progress; ULONG cRows = 0; LPSRowSet lpRow = NULL; ULONG ulCount = 0; SRestriction restrictObjectType; SPropValue spvObjectType; LPENTRYID lpWABEID = NULL; LPABCONT lpContainer = NULL; LPMAPITABLE lpContentsTable = NULL; HANDLE hFile = INVALID_HANDLE_VALUE; LPSPropTagArray lppta = NULL; const UCHAR szCRLF[] = "\r\n"; TCHAR szSep[MAX_SEP]; TCHAR rgFileName[MAX_PATH + 1] = ""; SetGlobalBufferFunctions(lpWABObject); // Read in the Property Name strings for (i = 0; i < NUM_EXPORT_PROPS; i++) { rgPropNames[i].lpszName = LoadAllocString(rgPropNames[i].ids); Assert(rgPropNames[i].lpszName); DebugTrace("Property 0x%08x name: %s\n", rgPropNames[i].ulPropTag, rgPropNames[i].lpszName); } // Present UI Wizard if (hResult = ExportWizard(hWnd, rgFileName, ARRAYSIZE(rgFileName), rgPropNames)) { goto exit; } // Find the last prop name chosen for (i = NUM_EXPORT_PROPS - 1; i > 0; i--) { if (rgPropNames[i].fChosen) { ulLastChosenProp = i; break; } } // // Open the WAB's PAB container // if (hResult = lpAdrBook->lpVtbl->GetPAB(lpAdrBook, &cbWABEID, &lpWABEID)) { DebugTrace("WAB GetPAB -> %x\n", GetScode(hResult)); goto exit; } else { if (hResult = lpAdrBook->lpVtbl->OpenEntry(lpAdrBook, cbWABEID, // size of EntryID to open lpWABEID, // EntryID to open NULL, // interface 0, // flags &ulObjType, (LPUNKNOWN *)&lpContainer)) { DebugTrace("WAB OpenEntry(PAB) -> %x\n", GetScode(hResult)); goto exit; } } // // All set... now loop through the WAB's entries, exporting each one // if (HR_FAILED(hResult = lpContainer->lpVtbl->GetContentsTable(lpContainer, 0, // ulFlags &lpContentsTable))) { DebugTrace("WAB GetContentsTable(PAB Table) -> %x\n", GetScode(hResult)); goto exit; } // Set the columns to those we're interested in if (hResult = lpContentsTable->lpVtbl->SetColumns(lpContentsTable, (LPSPropTagArray)&ptaColumns, 0)) { DebugTrace("WAB SetColumns(PAB Table) -> %x\n", GetScode(hResult)); goto exit; } // Restrict the table to MAPI_MAILUSERs spvObjectType.ulPropTag = PR_OBJECT_TYPE; spvObjectType.Value.l = MAPI_MAILUSER; restrictObjectType.rt = RES_PROPERTY; restrictObjectType.res.resProperty.relop = RELOP_EQ; restrictObjectType.res.resProperty.ulPropTag = PR_OBJECT_TYPE; restrictObjectType.res.resProperty.lpProp = &spvObjectType; if (HR_FAILED(hResult = lpContentsTable->lpVtbl->Restrict(lpContentsTable, &restrictObjectType, 0))) { DebugTrace("WAB Restrict (MAPI_MAILUSER) -> %x\n", GetScode(hResult)); goto exit; } // How many MailUser entries are there? ulcEntries = CountRows(lpContentsTable, FALSE); DebugTrace("WAB contains %u MailUser entries\n", ulcEntries); if (ulcEntries == 0) { DebugTrace("WAB has no entries, nothing to export.\n"); goto exit; } // Initialize the Progress Bar Progress.denominator = max(ulcEntries, 1); Progress.numerator = 0; Progress.lpText = NULL; lpProgressCB(hWnd, &Progress); // Write out the property names GetListSeparator(szSep); // Create the file (and handle error UI) if (hResult = CreateCSVFile(hWnd, rgFileName, &hFile)) { goto exit; } for (i = 0; i < NUM_EXPORT_PROPS; i++) { // Output the name if (rgPropNames[i].fChosen) { if (hResult = WriteCSV(hFile, TRUE, rgPropNames[i].lpszName, szSep)) { goto exit; } if (i != ulLastChosenProp) { // Output the seperator if (hResult = WriteCSV(hFile, FALSE, szSep, NULL)) { goto exit; } } } } if (hResult = WriteCSV(hFile, FALSE, szCRLF, NULL)) { goto exit; } // Map the prop name array to a SPropTagArray. lppta = LocalAlloc(LPTR, CbNewSPropTagArray(NUM_EXPORT_PROPS)); lppta->cValues = NUM_EXPORT_PROPS; for (i = 0; i < lppta->cValues; i++) { lppta->aulPropTag[i] = rgPropNames[i].ulPropTag; } cRows = 1; while (cRows && hResult == hrSuccess) { // Get the next WAB entry if (hResult = lpContentsTable->lpVtbl->QueryRows(lpContentsTable, 1, // one row at a time 0, // ulFlags &lpRow)) { DebugTrace("QueryRows -> %x\n", GetScode(hResult)); goto exit; } if (lpRow) { if (cRows = lpRow->cRows) { // Yes, single '=' Assert(lpRow->cRows == 1); Assert(lpRow->aRow[0].cValues == iptaColumnsMax); Assert(lpRow->aRow[0].lpProps[iptaColumnsPR_ENTRYID].ulPropTag == PR_ENTRYID); Assert(lpRow->aRow[0].lpProps[iptaColumnsPR_OBJECT_TYPE].ulPropTag == PR_OBJECT_TYPE); if (cRows = lpRow->cRows) { // yes, single '=' // Export mailuser if (hResult = ExportCSVMailUser(hFile, NUM_EXPORT_PROPS, ulLastChosenProp, rgPropNames, lppta, szSep, lpAdrBook, lpRow->aRow[0].lpProps[iptaColumnsPR_ENTRYID].Value.bin.cb, (LPENTRYID)lpRow->aRow[0].lpProps[iptaColumnsPR_ENTRYID].Value.bin.lpb)) { goto exit; } // Update progress bar Progress.numerator++; lpProgressCB(hWnd, &Progress); if (hResult) { if (HandleExportError(hWnd, 0, hResult, lpRow->aRow[0].lpProps[iptaColumnsPR_DISPLAY_NAME].Value.LPSZ, PropStringOrNULL(&lpRow->aRow[0].lpProps[iptaColumnsPR_EMAIL_ADDRESS]), lpOptions)) { hResult = ResultFromScode(MAPI_E_USER_CANCEL); } else { hResult = hrSuccess; } } } // else, drop out of loop, we're done. } WABFreeProws(lpRow); } } exit: if (hFile) { CloseHandle(hFile); } if (lppta) { LocalFree(lppta); } // Free the WAB objects WABFreeBuffer(lpWABEID); lpWABEID = NULL; if (lpContainer) { lpContainer->lpVtbl->Release(lpContainer); lpContainer = NULL; } if (lpContentsTable) { lpContentsTable->lpVtbl->Release(lpContentsTable); lpContentsTable = NULL; } // Free the prop name strings for (i = 0; i < NUM_EXPORT_PROPS; i++) { if (rgPropNames[i].lpszName) { LocalFree(rgPropNames[i].lpszName); } } return(hResult); }