Automatically update PST location (Outlook/MAPI profiles)


Note: updated 9th December 2015 as there was a bug (when processing multiple pst files).  It is also now a Visual Studio 2013 solution.

A recent request was whether it was possible to change the location of pst files using a script.  The actual scenario in this case wasn’t supported, because the pst files were located on a network share (which is a bad idea for a number of reasons), but the question itself is an interesting one.

The location of the pst file is stored in a property on the provider (in this case, the provider is MSUPST MS – the Unicode PST provider).  It is possible to update this property and therefore change the location of the pst file, but this can only be done with extended MAPI (or a third-party tool).  I have written a sample application that can be used to update this property.  It is a console application so can be scripted quite easily, so a simple script could move an existing pst file and then update the MAPI profile accordingly.

Syntax for the application:

UpdatePSTPath c:\old\path c:\new\path

When run, all profiles are checked and if any use the PST provider and the pst file is located in c:\old\path, then c:\old\path is replaced with c:\new\path.  The rest of the path is copied directly.

The code is below, though I have also included the entire Visual Studio 2013 solution as an attachment to this article.  Please note that I am not suggesting this code is reference code – C++ is not my favourite language, and MAPI certainly not my favourite API.  The code works, but may not be the best coding style, etc.  I have error-trapped and allowed for most issues (I think), but no guarantees!

/*
DISCLAIMER:
THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL
MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION
OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
*/

// UpdatePSTPath.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>
#include <InitGuid.h>
#define USES_IID_IMAPIProp
#include <MAPIGuid.h>
#include <mapix.h>
#include <MAPITags.h>
#include <MAPIUtil.h>
#include <MAPIDefS.h>
#include <MAPIUtil.h>
#include <cstring>
#include <wchar.h>
#include <mbstring.h>


#define PR_PST_PATH_W PROP_TAG(PT_UNICODE, 0x6700)
#define MAPI_FORCE_ACCESS 0x00080000

LPSTR oldPath;
LPSTR newPath;
bool bVerbose;

HRESULT ChangePSTLocation(LPPROVIDERADMIN lpProvAdmin)
{
// Change the PST location by modifying PR_PST_PATH_W

LPMAPITABLE lpProviderTable = NULL; // MAPI table pointer
LPMAPIPROP pMAPIProp=NULL;
LPPROFSECT lpProfSect;
LPSRow pRow = NULL;
LPSPropValue pProp;
HRESULT hRes=S_OK;

// Get provider table
hRes=lpProvAdmin->GetProviderTable(0,&lpProviderTable);
SizedSPropTagArray(3, sptProvider) ={3, { PR_INSTANCE_KEY, PR_DISPLAY_NAME, PR_SERVICE_UID}};

// Read providers
LPSRowSet prows=NULL;
ULONG ulRows=0;
hRes = lpProviderTable->SetColumns((LPSPropTagArray)&sptProvider, NULL);
hRes=lpProviderTable->GetRowCount(0, &ulRows);
hRes=lpProviderTable->QueryRows(ulRows,0,&prows);
if (hRes==S_OK)
{
// Now process each provider
for (unsigned int i=0; i<prows->cRows; i++)
{
hRes=lpProvAdmin->OpenProfileSection((LPMAPIUID)prows->aRow[i].lpProps[0].Value.bin.lpb,
NULL,
MAPI_MODIFY | MAPI_FORCE_ACCESS,
&lpProfSect);
if (hRes==S_OK)
{
hRes=lpProfSect->QueryInterface(IID_IMAPIProp, (void**)&pMAPIProp);
if (hRes==S_OK)
{
// Read the current PST path value and update it if necessary
LPSPropValue currentValue;
hRes=HrGetOneProp(pMAPIProp,PR_PST_PATH_W, &currentValue);
if (hRes==S_OK)
{
LPSTR existingFile = new CHAR[lstrlenW((LPWSTR)currentValue[0].Value.lpszW)+1];
WideCharToMultiByte(CP_ACP, 0,
currentValue[0].Value.lpszW,
-1,
existingFile,
lstrlenW(currentValue[0].Value.lpszW)+1,
0, 0);

if (bVerbose) printf("Current value %s\n", existingFile);
int pathlen=strlen(oldPath);

// If the current file path starts with the old path, we need to update it
if (strncmp(existingFile,oldPath,pathlen)==0)
{
// Calculate new path (swap the old path for the new)
int newpathlen=strlen(newPath) + strlen(existingFile) - pathlen;
LPSTR updatedPath=new CHAR[newpathlen+1];
memcpy(updatedPath,newPath,strlen(newPath));
memcpy((updatedPath)+strlen(newPath),
(existingFile)+pathlen,
strlen(existingFile)-pathlen+1);
int a = lstrlenA(updatedPath);
BSTR unicodestr = SysAllocStringLen(NULL, a);
MultiByteToWideChar(CP_ACP, 0, updatedPath, a, unicodestr, a);
wprintf(L"Updated path is %s\n", unicodestr);

// Set the updated path
currentValue[0].Value.lpszW=unicodestr;
hRes=lpProfSect->SetProps(1,currentValue,NULL);
lpProfSect->SaveChanges(0);
}
}
lpProfSect->Release();
}
}
}
FreeProws(prows);
}
lpProviderTable->Release();

return hRes;
}

HRESULT ProcessServices(LPSERVICEADMIN lpSvcAdmin)
{
// Query services and process any PST services found

enum {iDispName, iSvcName, iSvcUID, cptaSvc};
HRESULT hRes=S_OK;
LPPROFADMIN lpProfAdmin = NULL; // Profile Admin pointer
LPPROVIDERADMIN lpProvAdmin = NULL; // Provider Admin pointer
LPMAPITABLE lpMsgSvcTable = NULL; // MAPI table pointer
LPSRowSet lpSvcRows = NULL; // Row set pointer
SRestriction sres;
SPropValue SvcProps;
LPSPropValue lpProp = NULL;

SizedSPropTagArray(cptaSvc,sptCols) = { cptaSvc,
PR_DISPLAY_NAME,
PR_SERVICE_NAME,
PR_SERVICE_UID };
SizedSPropTagArray(1, sptGlobal) = { 1, PR_STORE_PROVIDERS };

// Get service table
hRes = lpSvcAdmin->GetMsgServiceTable( 0, // Flags
&lpMsgSvcTable); // Pointer to table
if (bVerbose) printf("Retrieved message service table from profile.\n");

// Set up restriction to query table
sres.rt = RES_CONTENT;
sres.res.resContent.ulFuzzyLevel = FL_FULLSTRING;
sres.res.resContent.ulPropTag = PR_SERVICE_NAME_A;
sres.res.resContent.lpProp = &SvcProps;
SvcProps.ulPropTag = PR_SERVICE_NAME_A;
SvcProps.Value.lpszA = "MSUPST MS"; // Unicode PST Provider
// Query the table to get the entry for the PST message service
hRes = HrQueryAllRows( lpMsgSvcTable,
(LPSPropTagArray)&sptCols,
&sres,
NULL,
0,
&lpSvcRows);
if (bVerbose) printf("Retrieved table for PST services.\n");

// Now process each PST service
for (unsigned int i=0; i<lpSvcRows->cRows; i++)
{
hRes = lpSvcAdmin->AdminProviders( (LPMAPIUID)lpSvcRows->aRow[i].lpProps[iSvcUID].Value.bin.lpb,
0,
&lpProvAdmin);

hRes=ChangePSTLocation(lpProvAdmin);

lpProvAdmin->Release();
}

FreeProws(lpSvcRows);
lpMsgSvcTable->Release();

return hRes;
}


HRESULT ProcessProfiles()
{
// Process each MAPI profile in turn

HRESULT hRes=S_OK;
LPPROFADMIN lpProfAdmin = NULL; // Profile Admin pointer
LPSERVICEADMIN lpSvcAdmin = NULL; // Message Service Admin pointer
LPMAPITABLE lpProfiles=NULL;

// Retrieve profile table
hRes = MAPIAdminProfiles(0, &lpProfAdmin);
if (!lpProfAdmin)
{
printf("Failed to retrieve profile interface.\n");
return hRes;
}

// Get profile table
hRes=lpProfAdmin->GetProfileTable(0, &lpProfiles);
if (!lpProfiles)
{
printf("Failed to read profiles.\n");
return hRes;
}

// Now go through the profile table and process each one
SizedSPropTagArray(1,profcols) = {1, { PR_DISPLAY_NAME } };
LPSRowSet prows=NULL;
ULONG ulRows=0;
hRes=lpProfiles->GetRowCount(0, &ulRows);
hRes=lpProfiles->QueryRows(ulRows,0,&prows);
if (hRes==S_OK)
{
for (unsigned int i=0; i<prows->cRows; i++)
{
hRes = lpProfAdmin->AdminServices(prows->aRow[i].lpProps->Value.lpszW, // Profile to process
LPWSTR(""), // Password for that profile
NULL, // Handle to parent window
0, // Flags
&lpSvcAdmin); // Pointer to new IMsgServiceAdmin.
if (hRes==S_OK)
{
if (bVerbose) printf("Retrieved IMsgServiceAdmin interface.\n");
hRes=ProcessServices(lpSvcAdmin);
lpSvcAdmin->Release();
}
}
FreeProws(prows);
}

lpProfiles->Release();
lpProfAdmin->Release();

return hRes;
}



int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hRes=S_OK;
bVerbose = false;

// Read arguments (get the old path and new path)
if (argc < 2)
{
printf("Invalid arguments. Expected syntax:\n");
printf("UpdatePSTPath OldPath NewPath");
return -1;
}

oldPath = new CHAR[lstrlenW(argv[1])+1];
WideCharToMultiByte(CP_ACP, 0, argv[1], -1, oldPath, lstrlenW(argv[1])+1, 0, 0);
newPath = new CHAR[lstrlenW(argv[2])+1];
WideCharToMultiByte(CP_ACP, 0, argv[2], -1, newPath, lstrlenW(argv[2])+1, 0, 0);
if ((strlen(oldPath)==0) && (strlen(newPath)==0))
{
printf("Invalid arguments. Expected syntax:\n");
printf("UpdatePSTPath OldPath NewPath");
return -1;
}
printf("Old path: %s\n",oldPath);
printf("New path: %s\n",newPath);

if (argc > 3)
{
if (_tcsicmp(argv[3], _T("verbose")) == 0)
{
bVerbose = true;
printf("VERBOSE MODE\n");
}
}

MAPIInitialize(NULL);
hRes=ProcessProfiles();
MAPIUninitialize();
return hRes;
}

UpdatePSTPath.zip

Comments (1)

  1. SuperJ says:

    Wow … impressive … I really used to wonder how tools like OProfile (now Outlook Profiler) did this …

Skip to main content